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的編譯環節有所了解。切換過來後,感覺是一種新的體驗,有興趣的可以試試。