2017年8月20日 星期日

Unity告別UnityScript,未來將只支援C#

作者:Richard Fine 原文
潤稿:Kelvin Lo


UnityScript(Unity中客製的JavaScript),從Unity 1.0開始一直伴隨著我們至今。正所謂天下無不散之宴席,終於要向它告別。我們已經開始逐步棄用UnityScript,未來只會保留C#作為Unity程式語言。

目前只有3.6%的Unity專案使用UnityScript進行開發,代表維護UnityScript將會影響Unity對新程式語言功能的支援進度,所以我們決定將棄用UnityScript。


棄用UnityScript的原因

每次Unity決定放棄支援一些功能,都會先瞭解這些決定可能會為開發者帶來的不便。所以我們必須確保這些決定有其進行的理由,這對我們很重要。Unity腳本程式設計正在經歷重大升級,其中一些重要功能包括:
  • 核心執行版本升級,支援使用.NET 4.6與C# 6
  • JobSystem,支援編寫多執行緒程式,且避免互相競爭與鎖死
  • NativeArray類型,支援建立及使用大型陣列,它們擁有原生程式控制的存儲區域,以便於對記憶體分配擁有更多控制權,不必再擔心GC回收機制
  • 支援控制腳本編譯,以便自訂將哪些腳本整合進程式集

這些只是一小部分,我們還將繼續支援一些新功能,並計畫開啟一些腳本專案。除了這些專案之外,我們也正利用最為合適的語言結構,來開放更多引擎底層API。

目前UnityScript與C#在功能與效能不相上下,當然C#可以實現的內容UnityScript也能達到同樣的效果。但說到開發生態,顯然C#是贏家,不僅僅是因為C#教學與範例網路上到處都是,C#語言也有豐富的工具,例如Visual Studio提供的代碼重構與智慧提示。或許你會質疑Unity Script可以用就好了,也不需要那麼多花俏的工具。

技術永遠是不斷發展的,隨著我們升級執行核心支援最新的C#版本時,就會出現一些C#簡單實現但UnityScript無法輕易實現或者不支援的功能。比如說目前UnityScript就不支援為方法參數(method parameters)提供預設值,並且C#語言支援的功能越來越多,例如ref return等都會造成同樣問題。目前我們暫未在API中使用這些功能,但出於效能與API結構設計考慮,我們也希望趕快讓這些新功能上線。

或者我們也可以選擇花時間來彌補UnityScript所欠缺的功能,但時間成本昂貴,把時間花在維護UnityScript就代表無法進行其它工作,好比加新功能或修Bug等。這還不包括在執行核心中支援UnityScript需要的工作,以及文件製作更新工作等等。

所以我們換了個方向思考,如果棄用UnityScript,會有多少開發者被影響呢?我們統計結果如下:

  • 截止目前使用Unity 5.6的專案中,包含至少一個.js檔的專案占14.6%。這個比例看起來相當高,但進一步分析資料,看看各專案中.js檔案與總檔案數(.js檔案 + .cs檔案)占比,會有新發現。
  • 也就是說,85.4%的Unity專案完全使用C#語言,根本不包含任何UnityScript腳本。
  • 9.5%的Unity專案大量使用C#語言,其中也包含一些UnityScript腳本,但腳本數量占腳本總數不足10%。還有1.5%的Unity專案所包含的UnityScript腳本占專案腳本總數在10~20%之間。
  • 3.6%的Unity專案使用UnityScript語言的腳本占腳本總數20%以上。
  • 僅有0.8%的Unity專案完全使用UnityScript。

這些資料顯示,大多數的Unity開發者都不是UnityScript的重度使用者。甚至專案所包含的UnityScript也並非實際使用的腳本,可能只是Asset Store某個套件的範例程式,對實際專案並無影響。所以我們棄用UnityScript計畫的第一步,就是從Asset Store開發者著手,先移除所有發佈套件裡的UnityScript腳本。

對於那3.6%使用較多UnityScript的Unity專案,以及那0.8%完全使用UnityScript的專案,我們表達深切的歉意。我們明白這樣的決定會對你產生影響,我們正製作一些轉換措施來嘗試讓整個轉換過程更加流暢,也希望大家能夠理解並支持我們所做出的決定。

棄用計畫

當然我們會一步一步捨棄UnityScript,而不是一刀切斷。主要計畫步驟如下:

首先,從6月開始我們已經修訂了Asset Store的套件審核條款,拒絕接受程式有包含UnityScript代碼的套件。所有新送審的套件都必須使用C#程式(在執行此項規定前我們已與開發者有過許多討論與溝通)。很快我們將會對Asset Store現有的套件進行檢查,如果有UnityScript則會通知發行商將程式碼轉為C#。如果一段時間後程式沒轉換為C#,套件將會從Asset Store下架。

