最佳實踐系列文章將探討咱們在與客戶合做時遇到的一些常見的問題。這些都是咱們的合做團隊辛苦得出的經驗和教訓,咱們很自豪可以和你們分享他們的智慧。咱們已經爲你們分享過如下內容:
這其中的不少問題只有在真正製做主機遊戲、手機遊戲、或者處理巨量遊戲內容時纔會出現。若是能在開發早期就將這些問題考慮進去,那麼開發過程就會更輕鬆,而遊戲也會更炫酷。
儘管擺放對象是在Unity中最爲常見的任務之一,咱們仍是看到很多團隊爲了如何能更好地完成這項工做而苦惱。要把對象排列完美,不讓角色跑出世界,不讓尋路出現障礙,並讓各個物理對象可以放置在理想的位置,這的確不是件輕鬆的事。
Unity提供了一些工具來方便處理這個問題,這些工具各自扮演着不一樣的角色。尤爲是在咱們想要製做大量重複使用的內容,或是製做一張放置了大量動態物理對象的場景,瞭解這些工具的使用方法則尤其重要。
Unity中提供了變換、旋轉和縮放工具,在Unity 2017.3中還加入了能同時擁有這三個功能的通用工具。這些小工具能在3D環境下修改遊戲對象的變換屬性。除此以外,還有2D環境下處理對象的矩形變換工具(Rect Transform)。
全部這些輔助工具的運做方式都與其所處的工做模式直接相關。若是將模式設爲中心(Center),輔助工具將使用根據對象邊界計算得出的對象中心點。這個計算結果是個近似值,因此在處理複雜對象時或許會感受該中心點偏移了真正的中心點。
若是將輔助工具模式設爲軸心(Pivot),輔助工具將使用對象自身引用框的原點(0,0,0)。這個軸心點是由建立該資源的做者在製做時設定的。一般須要將對象按照彼此相對的位置進行擺放時,咱們推薦使用軸心模式,並且在製做資源時要留意軸心點的位置。
第二個切換按鈕,全局/局部(Global / Local)切換按鈕,肯定了操做對象時所處的空間。設爲全局(Global)意味着輔助工具將會對齊世界(x,y,z)座標軸,而設爲局部(Local)將會對齊對象自身的(x,y,z)座標軸。一般咱們須要在局部空間下處理對象。但若是咱們想要讓多個對象互相對齊,則會切換到全局座標系,並使用網格對齊工具來排列對象。
在Unity中,最爲直接的定位工具是按下CTRL鍵(Windows系統)或是Command鍵(OSX系統)來吸附網格。在咱們經過座標軸手柄(也就是分別爲藍、紅、綠的座標軸輔助工具)來移動對象時,按下吸附鍵會讓對象根據當前網格大小計算的增量而移動,這個增量能夠自行修改。
自動吸附設置(Snap Settings)菜單能爲網格大小設置每一個軸的數值,讓咱們控制旋轉角度和縮放大小的吸附,還能將所選對象的當前變換屬性近似取其最近的網格線。
吸附全部座標軸(Snap All Axes)按鈕在排列任意對象時十分有用。咱們能夠選擇一組對象,將它們在座標軸上吸附到網格,而後從新定位,這樣就能把他們排列好了。效果以下圖所示:
這個方法在處理規則幾何體、大型關卡布局和設置碰撞塊時十分管用。若是咱們須要處理更爲複雜的放置狀況,則須要下面這些工具。
在使用變換工具時按住V,變換輔助工具會切換爲頂點吸附模式,顯示爲一個小方框。而後點擊所選對象網格上的一個頂點,將其拖到光標下的任意頂點上。在處理任意網格的對齊時,尤爲是要處理那些複雜的或是不存在碰撞的網格對齊時,這個方法很實用。
在上圖中,不論是電線仍是檯燈都沒有緊湊的碰撞網格,並且它們也都不是規則的立方體,因此它們不能用網格吸附或碰撞吸附處理。
若是須要這樣放置多個遊戲對象,咱們也能夠將頂點吸附模式保持在打開狀態。按下Ctrl+Shift+V打開,按下Ctrl+V關閉。這樣便不須要一直按住V就能頂點吸附多個對象了。
按下CTRL + SHIFT能夠啓用碰撞吸附。啓用後,必須拖拽變換輔助工具中心的黃色小方框,而不是使用座標軸控制工具。這樣會讓所選對象與光標下對象的碰撞體相對齊。
用頂點吸附或網格吸附來對齊網格在處理靜態幾何體時十分出色,但若是要處理動態對象,好比說互相穿透並在觸碰後發射到空中的對象時,應該吸附到碰撞體而不是可見網格。
若是有大量動態物理對象,或是十分複雜的關節系統,利用碰撞吸附來放置對象也許就不太合適。幸運的是,如今Unity編輯器中能夠運行PhysX。它能模擬幾秒的遊戲時間,完成物理效果,而後保存完成時對象的位置信息。
下方是示例代碼,代碼中不只有大量註釋,還包含了調試信息繪製和一個菜單項。只要將其放入項目中的任何一個Editor文件夾裏,便能看到一個新的GameMenu->Settle Physics菜單項。
using UnityEngine; using UnityEditor; [InitializeOnLoad] class PhysicsSettler { static bool registered = false; static bool active = false; static Rigidbody[] workList; static bool cachedAutoSimulation; const float timeToSettle = 10f; static float activeTime = 0f; static PhysicsSettler() { if (!registered) { EditorApplication.update += Update; SceneView.onSceneGUIDelegate += OnSceneGUI; registered = true; } } [MenuItem("GameMenu/Settle Physics")] static void Activate() { if( !active ) { active = true; workList = Object.FindObjectsOfType<Rigidbody>(); cachedAutoSimulation = Physics.autoSimulation; activeTime = 0f; foreach( Rigidbody body in workList ) { body.WakeUp(); } } } [MenuItem("GameMenu/Settle Physics", true)] static bool checkMenu() { return !active; } static void Update() { if( active ) { activeTime += Time.deltaTime; Physics.autoSimulation = false; bool allSleeping = true; foreach( Rigidbody body in workList ) { if( body != null ) { allSleeping &= body.IsSleeping(); } } if( allSleeping || activeTime >= timeToSettle) { Physics.autoSimulation = cachedAutoSimulation; active = false; } else { Physics.Simulate(Time.deltaTime); } } } static void OnSceneGUI(SceneView sceneView) { if( active ) { Handles.BeginGUI(); Color cacheColor = GUI.color; GUI.color = Color.red; GUILayout.Label("Simulating Physics.", GUI.skin.box, GUILayout.Width(200)); GUILayout.Label(string.Format("Time Remaining: {0:F2}",(timeToSettle - activeTime)), GUI.skin.box, GUILayout.Width(200)); Handles.EndGUI(); foreach( Rigidbody body in workList ) { if( body != null ) { bool isSleeping = body.IsSleeping(); if( !isSleeping ) { GUI.color = Color.green; Handles.Label(body.transform.position, "SIMULATING"); } } } GUI.color = cacheColor; } } }
當了解了什麼時候和爲什麼使用網格、頂點吸附、碰撞吸附時,咱們將在關卡設計工做流程得到更好的開發體驗。推薦開發者們多多嘗試這些工具,例如:對比軸心(Pivot)和中心(Center)模式的區別,或是比較全局(Global)和局部(Local)模式的不一樣,這樣就能夠知道什麼模式更爲適合。花費在佈置虛擬對象的時間越少,那麼在開發時獲得的樂趣就越多。