《命運(yùn)圣契》是一款百媚養(yǎng)成絕色卡牌手游,由極致游戲研發(fā)及發(fā)行。玩家需要在名為阿克邁斯大陸的奇幻世界里扮演橘屋冒險(xiǎn)團(tuán)團(tuán)長(zhǎng),因意外獲得神器,與百媚少女們締結(jié)契約,共同抵御魔王,從而開啟一段驚險(xiǎn)刺激的冒險(xiǎn)之旅。
作為首款轉(zhuǎn)型 Unity 引擎的產(chǎn)品,極致游戲在開發(fā)《命運(yùn)圣契》的過程中克服了多項(xiàng)技術(shù)難題并實(shí)現(xiàn)了開發(fā)提效。在 12 月 6 日 Unity Open Day 技術(shù)開放日廈門站,我們邀請(qǐng)到了極致游戲的技術(shù)經(jīng)理汪興分享《命運(yùn)圣契》在架構(gòu)、開發(fā)流程、工具等方面服務(wù)于提升開發(fā)效能的技術(shù)方案。以下為演講實(shí)錄。
大家下午好,很高興來到這里和大家分享我們《命運(yùn)圣契》在開發(fā)過程中的一些提效實(shí)踐。
《命運(yùn)圣契》是今年 9 月份上線的一款豎版 3D 養(yǎng)成卡牌游戲。這款游戲采用的是客戶端 Unity + 服務(wù)端 Erlang 的技術(shù)棧。
這款游戲作為我們團(tuán)隊(duì)轉(zhuǎn)型 Unity 引擎的首款產(chǎn)品,也是我們公司首批 Unity 項(xiàng)目之一。因?yàn)閳F(tuán)隊(duì)是從其他的引擎轉(zhuǎn)型過來,所以在前期屬于摸著石頭過河的狀態(tài),Codebase 比較混亂,框架和業(yè)務(wù)代碼會(huì)比較耦合,通用性抽象比較欠缺一些,會(huì)存在一些重復(fù)的工作。在這種情況下,我們想和其他項(xiàng)目進(jìn)行一些技術(shù)共享,也會(huì)比較困難。
針對(duì)這個(gè)情況,我們重新設(shè)計(jì)了我們項(xiàng)目的分層架構(gòu)。
在 Unity 引擎基礎(chǔ)上,我們把項(xiàng)目分為四層。
第一層是 Package 層,這一層除了 Unity 和第三方的一些 package 之外,會(huì)把內(nèi)部具有獨(dú)立重用價(jià)值的模塊進(jìn)行抽象封裝,從而讓我們能夠和其他的項(xiàng)目進(jìn)行技術(shù)的復(fù)用。在 Package 之上,是我們的框架核心層,這一層是我們基于項(xiàng)目的特性、產(chǎn)品的需求,打造的一些核心的組件,這一層和我們的業(yè)務(wù)層進(jìn)行了解耦。再之上是通用業(yè)務(wù)層,是對(duì)一些具有通用性業(yè)務(wù)的抽象,能夠讓我們?cè)陂_發(fā)中能減少重復(fù)。最上面是游戲玩法層。
在這樣一個(gè)分層結(jié)構(gòu)下面,為了維持我們架構(gòu)的整潔性,在 C# 側(cè),會(huì)把每一個(gè) package 封裝為一個(gè)程序級(jí),Package 之上的每一層會(huì)作為一個(gè)程序級(jí)。通過程序級(jí)的依賴管理,能夠去達(dá)到各個(gè)層次的解耦。同時(shí)因?yàn)槲覀冺?xiàng)目組主要是用 Lua 開發(fā),在 Lua 側(cè)也有類似的分層,為了保證 Lua 側(cè)分層的解耦,我們會(huì)在 Lua 側(cè)實(shí)現(xiàn)一個(gè)分層依賴的檢查機(jī)制,當(dāng)我們識(shí)別到 Lua 有下層代碼耦合調(diào)用上層代碼的時(shí)候,我們會(huì)進(jìn)行報(bào)錯(cuò)提示,通過這種方式來實(shí)現(xiàn) Lua 側(cè)分層的解耦。
我們通用業(yè)務(wù)抽象的例子——數(shù)據(jù)資源系統(tǒng),將玩家的各種數(shù)據(jù)進(jìn)行了資源化的抽象,除了常規(guī)的物品之外,有我們的英雄、經(jīng)驗(yàn)等級(jí),乃至于郵件、加成效果這些。把這些數(shù)據(jù)都進(jìn)行了資源化抽象之后,可以進(jìn)行統(tǒng)一的添加扣除、批量操作等等,從而來簡(jiǎn)化我們的業(yè)務(wù)代碼。同時(shí)通過一些統(tǒng)一的安全檢查、編輯處理,保障我們的業(yè)務(wù)安全。
總的來說,通過我們的分層架構(gòu)來說,我們整個(gè) Codebase 變得層次更加清晰,同時(shí)可以從不同維度上面去做代碼的復(fù)用封裝,從而提升我們的開發(fā)效率。
UI模塊
接下來講一下 UI 模塊,我們做一個(gè)數(shù)字卡牌游戲,UI 量級(jí)很大,并且我們的 UI 和 UX 也經(jīng)過數(shù)次大的翻新。最開始通過人工制作 Unity UI Prefab 的方式,這種方式效率就會(huì)比較低,來回溝通核對(duì)的成本就會(huì)比較高。同時(shí)我們做出來的 UI,因?yàn)橥粋€(gè)控件在不同的 UI 上面重復(fù)編輯實(shí)現(xiàn),導(dǎo)致我們統(tǒng)一性會(huì)比較差,細(xì)節(jié)品質(zhì)上面體現(xiàn)出來會(huì)比較低一些。因?yàn)?UI 上的表現(xiàn)狀態(tài)很多,一開始通過代碼控制各個(gè)組合狀態(tài)表現(xiàn),這樣導(dǎo)致我們代碼比較繁瑣,迭代優(yōu)化效率比較低。
針對(duì)這些問題,首先是針對(duì) PSD 自動(dòng)生成 Prefab 的機(jī)制,借助這個(gè)工具,我們美術(shù)在設(shè)計(jì)好 UI 的 PSD 之后,直接通過這個(gè)工具轉(zhuǎn)換成 Unity 里面的 Prefab,整個(gè)過程很高效,同時(shí)也節(jié)省了過程中大量的細(xì)節(jié)溝通成本。
UI 公共組件機(jī)制,在美術(shù)的 PS 里面為它們實(shí)現(xiàn)了一個(gè) UI 的公共組件插件,通過這個(gè)插件,美術(shù)在設(shè)計(jì) UI 的時(shí)候,可以直接從公共組件庫(kù)里面去選擇想要的組件。比如說各式各樣的按紐,通用物品節(jié)點(diǎn),類表樣式框等等。在 Unity 這邊,每個(gè)公共組件會(huì)對(duì)應(yīng)一個(gè)預(yù)制體,在 PSD 導(dǎo)出層 UI 的 Prefab 的時(shí)候,會(huì)把這些公共組件替換成 Unity 的預(yù)制體的引用。通過這種方式,公共組件同一個(gè)組件只需要設(shè)計(jì)開發(fā)一次,這樣我們的開發(fā)效率就得到了提升,并且我們也很容易對(duì)這些公共組件做迭代優(yōu)化,從而能夠最終提高我們的 UI 統(tǒng)一性,提升細(xì)節(jié)的品質(zhì)。
針對(duì) UI 狀態(tài)表現(xiàn)多的問題,我們實(shí)現(xiàn)了狀態(tài)控制器,狀態(tài)控制器能夠讓我們很方便在各個(gè)不同表現(xiàn)狀態(tài)之間進(jìn)行切換,每個(gè)狀態(tài)下會(huì)去控制編輯我們的 transform、圖片、文字等等一系列的組件、參數(shù)。代碼中只需要在各個(gè)狀態(tài)之間做簡(jiǎn)單的切換就行了。這種方式,原來需要寫大量代碼才能實(shí)現(xiàn)的表現(xiàn)效果,現(xiàn)在可以在 UI 上、編輯器上做可視化的編輯,這樣所見即所得的編輯。
Coding 環(huán)節(jié)
接下來講一下 Coding 環(huán)節(jié),因?yàn)槲覀冺?xiàng)目開發(fā)主要是用 Lua 語(yǔ)言,所以為了實(shí)現(xiàn)和 Unity 對(duì)象的高效交互,我們實(shí)現(xiàn)了變量綁定的機(jī)制,這個(gè)機(jī)制的核心在于根據(jù)我們?cè)O(shè)定的 Game Object 的命名規(guī)則,它會(huì)自動(dòng)去搜集需要序列化的組件,搜集起來之后,序列化到場(chǎng)景或者是預(yù)制資源里面。同時(shí)生成右側(cè)圖里面的 Lua 自動(dòng)綁定的代碼,通過這種方式,我們?cè)?Lua 側(cè)很容易操控我們想要操作的組件。在這之外還支持自定義序列化字段,比如說 Int/Float 這種參數(shù),從而幫助我們?nèi)ピ诰庉嬈飨旅嬲{(diào)節(jié)我們組件的參數(shù)。
在變量綁定基礎(chǔ)上,結(jié)合 Lua 對(duì) class 的模擬,以及 Unity 本身預(yù)制體變體的機(jī)制,實(shí)現(xiàn)Lua 組件的變體機(jī)制,通過這個(gè)變體機(jī)制,就能夠比較容易對(duì)我們現(xiàn)有的 Lua 組件進(jìn)行集成和復(fù)用擴(kuò)展。
開發(fā)中還往往會(huì)面對(duì)場(chǎng)景和界面管理復(fù)雜的問題,比如說經(jīng)常會(huì)跳轉(zhuǎn)層級(jí)很深,循環(huán)跳轉(zhuǎn),以及還原狀態(tài)等等的問題。
在面臨這些問題,我們就實(shí)現(xiàn)了導(dǎo)航系統(tǒng),在導(dǎo)航系統(tǒng)里面會(huì)把場(chǎng)景和界面進(jìn)行分組管理。我們的導(dǎo)航系統(tǒng)整體呈現(xiàn)是一個(gè)樹型的管理結(jié)構(gòu),通常一個(gè)玩法模塊會(huì)做一個(gè)分組的節(jié)點(diǎn)進(jìn)行管理,同時(shí)在復(fù)雜的玩法下面,也能夠支持我們多層嵌套的分組管理。
在這種管理機(jī)制下面,當(dāng)我們要從一個(gè)玩法跳轉(zhuǎn)到另外一個(gè)玩法的時(shí)候,我們業(yè)務(wù)上只需要關(guān)心,我去調(diào)用哪一個(gè)玩法的打開接口,導(dǎo)航系統(tǒng)底層會(huì)根據(jù)我們畫面的完整性以及內(nèi)存的占用等等情況,來決定是否要對(duì)上一個(gè)玩法進(jìn)行隱藏或者是卸載。同時(shí)導(dǎo)航系統(tǒng)會(huì)幫我們記錄一下我們打開的場(chǎng)景和界面的信息,幫我們記錄一下我們的跳轉(zhuǎn)路徑。通過這樣一些信息,我們?cè)诜祷氐臅r(shí)候只需要做關(guān)閉當(dāng)前的玩法,導(dǎo)航系統(tǒng)就可以幫我們重新加載或者是還原前一個(gè)玩法。
在這基礎(chǔ)上為了提升玩家操作的流暢度,避免跳轉(zhuǎn)過程中有反復(fù)加載的行為,我們也是實(shí)現(xiàn)了場(chǎng)景和界面的緩存機(jī)制。并且為了應(yīng)對(duì)不同設(shè)備內(nèi)存大小不一樣的情況,也實(shí)現(xiàn)了內(nèi)存的分級(jí)控制。
最后,我們可以看到的這是我們導(dǎo)航系統(tǒng)的狀態(tài)面板,這個(gè)狀態(tài)面板會(huì)詳細(xì)的呈現(xiàn)整個(gè)導(dǎo)航系統(tǒng),以及所有場(chǎng)景和截面的詳細(xì)信息,包括調(diào)用堆棧各種之類的信息,來輔助我們排查復(fù)雜業(yè)務(wù)場(chǎng)景下面的問題。
Debug
接下來講一下 Debug 環(huán)節(jié),在日志模塊首先是實(shí)現(xiàn)了Lua Debug 日志開關(guān)和發(fā)布剪裁機(jī)制。在開發(fā)文件下面,能夠通過我們的可視化界面去對(duì)我們 Debug 日志做文件級(jí)和模塊級(jí)的開關(guān)控制,從而排除其他模塊的日志干擾。并且在正式發(fā)布的時(shí)候,會(huì)把所有的 Lua Debug 日志從我們的代碼里面剪裁剔除掉,這樣開發(fā)文件下面就可以添加很多的 Debug 日志來輔助排查問題,不用擔(dān)心造成其他的影響。
其次是我們的錯(cuò)誤日志監(jiān)控告警機(jī)制,對(duì)線上的錯(cuò)誤都會(huì)進(jìn)行告警和監(jiān)控。當(dāng)發(fā)生錯(cuò)誤的時(shí)候,我們會(huì)第一時(shí)間通過我們的飛書進(jìn)行告警,能夠第一時(shí)間掌握這些線上的錯(cuò)誤信息,同時(shí)也會(huì)在后臺(tái)去把所有的錯(cuò)誤信息統(tǒng)計(jì)起來,幫助我們?nèi)プR(shí)別現(xiàn)在錯(cuò)誤的影響范圍有多大。
最后是我們的日志持久化機(jī)制,通過日志持久化機(jī)制,會(huì)持久化記錄玩家的操作行為,以及相關(guān)的日志信息,結(jié)合我們的主動(dòng)異常上報(bào),玩家發(fā)生異常的時(shí)候可以主動(dòng)上報(bào)客戶端詳細(xì)的狀況,以及相關(guān)的日志,通過截圖以及詳細(xì)日志信息,就可以更好去定位解決我們線上的異常問題。
Debug 這一塊我們還有一個(gè)時(shí)間機(jī)制工具,我們常常要去測(cè)試時(shí)間機(jī)制相關(guān)的玩法,這時(shí)候就實(shí)現(xiàn)了前后端一體時(shí)間修改和加速的工具,這個(gè)工具機(jī)制上面,首先是把邏輯時(shí)間和系統(tǒng)時(shí)間、表現(xiàn)時(shí)間進(jìn)行了拆分,從而實(shí)現(xiàn)了我們?nèi)バ薷挠螒驎r(shí)間或者是實(shí)現(xiàn)游戲的加速不會(huì)對(duì)系統(tǒng)時(shí)間和表現(xiàn)時(shí)間進(jìn)行效果的影響。比如我們對(duì)游戲的邏輯時(shí)間加速 100 倍,這時(shí)候我們的表現(xiàn)不會(huì)有什么鬼畜的效果。
在這種情況下面,當(dāng)我們要去測(cè)試一些周期性或者是持續(xù)性和時(shí)間相關(guān)的玩法,就可以對(duì)我們游戲邏輯時(shí)間做一些比較大的加速倍率,這種情況下可以比較快速測(cè)試一個(gè)游戲玩法周期,讓它實(shí)現(xiàn)一個(gè)比較自然的過渡。
結(jié)合我們的數(shù)據(jù)備份還原工具,通過這個(gè)工具可以幫助我們快速還原問題的場(chǎng)景。比如說通過時(shí)間工具等,可以重現(xiàn)了我們的問題場(chǎng)景之后,我們能夠把這個(gè)場(chǎng)景下面相關(guān)的一系列信息都備份起來,緊接著會(huì)做 Debug 調(diào)試以及做嘗試性的代碼修復(fù)。當(dāng)我們需要再次去驗(yàn)證或者是調(diào)試這個(gè)問題的時(shí)候,我們只需要做一個(gè)一鍵的數(shù)據(jù)還原,就可以還原到我們的問題場(chǎng)景。
結(jié)合這兩個(gè)工具,我們就能夠比較高效的提升我們 Debug 效率。
性能
接下來講一下關(guān)于性能方面提效的一些技術(shù)方案。
首先是渲染遮擋屏蔽機(jī)制,這個(gè)機(jī)制核心的作用是能夠讓我們不去渲染那些被遮擋、被覆蓋的那些場(chǎng)景和界面。典型作用,當(dāng)我們比如說打開一個(gè) UI,去遮擋了下層 UI,或者是遮擋了下層場(chǎng)景之后,會(huì)去禁用下層 UI 或者是下層相機(jī)的渲染,從而減少性能消耗。
首先會(huì)在 UI 上掛載一個(gè)遮擋檢測(cè)的組件,這個(gè)組件上面可以自動(dòng)智能識(shí)別,也可以手動(dòng)調(diào)整 UI 的遮擋和顯示區(qū)域。運(yùn)行時(shí)通過基于線段數(shù)的遮擋檢測(cè)算法,從而去識(shí)別哪些 UI 現(xiàn)在被遮擋住了,我們就可以控制這些 UI,讓它不去做顯示。對(duì)場(chǎng)景上面,我們?cè)趫?chǎng)景相機(jī)上也會(huì)掛一個(gè)控制組件,通過這個(gè)組件發(fā)現(xiàn)場(chǎng)景當(dāng)它被完全遮擋的時(shí)候,我們會(huì)去禁用或者是減少場(chǎng)景相機(jī)的渲染。
在這張動(dòng)圖可以看到,當(dāng)場(chǎng)景上面打開了一個(gè)全屏 UI 之后,通過這種機(jī)制可以明顯減少在這種情況下的渲染消耗。
在資源優(yōu)化方面,實(shí)現(xiàn)了統(tǒng)一可視化的資源導(dǎo)入管理工具,通過這個(gè)工具我們?cè)谫Y源導(dǎo)入的時(shí)候可以做一些紋理尺寸合法性的檢查,統(tǒng)一設(shè)置紋理壓縮格式,mipmap 參數(shù)等等。同時(shí)對(duì)紋理質(zhì)量我們提供了不同檔位可供調(diào)節(jié),這樣在必要情況下可以把部分的紋理設(shè)置成更高的質(zhì)量。我們也會(huì)對(duì)高質(zhì)量的紋理做一些比例控制,防止這一塊紋理的質(zhì)量失控,從而取得我們調(diào)整的靈活性和紋理質(zhì)量性能的平衡。
在壓測(cè)這一塊,我們實(shí)現(xiàn)了前后端一體化的壓測(cè)工具,這個(gè)壓測(cè)工具既能夠?qū)崿F(xiàn)后端性能的測(cè)試,也能夠幫我們測(cè)試前端多人同屏這種性能熱點(diǎn)情況下面的消耗。核心思路,基于流量回放的思路,采用 Locust 的性能框架測(cè)試框架來實(shí)現(xiàn)。最核心的兩點(diǎn)是操作錄制和操作回放。
具體流程上來講,我們會(huì)采用一個(gè)或多個(gè)錄制用戶進(jìn)入我們的游戲,這個(gè)用戶在游戲里面去進(jìn)行正常游戲的行為,我們會(huì)把這個(gè)用戶產(chǎn)生的網(wǎng)絡(luò)相關(guān)的操作轉(zhuǎn)發(fā)到我們的壓測(cè)工具這邊存儲(chǔ)起來,形成我們需要回放的一個(gè)流量數(shù)據(jù),壓測(cè)工具產(chǎn)生的這些壓測(cè)用戶,他們會(huì)按時(shí)間線去把這些需要回放的操作、通信消息,提取出來,去按照時(shí)間線去執(zhí)行,從而和我們的游戲服務(wù)器進(jìn)行交互,進(jìn)而產(chǎn)生壓力。
這種實(shí)現(xiàn)方案的核心好處就在于,第一個(gè)我們大部分情況下面,通過這個(gè)工具不需要編寫任何的代碼就可以實(shí)現(xiàn)我們業(yè)務(wù)的壓測(cè),在業(yè)務(wù)改變的時(shí)候,也不需要維護(hù)我們的壓測(cè)工具。第二個(gè)通過流量回放的方式,可以最大程度貼近我們真實(shí)用戶的行為。
采用這個(gè)技術(shù)方案有兩點(diǎn)核心的挑戰(zhàn)。
第一個(gè)是隨機(jī)性和全局?jǐn)?shù)據(jù)的影響。像游戲里面的戰(zhàn)斗一般都會(huì)有隨機(jī)性,戰(zhàn)斗這種隨機(jī)性帶來的影響可能導(dǎo)致比如說我錄制的時(shí)候這個(gè)戰(zhàn)斗是勝利的,但我們回放的時(shí)候這個(gè)戰(zhàn)斗可能是失敗了,這種失敗可能導(dǎo)致我的壓測(cè)用戶沒辦法順利按照我錄制的消息,能夠順利執(zhí)行下去。針對(duì)這個(gè)問題,核心解決思路是,我們要在服務(wù)端去做個(gè)人玩法業(yè)務(wù)邏輯的隔離,因?yàn)槲覀兊姆?wù)端采用的是 Erlang 模型,Erlang 語(yǔ)言天然自帶 Actor 模型,我們通過 Erlang 語(yǔ)言天然的 Actor 模型的支持,能夠很好去隔離不同用戶的后端邏輯。在這基礎(chǔ)上,實(shí)現(xiàn)了各個(gè)用戶的隔離之后,就可以對(duì)每個(gè)用戶去設(shè)定他們的初始隨機(jī)種子,當(dāng)我們把壓測(cè)用戶和錄制用戶采用相同隨機(jī)種子的時(shí)候,這時(shí)候就能控制這個(gè)隨機(jī)性,從而達(dá)到我們想要控制隨機(jī)性結(jié)果的目的。
全局?jǐn)?shù)據(jù)的影響,典型就是全局的唯一 ID。唯一 ID 因?yàn)橛腥中裕酝ǔ7峙湟粋€(gè)唯一 ID 會(huì)影響到另一個(gè)唯一 ID。這種情況下我們的解決方案是這樣的,我們以玩家抽卡為例子,當(dāng)我們的錄制用戶去做一個(gè)抽卡行為的時(shí)候,服務(wù)端這個(gè)時(shí)候會(huì)生成一張卡牌,這個(gè)卡牌具有全局的唯一 ID,這時(shí)候會(huì)把錄制用戶的 ID 作為前綴去生成這張卡牌的唯一 ID,這樣后續(xù)在錄制用戶再去發(fā)起升級(jí)這張卡牌請(qǐng)求的時(shí)候,我們消息里面就會(huì)帶有卡牌的唯一 ID,去通信到服務(wù)端,服務(wù)端去查找這張卡牌,進(jìn)而升級(jí)。
壓測(cè)用戶在去回放這整個(gè)操作過程中,首先會(huì)回放一個(gè)抽卡的操作,抽卡的操作也會(huì)在服務(wù)端生成一張卡牌。根據(jù)唯一 ID 生成規(guī)則,我們會(huì)把壓測(cè)用戶的 ID 作為新的這張卡牌的 ID 前綴。在這種情況下面,我們的壓測(cè)用戶再去回放卡牌升級(jí)這條消息的時(shí)候,我們會(huì)把消息里面的卡牌 ID 做一個(gè)規(guī)則的替換,把它替換成實(shí)際壓測(cè)用戶抽到這張卡牌的 ID,通過這種方式,就能夠?qū)崿F(xiàn)壓測(cè)用戶即使有受全局性的影響,也能夠正常執(zhí)行回放邏輯。
第二個(gè)挑戰(zhàn)是交互玩法消息回放,對(duì)于交互玩法可能存在的問題是,比如說我們添加一個(gè)好友,錄制的時(shí)候添加一個(gè)人為好友,回放的時(shí)候可能是所有的機(jī)器人,壓測(cè)用戶都會(huì)去添加同一個(gè)人為好友。這個(gè)問題對(duì)此我們的解決方案是,針對(duì)我們的交互類玩法會(huì)編寫針對(duì)性的消息轉(zhuǎn)換邏輯。這個(gè)轉(zhuǎn)換邏輯可以選擇在服務(wù)端測(cè)試環(huán)境下面去編寫這種代碼,通過這種代碼可以實(shí)現(xiàn)我們壓測(cè)用戶隨機(jī)選擇不同的人去添加好友。
最后是線上性能監(jiān)控,對(duì)于性能優(yōu)化來講,能夠真實(shí)掌握線上用戶的真實(shí)數(shù)據(jù)非常關(guān)鍵,所以我們對(duì)線上性能做了全盤的監(jiān)控,既能夠監(jiān)控崩潰的情況,也能夠掌握玩家的設(shè)備分布、質(zhì)量分級(jí)等數(shù)據(jù),以及更大數(shù)據(jù)層面的幀率、卡頓、加載耗時(shí)這些信息,通過這些信息我們能夠設(shè)定我們的優(yōu)先級(jí),優(yōu)先及時(shí)解決我們一些性能的熱點(diǎn)問題。
好的,今天我的分享到此結(jié)束,謝謝大家!
Unity 官方微信
第一時(shí)間了解Unity引擎動(dòng)向,學(xué)習(xí)進(jìn)階開發(fā)技能
每一個(gè)“點(diǎn)贊”、“在看”,都是我們前進(jìn)的動(dòng)力
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.