再來,可能你已經注意到了,Unity 2017.2測試版的Create Assets選單下已經找不到Javascript(即UnityScript)。目前我們只移除了功能表上的建立功能,Unity編輯器仍然支援UnityScript,您仍然可以從編輯器之外創建UnityScript檔(例如MonoDevelop)。這麼做的原因是想確保新手開發者不會選用UnityScript,以免浪費學習成本。

另外,我們正在開發UnityScript自動轉換為C#的工具。目前該工具已有些成果,但還沒到我們滿意的地步。我們也還沒決定是否將工具整合到Unity編輯器或單獨開源提供。無論以何種方式,這個工具都會在今年年底Unity 2017.2正式發佈時提供大家使用。後續也會單獨介紹這個工具,請大家保持關注。

最後,我們也會繼續分析資料,我們希望能看到Unity專案都可以儘快切換至C#,尤其是那些UnityScript腳本數量少於10%的那些專案,因為需要移植的程式相對較少。但如果分析結果轉換未達預期,我們會暫停計畫並調查原因。在徹底棄用UnityScript之前,我們將確保不遺漏任何重要資訊。

一旦確認UnityScript使用率到一個低點之後,我們就會剝離UnityScript編譯器,不再將.js檔識別為腳本。也會從文檔中移除UnityScript程式範例,腳本更新器也將不再支援UnityScript。

如果有單獨需求,開發者仍可從Unity的GitHub中下載UnityScript編譯器,我們不會接受任何推送請求,但您可以建立自己的分支實現你的需求。


關於Boo

在歷史紀錄上Unity曾在2014年宣佈放棄支援Boo語言。但Boo編譯器仍存在現在的編輯器中,只因為UnityScript會用到了Boo的執行庫,並且UnityScript編譯器本身就是用Boo語言編寫的。所以雖然我們沒提但你仍可在Unity專案中使用.boo文件。

但是剝離UnityScript支援之後,就代表Boo編譯器也會被移除。目前所有使用Unity 5.6的專案中僅有0.2%的專案包含了.boo檔,僅有0.006%的專案擁有3個以上.boo檔案。


結論

希望本篇文章有清楚解釋了棄用UnityScript將會為大家帶來的影響,我們會依照流程:通知大家我們的計畫->推動Asset Store套件與編輯器UI更新,最後依照實際使用率的分析資料來決定是否執行。

放棄某個功能看起來像是退步,但這也是提高Unity開發效率的必經之路。我們希望集中精力為大家儘快修復現有的問題並製作新功能,也希望大家能夠理解並支持我們的決定。

2017年8月7日 星期一

最佳實踐 - Unity碰撞效能優化

作者:William Armstrong 原文
潤稿:Kelvin Lo

Unity有一個名為Spotlight的團隊,有一群優秀的Unity開發人員一起研究並試著不斷的打破Unity的極限。針對各種複雜圖形效能和設計問題,我們不斷嘗試各種新的解決方案。

這個系列的文章會探討我們在和客戶合作時遇到的一些常見的問題。這些都是我們的合作團隊辛苦得出的經驗和教訓,我們很榮幸能夠和大家分享這些智慧。

這些問題很多只會出現在發佈到主機遊戲、手機遊戲、或者處理大量遊戲內容時才會出現。如果能在開發早期就將這些問題考慮進去,那麼開發過程就會更輕鬆,而遊戲也會做得更好。

較大的問題

有時候我們追蹤某個物理效能問題時,會歸咎到某一個問題的物件資源或設定上。常常查看Profiler並和前次執行結果比對,是發現這些問題的最佳方式。如果能儘早發現效能的減退,就可以透過查看最近所做的變更找到問題所在。

雖然運算單一簡單的物理
節(physics joint)速度很快,但背後的運算卻很複雜。本質上一個關節就是一套由剛體的位置、速度、加速度、旋轉等資訊組成的方程式。如果建立了一個帶有許多不同剛體的物件,而這些剛體又各自包含了許多會互相碰撞的關節。為了不讓這些關節不會互相穿透,要滿足這些關節的碰撞運算代價會非常大。

所以設計這種複雜關節時,要慎重考慮所需關節數量,碰撞類型以及必要的剛體數量。你可以用圖層(Layer)來過濾不必要的碰撞,也要謹慎評估哪些關節要打開"Allow Collision"來計算碰撞。透過限制關節的活動範圍可以減少碰撞檢測的量。或調整關節使它和其他關節不會發生碰撞。透過將關節和剛體作為插值方法的控制點來減少它們的使用數量。

使用Profiler可以看到某個特定時間啟動的剛體總數。觀察Rigidbody count的值
,尤其是當剛體互相接近時,會對效能產生很大的影響。在執行時放置或產生物件時,這個數字很容易會膨脹到超出預期。這就好比一罐汽水罐的碰撞很容易做,但如果罐子做成Prefab疊成超市裡的飲料塔,碰撞計算就很容易出大問題。


