Unity工程無代碼化

  • 2019 年 10 月 3 日
  • 筆記

目的

Unity默認是將代碼放入工程,這樣容易帶來一些問題。
1. 代碼和資源混合,職能之間容易互相誤改。
2. 當代碼量膨脹到一定程度後,代碼的編譯時間長到無法忍受。新版的unity支持通過asmdef來將代碼分成多個dll工程,有所緩解。

所以,我們可以將代碼全部挪到Unity工程之外,將代碼編譯成dll,然後把dll以managed plugin的方式放入unity工程。

 

實現

那麼,我們怎麼組織代碼工程呢。先看下unity的vs tool自動生成的工程格式。

 

 

Assembly-Csharp: Scripts下的代碼

Assembly-Csharp-Editor: Editor下的代碼

Assembly-Csharp-firstpass: PluginsScripts下的代碼 (一般還有個Csharp-firstpass-editor的工程,此工程下為Plugins/Editor的代碼)

Unity.2D.Spritexx:這個是筆者導入的Sprite2D的package,由於此package為unity內置,實際存放目錄在Unity安裝目錄的 Data/Resources/PackageManager/Builtin下。非內置的在Library/PackagesCache目錄下。

 

查看工程settings

首先需要打開查看權限,visual studio-option-tools for unity-general,將Access to project properties改成True。

 

 

  

 

具體的compilation symbols很長,這裡就不貼出來了。

此時,我們直接將此工程拷貝到項目外面,然後批量修改csproj里的文件路徑,右鍵build,將dll放入原工程,觸發unity編譯後,會出現很多重複定義的報錯,沒關係,這是因為dll和工程內的代碼重複了。

直接刪嗎?不急,先備份下工程。

刪除代碼,報錯消失。點play,所有功能消失,一大堆script missing的warning。

原有腳本的meta都被刪除了,難怪。沒辦法,用文本全局替換下guid。

原始腳本的meta 類似這樣

 

 

而dll內的腳本meta 類似這樣

 

 

其中fileID為dll的guid,而guid為dll內class的id(class名字不變的情況下,此ID不會變)

批量替換後,點play,一切恢復正常。(此批量替換過程,理論上完全可以自動化)

至此,工作是否已經完成?

沒有。

 

這時候打包,會提示dll reference了 UnityEditor.dll,怎麼辦呢? 查看project 的編譯宏,果然有各種UNITY_EDITOR的宏。

看來需要在打包的時候,單獨重新編譯dll。

好在visual studio支持命令行打包dll,即msbuild。本地編譯runtime的dll,命令行如下

Msbuild xx.csproj /t:Rebuild /p:DefineConstants=a;b;c;…

此處的defienConstants即為編譯宏,覆蓋了csproj里定義的define。依次將csharp和csharp-plugin編譯。(Editor dll暫時忽略)

 

再次打包,成功。

 

但是此時 點擊編輯器的play,你會發現有些功能發生異常(取決於代碼),有些定義找不到了。原因是Editor的dll和Play的dll不兼容了(編譯宏不一樣)。所以為了編輯器運行正常,又得編譯一遍play的dll。

 

此過程到此,感覺流程已經走通了。然而在發佈到項目組正式開發流程之前,還有一些問題需要解決。

 

問題

  • 代碼調試問題

vs調試 managed dll時,需要mdb文件。所以我們在編譯dll時,需要手動生成mdb。可以從google上下載一個pdb2mdb,在post build時,自動生成mdb,並將mdb和dll拷貝到unity工程。(隔壁項目組說將pdb和dll放入工程,unity會自動生成mdb,但嘗試了n次均失敗,遂放棄unity自帶的mdb功能)。

 

  • 編譯宏問題

unity編譯代碼的宏,來自於幾個地方:

a. Build setting defines

b. asmdef內的定義

c. unity內部的define(根據buildplayer傳入的platform和buildoption來決定)

如果僅靠visual tools生成的工程里的define,顯然不太實際。vs tools本身不負責編譯dll,工程里的define僅僅為了方便代碼編輯和代碼自動補全。這部分可以查看 vs tools的源碼(已經做成package了),vs tools是根據unity編譯出的dll,去自動更新vs工程。而無代碼化後的流程 其實是反過來了。

不過,通過查看 visual studio code editor源碼,找到unity一個內部的接口

 

通過此接口,可以獲取各個target和option的編譯宏

 

  • 引用依賴問題

Vs tools是將所有可能依賴的dll全部加到reference列表中,多的reference倒是無所謂,最終編譯都只會包含真正需要引用的dll。但是ref dll的路徑 需要處理下,默認為全路徑,但每個人機器上unity安裝目錄都不一樣,所以需要通用化。有兩個方法:

a. 將dll拷貝到工程同級目錄,將ref hintpath改成相對路徑(不過,在unity升級後,此目錄也需要升級,不適合unity需要頻繁升級的項目)

b. 添加環境變量UNITY_PATH,修改csproj,示例如下

 

csproj的Condition可以很好的解決不同環境下的一致性問題

 

  • Include scripts問題

由於涉及到多人協作,我們的csproj沒有採取正則include,而是顯式的include了每個代碼文件,這樣產生了兩個問題

a. 所有人都需要提交csproj,而csproj默認是將新加的代碼添加至末尾,所以在多人同時新加文件時,很容易產生衝突,而且不能自動resolve,需要手動合併。

b. 若引入了第三方的package,升級變得繁瑣,要逐個檢查是否需要include

 

為了解決這個問題,綜合各個方面,決定使用終極方案,即根據代碼文件/asmdef以及自定義的一些規範,自動生成csproj,這個過程跟vs tools類似。實現後,開發不再需要提交csproj文件,並且會自動檢測代碼文件的新增或刪除,並自動修改,這樣你的visual studio會自動提示reload(是不是跟原始的工作流程很像了?)。這個自動生成的過程,可以參考visual studio editor package代碼,此處就不再做詳細展開。

 

  • Burst compile問題

Unity2019提供了burst功能,這個burst過程是在打包階段發生的,它會自動掃描unity自動生成的dll(即Csharpt-xxx系列)內的代碼,然後進行burst。但是此時我們的代碼都在plugins下的dll里,所以掃描不到。此時需要修改burst package相關的代碼,具體如何修改就不貼了,很簡單。

 

總結

踩了不少坑,也對unity的編譯環節有所了解。切換過來後,感覺是一種新的體驗,有興趣的可以試試。