【GDC2023乾貨分享】開源軟體在引擎開發中的幫助
對於遊戲引擎開發者和維護人員來說,如何高效、便捷、低成本地進行引擎維護,讓引擎能夠得到快速最佳化和功能更新,並使其構建系統能夠相容多個開發平臺和工具,是一個亙古不變的難題。
在GDC2023中,來自網易遊戲《星戰前夜:無燼星河(EVE Echoes)》團隊的引擎程式師KK分享了他們是如何藉助開源軟體,對自研引擎NeoX做和遊戲開發適配的針對性最佳化,並取得良好效果的。
以下為演講摘選(根據英文演講翻譯整理):
大家好,今天我分享的主題是《開源軟體在引擎開發中的幫助》。本次分享分為兩部分,第一部分是如何使用開源軟體來最佳化傳統引擎;其中的主要內容是我們如何將引擎遷移至開源構建系統 Bazel。利用Bazel,我們顯著減少了構建時間,提升了開發效率。
第二部分,我還會分享我們在這過程中學到的如何更好的與開源社群合作的經驗。
透過這次分享,我希望能向你們展示開源工具是非常強大的。它們能夠極大的提升遊戲引擎的開發效率。更重要的是,在一些情況下,開源軟體幾乎是不可或缺的。因為開源的特性使得我們可以對工具本身進行修改,從而滿足我們在遊戲開發中遇到的特殊而複雜的需求。
先介紹下背景,我來自《星戰前夜:無燼星河(EVE Echoes)》團隊。這是一款科幻背景太空遊戲,由網易和 CCP Games 合作研發,遊戲開發用的是網易的自研引擎NeoX。網易旗下多個爆款遊戲也使用了NeoX 引擎開發,如《明日之後》《第五人格》等等。NeoX引擎在網易有著悠久而成功的歷史,但是就像很多其他擁有著較長曆史的程式碼倉庫一樣,NeoX也難免揹負著一些“技術債”。遊戲行業技術日新月異,為了與時俱進,星戰前夜團隊自去年開始逐漸的更新迭代引擎。
一、為什麼使用開源軟體Bazel來最佳化傳統引擎
首先,我們要做的就是最佳化構建時間。
NeoX 是一個功能豐富的跨平臺遊戲開發引擎,但隨之而來的是相對較長的構建時間。舉例來說,在配備六核 Core i7 處理器的 Windows 上,我們引擎的完整構建時間為 27 分鐘。而在 4 核 Core i7的Macbook Pro 上編譯iOS版本時則是 24分鐘。安卓平臺的構建時間也差不多。如果我們更新一些與渲染相關的程式碼,增量構建時間約為 14 分鐘。
想象一下,一個簡單的渲染器更改需要 14 分鐘才能看到結果,這極大地影響著我們的開發效率。因此,在大規模重構程式碼之前,我們希望首先最佳化構建時間。
和龐大的引擎相比,《星戰前夜:無燼星河》擁有一個相對較小的開發團隊,我們沒有獨立的子團隊來負責不同的目標平臺,因此我們希望新的構建系統能夠在所有的平臺上工作。
面臨的挑戰
以下是我們所面臨的挑戰。《星戰前夜: 無燼星河》的主要目標平臺是 iOS 和Android,同時我們的主力開發平臺為Windows;由於 EVE Echoes是一款 MMORPG遊戲,我們還需要在伺服器端,也就是Linux平臺上,編譯部分引擎程式碼,比如核心的戰鬥邏輯。也就是說,我們有 iOS 、Android、Windows、Linux四個不同的目標平臺。
為了構建iOS應用,我們需要使用macOS系統,這也是我們的主要安卓構建平臺。加上Windows和Linux,我們有三個不同的host平臺。
遊戲開發往往是非常複雜的,因此我們需要Xcode、Android Studio和Visual Studio等整合開發環境提供的除錯和分析工具,因此新的構建系統需要能夠與各種整合開發環境(IDE)進行整合,其生成的二進位制檔案必須可以在這些IDE 中進行除錯和效能分析。
程式語言方面,我們主要使用了C++,引擎使用了部分java程式碼用於與Android系統互動。同樣,我們也有用於iOS整合的 Objective C程式碼。
我們需要一個新的構建系統,能夠相容三個作業系統、面向四個目標平臺構建我們的引擎,能夠與多種IDE整合,並支援我們使用的三種程式語言。
最重要的是,構建系統需要能夠支援遠端快取或遠端執行,從而減少構建時間。考慮到上面的所有條件,這似乎是一個不可能完成的任務。
開源構建工具 Bazel
但經過一番研究之後,我們發現了由Google 開源的構建系統 Bazel。Bazel網站首頁上寫著:構建和測試 Java、C++、Android、iOS、Go 和其他各種語言平臺,可以在Windows、macOS 和Linux 上執行。這個工具幾乎就像是為我們量身定做的。
事實上也是如此,藉助Bazel的遠端快取,我們成功地將 Windows 平臺的完整構建時間從 27 分鐘降到 4 分鐘。因為更快的SSD, iOS 和 Android 的完整構建時間甚至可以進一步縮短到了 2.5 分鐘,這對我們的持續整合任務(CI jobs) 有著顯著幫助。此外,遠端執行可以有效地減少增量構建時間,同樣以對render的修改為例,面向iOS平臺的構建時間從 14 分鐘減少到了4 分鐘。值得一提的是,這個構建是由3臺mac mini所組成的“編譯叢集”完成的。如果我們向這個“叢集”中新增更多的裝置,我們還能進一步提高增量構建的效率。
另一方面,考慮到如此複雜的使用場景,你可以想象到,再優秀的開源工具都很難做到直接開箱即用,因此,在接下來的分享中,我將介紹我們遷移至Bazel的完整旅程。
二、如何將引擎遷移至開源軟體 Bazel
在此之前,讓我簡要介紹一下 Bazel 的工作原理。
Bazel 的工作原理
在 Bazel 專案中,我們使用名為 BUILD 或BUILD.Bazel 的檔案來描述構建目標。
這是一個用於構建一個小型數學庫的構建檔案。
cc_library表示這是一個C或C++庫的構建目標。
我們將其名字設定為“math”,其他構建目標如果想要連結這個庫,可以透過在其deps屬性新增這個“math”庫。
hdrs屬性包含所有的標頭檔案。
srcs屬性包含所有的原始碼檔案。
這個“math” 庫本身也有一個依賴,也就是“portable”,用於處理跨平臺開發邏輯。
大部分人更熟悉CMake,這是與之對應的CMakeList.txt檔案,大家可以將其和Bazel的BUILD檔案進行對比。
BUILD 檔案使用的語言為一種Python方言:Starlark 。Bazel 的構建檔案的獨特之處在於它是宣告式的,我們不能在BUILD檔案中使用命令式命令。
Bazel這樣設計主要是為了保證構建的“可重複性”,即對於相同的輸入,始終會得到相同的輸出結果。這是Bazel能夠正確快取你的構建結果的一個重要前提。例如,如果構建過程依賴於 “date” 命令,即使使用相同的輸入檔案,每次構建的結果也會不同,而這樣 Bazel 就無法為你快取結果。
Bazel 的另一個獨特功能是 Sandbox 機制。為了解釋這一點,假設我們的專案中有多個容器的實現。我們定義了兩個 cc_library,一個是 hashmap, 一個是 set。我們對它們分別定義,以便可以最大限度地減少增量構建時間。
在編譯 hashmap 時,Bazel 不會直接在源目錄下進行編譯,而是先建立一個 sandbox 目錄,將 hashmap 標頭檔案和原始檔連結到該目錄,然後在 sandbox 目錄下進行編譯。
這麼做可以防止程式碼意外include未列出的標頭檔案,對於正確的增量構建非常重要,尤其是在涉及遠端快取時。
例如,如果我們不小心在了hashmap.cpp中include了set.h,並使用了其中的一些函式,在有sandbox機制的情況下,編譯會失敗因為在sandbox中找不到set.h。如果沒有sandbox機制則編譯會成功,但當未來set.h進行更新時,構建系統不知道hashmap需要被重新編譯,從而可能導致錯誤的構建結果。
以上就是我們目前所需要了解的關於 Bazel 的資訊。
構建系統遷移到 Bazel
以下是我們將構建系統遷移到 Bazel 的計劃。
- 首先,我們建立所有的構建檔案,以便可以使用 Bazel 構建我們的引擎。
- 接著嘗試將 Bazel 與我們正在使用的所有三個整合開發環境整合。
- 最後,搭建遠端伺服器用於遠端快取和執行。
在使用 Bazel 之前,我們使用 CMake 生成原生的 Visual Studio、Xcode 和Android Studio工程來構建我們的引擎。我們主要使用 Conan 來管理第三方庫依賴。我們的目標是在不影響開發進度的情況下,將構建系統從 CMake 無縫遷移到 Bazel。
我們從依賴樹的底層節點開始。下圖是我們引擎內部依賴樹的簡化版本。頂部是名為 client 的可執行目標。它依賴於其他庫,如“world”和“render”。
在依賴樹的底部是通用的utility lib (通用工具庫)和 portable lib (可移植庫)。因為它們沒有更多的內部依賴項,所以可以輕鬆的在 Bazel 中構建。
我們先編寫portable的構建檔案並使用Bazel對其進行編譯。
在我們使用 Bazel 成功構建 portable 之後,我們需要允許CMake 專案中的其他目標能夠連結它。
這裡要用到 CMake 的"IMPORTED" 庫。我們可以在 CMake 專案中匯入 Bazel 生成的二進位制檔案。這樣一來,CMake 專案中的其他庫就可以連結到它。
我們還希望在 CMake 專案中自動觸發 Bazel 構建。在這裡我們使用add_custom_target,即定義一個自定義目標,當這個目標被構建時會觸發bazel build命令來觸發 Bazel 構建。我們設定IMPORTED庫portable依賴於這個自定義目標portable_bazel_build,這樣每當我們需要import portable時,相關的bazel構建命令就會被執行。
現在我們可以在 Bazel 中構建 “Portable” 併成功連結到 CMake 專案中。我們需要做的就是自下而上重複這個過程,直到整個引擎都可以使用Bazel進行編譯。
遷移流程示意圖
在實踐中,我們遇到的第一個問題實際上是來自 Conan ,而不是 Bazel 本身。
使用Bazel構建時,我們同樣需要第三方依賴。理論上我們可以使用Bazel直接從原始碼編譯所有的第三方依賴管理, Bazel也非常適合這種模式。但是我們沒有太多時間為我們所有的依賴項編寫 Bazel 構建檔案。此外,NeoX 引擎團隊已經在使用 Conan 管理所有第三方依賴項和升級了,我們不希望花費額外的時間單獨維護第三方依賴,因此我們需要實現 Bazel 和 Conan 的整合。
好在 Conan 提供了 Bazel 的構建生成器(Conan Bazel Generator)。和 CMake一樣,Bazel也支援使用cc_import 匯入預編譯的lib。
Conan 的 Bazel 構建生成器工作原理是這樣的。首先其從 Conan 配置檔案中讀取所有依賴項。接著從伺服器下載所有需要的庫,並將它們儲存在磁碟上。然後生成器會為所有的第三方庫自動生成對應的BUILD檔案。這樣你就可以在Bazel工程中使用它們了。
不幸的是 Bazel 構建生成器在當時還處於試驗階段,無法為我們的某些依賴正確生成BUILD檔案。
如果 Conan 不是開源軟體,我們就需要軟體供應商來解決這個問題。考慮到同時使用 Conan 和 Bazel 並不是一個常見的需求,我們可能需要等待很長一段時間這個問題才能被解決,或者根本不會被修復。即便是在最好的情況下,我們的整個遷移進度也會受到影響。
好在 Conan 是一個開源工具,所以我們可以自行修復 bug。
例如,我們碰到的其中一個具體問題是,在匯入 DLL 庫時,我們需要設定另一個名為“interface_library”的屬性。
新增起來其實很容易,所以我們為此建立了一個合併請求(Pull Request,下文簡稱PR)。
我們還修復了另一個lib 檔案路徑解析相關的問題。
在修復了 Bazel 構建生成器之後,遷移工作進展順利。我們遇到的大多數問題都與引擎本身有關。兩個月後,我們得到了第一個完全使用 Bazel 構建的引擎。
將 Bazel 整合到整合開發環境
現在我們需要將 Bazel 整合到整合開發環境中。
針對 Visual Studio專案,我們使用了開源工具 Lavender 。Lavender 會生成一個包含所有原始檔和編譯器引數的 Visual Studio 專案。但構建實際上是透過呼叫 Bazel 命令完成的。這是 Lavender 生成的 Visual Studio 專案檔案。專案仍然是用 Bazel 構建的,但是 Visual Studio 能夠正確獲取原始碼和生成的庫的位置,就可以對庫進行debug和分析。
我們還提供了所有前處理器定義和標頭檔案搜尋路徑,使得 Code Intelligence 可以正常工作。
不幸的是,Lavender 已經不再維護,而且還存在一些 bug。例如在某些情況下,Code Intelligence 會無法正常工作。不過分享進行到這裡,我相信有的觀眾可能已經猜到了,由於 Lavender 是開源的,我們可以自己修復這些 bug。
實際上,關於Code Intelligence的bug原倉庫已經有一個PR。所以我們建立了一個 fork (個人專案分支) ,合併了對應分支的內容。此外,我們還進行了一些微小更新,比如根據 Bazel 專案層次結構,在 Visual Studio 中相應組織資料夾結構。經過修改後,我們解決了 Lavender 的問題。這是 Lavender 生成的 Visual Studio 專案。
對於Xcode 專案也有類似的工具,叫做 Tulsi。它的工作原理和 Lavender相似。在Tulsi生成的 Xcode 工程中,編譯仍舊是透過 Bazel 完成的。值得一提的是Tulsi最近被另一個更強大的工具rules_xcodeproj替換了,不過我們目前仍然在使用Tulsi。
我們在 Xcode 上遇到的唯一問題是對遠端編譯庫的除錯。這個我在講到遠端執行時,會詳細討論。
Bazel 在 Android Studio 中的工作方式略有不同。谷歌和 IntelliJ 合作維護一個開源的 IntelliJ Bazel 外掛。這個外掛允許我們在所有 IntelliJ 整合開發環境中匯入 Bazel 專案,包括 Android Studio。工作原理簡單直接。
我們也遇到了和Xcode類似的遠端編譯庫除錯問題,並用類似方式解決了。
遠端快取和執行
現在,我們已經可以在 Bazel 中構建引擎並使用整合環境對其進行除錯。最後一個要探討的問題是遠端快取和執行。
關於遠端快取和遠端執行,我覺得 Bazel 的開源策略是很聰明的。谷歌沒有直接開源一個Bazel遠端伺服器,我認為這是因為谷歌內部使用的Bazel伺服器使用了大量谷歌內部的基礎架構,因此很難直接開源。即使谷歌做到了,這個伺服器對於絕大部分Bazel使用者來說也太複雜了。
谷歌的策略是開源遠端 API。Bazel 使用此 API 與遠端伺服器通訊來實現遠端快取和執行。每個人都可以使用此 API 建立自己的遠端伺服器。
我個人認為這是一個非常成功的策略,目前已經有很多遠端伺服器實現了,並且很多都是開源的。當我們嘗試給專案新增遠端快取時,只用了很短的時間內就成功使用 bazel-remote 搭建了一個僅快取伺服器。
另一方面,其它的構建系統也都開始使用 Bazel 遠端 API,也就意味著這些構建系統都可以使用遠端 API 與任意這些遠端伺服器進行對話。
Bazel 的遠端 API 非常簡潔。API 協議基於 gRPC,文件也非常詳細。事實上我需要刪除大量註釋才能做成下面的截圖。
Bazel 遠端API 目前主要提供的服務包括:用於接受執行請求的執行服務(Execution),用於儲存構建操作結果的快取服務(action) 。請注意,它不儲存構建生成的實際輸出檔案(artifacts),只是儲存對它們的引用。
輸出檔案和輸入檔案一起儲存在內容可定址儲存(Content-addressable storage (CAS) )中。
每個檔案都透過摘要(digest)來引用。摘要由檔案的雜湊值和檔案大小(以位元組為單位)組成。
如果設定了遠端伺服器,在嘗試構建目標時,Bazel 客戶端(Client) 會先詢問操作快取服務(Action Cache Service) 是否已經有快取。如果有,Bazel 客戶端會根據快取中的檔案引用,嘗試從內容可定址儲存中下載快取的結果。如果快取丟失或者下載失敗,Bazel 客戶端會將所有輸入檔案上傳到內容可定址儲存中,然後請求遠端執行構建目標。
執行服務將從內容可定址儲存中獲取所有輸入檔案,執行構建命令,再將所有生成的結果上傳到內容可定址儲存,將操作結果上傳到操作快取服務服務,然後將結果返回給 Bazel 客戶端。
最後,Bazel 客戶端在內容可定址儲存下載所有輸出檔案。
遠端伺服器也可以選擇只實現 ActionCache 服務和 ContentAddressCache 服務,我們就會得到一個只提供快取功能的伺服器,這種情況下,由 Bazel 客戶端負責構建目標並上傳結果。
正如我前面提到的,我們在非常短的時間內就完成了快取伺服器的部署。我們用的是 bazel-remote,是用 Go 語言編寫的快取伺服器。
透過使用遠端快取,構建過程中的大部分時間可以被節省下來,尤其是爭對持續整合的自動化任務。例如我們的 clang-tidy 平均檢查時間從大約 40 分鐘縮短到 7 分鐘。但我們仍然希望透過遠端執行,進一步減少增量構建時間。
遠端執行的部署要更復雜一些。對於快取,我們只需要一個伺服器來提供構建期間生成的所有輸出結果,並不在乎這些請求執行在什麼樣的平臺上。
對於遠端執行,我們則必須需要關注這些操作所在的執行平臺。我們需要在不同的平臺上使用遠端工作器(remote workers)來執行這些操作(actions),這也就意味著,我們需要一個可以同時支援 MacOS、Windows 和 Linux 的遠端伺服器。
大多數 Bazel 使用者都來自網際網路行業,而 Linux 是目前網際網路開發環境中最常用的系統。因此,目前大多數遠端伺服器都支援 Linux。由於 MacOS 平臺也常常用於構建 IOS 應用程式,因此有的遠端伺服器也支援 MacOS 。但對於 Window 平臺,我們就沒有那麼幸運了。Bazel 在 Windows 上的應用相對不多,遠端執行則被使用得更少了。
我們嘗試了使用 Buildbarn,一個用 Go 語言編寫的開源遠端伺服器。
Buildbarn 的一個優點是,它可以支援多種型別的工作器。它在 Linux 上使用基於 FUSE(Filesystem in Userspace) 的工作器,在 MacOS 上則是基於 NFSv4(Network File System version 4)的工作器。兩者都使用的是虛擬檔案系統(VFS)。
基於VFS的工作器都有一個很大的優勢:只抓取編譯實際需要讀取的原始檔。這是什麼意思呢?假設我們有這樣一個數學庫,還有一個名為“render.cpp”的編譯單元,這個編譯單元對數學庫有依賴,但它只需要include matrix.h。當我們使用基於虛擬檔案系統的遠端工作器時,顧名思義,工作器會為該編譯動作生成一個虛擬檔案系統,實際上不會從內容可定址儲存中下載任何檔案。只有在編譯器需要真正開啟檔案時,VFS 才會從內容可定址儲存中( Content-addressable storage )獲取對應檔案。
這樣一來,遠端執行工作器可以大大減少設定編譯操作的所有輸入檔案所需的時間。如果沒有 VFS,執行編譯操作的工作器需要獲取所有可能被用作原始碼的檔案。這也就是Buildbarn 支援的第三種工作器:本地工作器(native worker)。但是當處理像 NeoX 這樣的大型工程時,每個編譯操作都需要花費大量時間來下載所有的輸入檔案,遠端伺服器方案就不太實際了。
不幸的是,Buildbarn 在 Windows 上並沒有基於VFS的工作器。Windows本身有一個很不錯的 VFS API 叫 ProjFS。微軟用其實現了GitVFS 。但由於 Windows 不是 Web 行業開發中最常用的構建環境,因此Buildbarn 在 Windows 上沒有相應的可用實現。
幸運的是,Buildbarn 是個開源工具。由於它支援多種工作器,它有一個非常簡潔明瞭的 API,用於工作器和任務排程器之間的互動。正如遠端 API 一樣,這個API 也是基於 gRPC 協議,並且有非常規範的文件。它只有 210 行 Protobuf 編碼、一個Service、一個遠端呼叫協議(RPC)、和四個資訊定義(message)。這意味著我們可以自己搭建一個自定義的工作器。
目前市面上存在支援 Windows 的商業化遠端構建服務,我們也相信它們可以提供很好的服務,但是到這個階段,我們更傾向於使用開源軟體,因為我們可以修改開源軟體,滿足我們的任何需求。因此我們決定構建自己的Windows工作器。
我們內部經常用 Python 程式設計,所以這個工作器也是用 Python 構建的。我們的第一版工作器並沒有實現 VFS,而是學習了另一個Bazel伺服器Buildfarm對輸入目錄進行快取,這樣就不用每次都重新下載整個輸入樹(tree)了。我們最後用 2840 行 Python 程式碼搭建了一個工作器,不包括註釋、空行、和測試。當然這個方案還不夠完美。畢竟在首次構建專案時,還是需要花費一些時間來獲取大量原始檔的。但是,利用這個工作器來加快構建對我們來說完全足夠了。
如果你們感興趣的話,可以看看這個倉庫:https://github.com/kkpattern/bb-remote-execution-py
三、關於開源工具
我們正在使用更多的開源工具來幫我們開發引擎。
例如,我們使用 clang-format 來自動格式化引擎程式碼;利用AddressSanitizer(ASan)來檢測記憶體訪問錯誤。這兩個工具都來自LLVM。我們的大部分的持續整合管線都是在Kubernetes 叢集執行的。我們也大量使用 Jenkins 進行持續整合。我們有很多內部服務都是用 FastAPI 構建的。
本次分享的重點,並不是為了介紹某個特定的開源軟體,因為不同專案的需求不同,需要的工具也不同。我們更想透過分享我們的專案經驗,向大家展示開源軟體對遊戲開發的幫助,並願意去嘗試使用開源軟體。
在我接觸這個專案之前,我們在和開源社群合作方面並沒有太多經驗。雖然我們使用了很多開源庫和開源工具,但我們之前並未更多的參與到開源社群之中。在這次經歷中,我們也學到了很多如何跟開源社群相互合作的經驗,所以我們想跟大家分享一下,希望可以幫助大家更好地融入開源社群。
勇於提問。常言道“沒有愚蠢的問題,只有愚蠢的答案。”需要他人為自己解答疑惑是再正常不過的事情。很多開源軟體都有專門面向新人的郵件列表或者slack頻道等,如果你對一個開源軟體有任何問題都可以放心的在這些地方進行提問。
問題沒有很快得到回覆是非常常見的,這不代表社群不歡迎你。很多人都是抽出自己工作之外的時間來建設開源社群,他們可能沒有足夠的時間及時的回答你的問題,這是非常正常的。如果你的一個問題沒有得到答案,請不要氣餒,同時在你有了新的問題時也不要猶豫。
不要錯過任何探索開源工具的機會,很多開源軟體可能無法完美的滿足你的需求,或者無法做到開箱即用。但開源意味著你可以隨時更新修改工具以滿足你的需求。因此,當一個工具無法滿足的你的需求時,不妨多看看他的原始碼和實現,或許你能為他新增新的功能。
最後,在修復了錯誤或實現新功能後,記得也要為社群做出回饋,事實上這麼做對你自身也是大有益處的。許多開源工具都在不斷更新,某些更新可能會破壞你所新增的新功能。如果你將該功能貢獻回開源社群,新增上一些單元測試(Unit test),就會大大減少該功能在未來的更新中被破壞的機率。無論如何,為開源社群貢獻資源總是一件好事。當你建立了基於某個專案的 fork,開發了不錯的新功能,請儘量嘗試將該功能回饋給社群。
和你的問題一樣,你提交的PR可能也無法被及時review以及合入。同樣不要氣餒。如果你實在無法將程式碼公開到開源社群,可以試著在專案內建立一個管線,定期(如每天)抓取最新的上游程式碼,並將它合併到你的內部 fork 裡,然後執行這個管線,看看是否能透過你的內部測試。這樣一來,如果上游程式碼的更新導致你的程式碼無法執行,就能立刻知道。你甚至可以使用“git bisect”之類的工具來找出具體是哪些程式碼的變更導致程式碼無法執行。然後你可以及時向上遊專案反映你的問題,這樣他們就能在下一個版本之前進行修復。這樣做有利於你的 fork 在未來的更新。
以上就是今天分享的內容。希望本次分享可以讓大家感受到開源工具的強大,並瞭解到它們對於工作流起到的關鍵作用。你可以透過修改開源工具,來滿足各種各樣的特殊需求。如果你已經在使用一些開源工具,記得要多在社群內互動,多問問題和建立 Pull Request 。在開源工具變得越來越強大的同時,讓你的專案也不斷最佳化。
在分享的最後,我想感謝整個《星戰前夜:無燼星河》團隊,感謝他們在整個旅程中的耐心、支援以及建議。
我還想感謝 NeoX 引擎團隊為我們提供的寶貴建議。
最後,我還要感謝開源社群。沒有這些了不起的開源社群,我們也無法完成這項艱鉅的任務。
相關閱讀:
【GDC2023乾貨分享】移動平臺上的軟光柵遮擋剔除方案
【GDC2023乾貨分享】創新的使用者獲取模式——直播引流探索
原文:https://mp.weixin.qq.com/s/L1oEZhCWeIUpIAwh7YHVLA
相關文章
- 六西格瑪可以幫助到軟體開發嗎?
- 【GDC2023乾貨分享】移動平臺上的軟光柵遮擋剔除方案
- 在Linux中,開源軟體和自由軟體的區別?Linux
- 何為開源,聊聊軟體開發中的那些開源協議協議
- 整理分享5款可能對大家有幫助的軟體
- 探討敏捷開發在軟體開發中的應用敏捷
- 在雲中利用開源軟體進行開發以提高創新能力
- 力軟敏捷開發框架幫您開發什麼軟體敏捷框架
- 【GDC2023乾貨分享】創新的使用者獲取模式——直播引流探索模式
- 乾貨分享:Air780E開發板如何使用?AI
- 關於直播平臺開發中流媒體傳輸,重點乾貨分享
- 開源軟體名稱中的故事
- 敏捷軟體開發的最佳資源敏捷
- 中國軟體“成也開源,敗也開源”?
- 軟體開發中的DevOpsdev
- 【開源】.net微服務開發引擎Anno開源啦微服務
- 開源專案管理軟體有哪些?分享7個實用開源專案管理軟體專案管理
- 11 個對開發有幫助的 JS 技巧JS
- 開源軟體在地圖資料處理中的應用地圖
- 開源 - Ideal庫 - Excel幫助類,TableHelper實現(二)IdeaExcel
- 開源 - Ideal庫 - Excel幫助類,TableHelper實現(三)IdeaExcel
- 開源 - Ideal庫 - Excel幫助類,設計思路(一)IdeaExcel
- 開源 - Ideal庫 - Excel幫助類,ExcelHelper實現(四)IdeaExcel
- 開源 - Ideal庫 - Excel幫助類,ExcelHelper實現(五)IdeaExcel
- 軟體整合測試乾貨分享,2022國內軟體測試公司排名
- 把低程式碼開發平臺推給開百貨店的朋友,對他有幫助嗎?
- 開源軟體映象站的使用:騰訊軟體源、阿里軟體源、浙大軟體源阿里
- 網店開設教程免費分享,無套路乾貨分享!
- 乾貨分享:Air780E軟體指南:字串處理AI字串
- 【乾貨分享】軟體Bug和缺陷有什麼區別?
- 【分享】具有“魔性”的通用軟體開發框架框架
- 遊點乾貨 | GUI是如何幫助遊戲進行敘事的?GUI遊戲
- 乾貨分享:開發超市小程式應用需要具備哪些功能
- 直播平臺開發乾貨分享——標準直播及快、慢直播的特性
- 軟體功能測試在軟體開發中的重要性。在哪裡做軟體測試?
- 乾貨分享 | C語言的聯合體C語言
- Google 正式開源 Jib ,幫助 Java 應用快速容器化GoJava
- 軟體開發:app軟體開發,pc端軟體開發,微商城/小程式開發APP