採用MeshCollider要非常小心,通常為了方便,開發者很容易會拿模型網格直接作為碰撞網格,但這可能會引起嚴重的效能下降,而且還不易察覺。PhsyX你給他甚麼他就算甚麼,所以如果一個小物體身上有高模或不合身的碰撞體,這時可能還好,但是如果將MeshCollider放大到容易被RayCast偵測運算的環境中,你會發現效能會驟然下降。如果非要用MeshCollider,建議是對於所有可能會有碰撞運算的物體,另外製作一組低模的碰撞網格。如果網格有問題又沒時間製作自訂網格,可以將MeshCollider設定裡的Convex打勾,並調整SkinWidth來取得一個比較適合的低面碰撞網格。

擁有多個重疊碰撞器的影子戰術NPC

較小的問題

通常開發者都會明智的避開不做大或慢的事情。但很多常見的情況是,專案所做的一系列理性決策,每個都一點一滴無形的影響了PhysX的效能。尤其是製作大規模遊戲時很容易出現這種狀況。例如你用5個方塊做關卡AI測試時表現很OK。但相同的AI放到實際的關卡時,大量的Physics.Update運算造成效能不好,而你卻不知道問題在哪? 


"明明測試都很OK的,為甚麼上正式版就壞了?" -- by 工程師


你有做足夠的測試了嗎?
可以從ProjectSettings->Physics裡找到碰撞圖層過濾(Layer Collision Matrix)來檢查是否存在不必要的碰撞規劃。透過打勾/取消打勾那些方塊,可以控制碰撞體之間的碰撞行為。這個功能會在碰撞發生前先進行計算,進而避開不必要的碰撞計算。許多遊戲使用”Is Trigger”為true的大型碰撞器來檢測角色或其他物件,這通常被稱為觸發器。通常這些觸發器會設定和預設圖層或所有物件發生碰撞計算。透過將角色放到特定層,並將這些觸發器放到某個只會與角色層發生碰撞計算的層,可以避免大體積碰撞器與複雜的世界網格或地形網格發生碰撞計算。

觸發器(Trigger)是否是使所有物件慢下來的原因?
當碰撞器屬性的“Is Trigger”設為true,它依然是一個需要進行碰撞計算的碰撞器。因此移動一個觸發器和移動一個碰撞器所產生的消耗都是一樣的,會需要做很多工作來發送碰撞和重疊事件。如果要在每幀都移動觸發器,確保它只對必須的物件進行碰撞計算。盡可能將觸發器做小,並將相似或重疊的觸發器分組。

我們常見到專案在NPC上使用多個大型觸發器用來檢查是否有互動。NPC的每種類型的遊戲物件都會有自己的碰撞器,以及一堆放在OnCollision中的程式,用來找到正確的互動物件。通常將多個觸發器合併一起會比較快,然後在OnCollision函式中用Tag或Layer或距離來進行過濾。很多情況下可以透過完全繞過碰撞計算來獲得更好的效能。而不必每幀都對球體與世界的碰撞進行計算,而是將所有希望存取的物件註冊到一個共用管理器,然後讓NPC對所有已註冊物件做簡單的距離計算。如果有一小堆需要檢測的潛在目標。這種方法要比對世界中所有的碰撞做檢測效能要高的多。

物件在Hierarchy裡的結構會導致PhysX做了額外的工作嗎?
在《ReCore》這個遊戲裡,我們發現場景中圓形旋轉平臺上的每一個平台都擁有自己的剛體。這導致了每個平台都會與其他平台發生碰撞檢測。將所有的平臺歸於同一父物件之下,將剛體賦予父物件,讓父物件進行旋轉,在Physics.Update中節省了大量的時間。
注意:將一個共用剛體下的碰撞器合併會增加Raycast對它或形狀投射檢測的開銷。

從另一個方面來說,如果一個共用的剛體父物件下已經有幾個碰撞器,你需要非常小心,不要讓它們做相對於父物件的移動。因為任何時候剛體改變形狀,會造成中心的質量和慣性張量(Inertial Tensor)
都必須重新計算,這需要大量的時間。當多個剛體連接到動畫角色的四肢時,常常會出現這種問題。可以通過設定自己的質心來關閉它。只要遊戲物件的形狀不要變化太大就沒問題。

這堆小小的系統問題累積起來是很可怕的,所以在開發中要時刻牢記,並在一開始就做好計畫規避這些問題。如果突然發現Physics時間出現一個巨大峰值,那就分析下當下的修改有哪些並思考對應方式。

最後感謝Mimimi Productions和Armature Studio 讓我們用他們的遊戲作為例子。我們還會為大家分享更多最佳實踐系列文章。

著作人