unity最佳實踐的50技巧

這是轉的別人的譯文http://www.cnblogs.com/zhaoqingqing/p/5677514.html

關於這些技巧

這些技巧不可能適用於每個項目。
  • 這些是基於我的一些項目經驗,項目團隊的規模從3人到20人不等;
  • 框架結構的可重用性、清晰程度是有代價的——團隊的規模和項目的規模決定你要在這個上面付出多少;
  • 很多技巧是品味的問題(這裏所列的所有技巧,可能有同樣好的技術替代方案);
  • 一些技巧可能是對傳統的Unity開發的一個衝擊。例如,使用prefab替代對象實例並不是一個傳統的Unity風格,並且這樣做的代價還挺高的(需要很多的preffab)。也許這些看起來有些瘋狂,但是在我看來是值得的。

流程

1、避免Assets分支

所有的Asset都應該只有一個唯一的版本。如果你真的需要一個分支版本的Prefab、Scene或是Mesh,那你要制定一個非常清晰的流程,來確定哪個是正確的版本。錯誤的分支應該起一個特別的名字,例如雙下劃線前綴:__MainScene_Backup。Prefab版本分支需要一個特別的流程來保證安全(詳見Prefabs一節)。

2、如果你在使用版本控制的話,每個團隊成員都應該保有一個項目的Second Copy用來測試

修改之後,Second Copy和Clean Copy都應該被更新和測試。大家都不要修改自己的Clean Copy。這對於測試Asset丟失特別有用。

3、考慮使用外部的關卡編輯工具

Unity不是一個完美的關卡編輯器。例如,我們使用TuDee來創建3D Tile-Based的遊戲,這使我們可以獲得對Tile友好的工具的益處(網格約束,90度倍數的旋轉,2D視圖,快速Tile選擇等)。從一個XML文件來實例化Prefab也很簡單。詳見Guerrilla Tool Development

4、考慮把關卡保存爲XML,而非scene

這是一種很奇妙的技術:
  • 它可以讓你不必每個場景都設置一遍;
  • 他可以加載的更快(如果大多數對象都是在場景之間共享的)。
  • 它讓場景的版本合併變的簡單(就算是Unity的新的文本格式的Scene,也由於數據太多,而讓版本合併變的不切實際)。
  • 它可以使得在關卡之間保持數據更簡便。
你仍就可以使用Unity作爲關卡編輯器(儘管你用不着了)。你需要寫一些你的數據的序列化和反序列化的代碼,並實現在編輯器和遊戲運行時加載關卡、在編輯器中保存關卡。你可能需要模仿Unity的ID系統來維護對象之間的引用關係。

5、考慮編寫通用的自定義Inspector代碼

實現自定義的Inspector是很直截了當的,但是Unity的系統有很多的缺點:
  • 它不支持從繼承中獲益;
  • 它不允許定義字段級別的Inspector組件,而只能是class類型級別。舉個例子,如果沒有遊戲對象都有一個ScomeCoolType字段,而你想在Inspector中使用不同的渲染,那麼你必須爲你的所有class寫Inspector代碼。
你可以通過從根本上重新實現Inspector系統來處理這些問題。通過一些反射機制的小技巧,他並不像看上去那麼看,文章底部(日後另作翻譯)將提供更多的實現細節。
                                                                                                                                         

場景組織

6、使用命名的空Game Object來做場景目錄

仔細的組織場景,就可以方便的找到任何對象。

7、把控制對象和場景目錄(空Game Objec)放在原點(0,0,0)

如果位置對於這個對象不重要,那麼就把他放到原點。這樣你就不會遇到處理Local Space和World Space的麻煩,代碼也會更簡潔。

8、儘量減少使用GUI組件的offset

通常應該由控件的Layout父對象來控制Offset;它們不應該依賴它們的爺爺節點的位置。位移不應該互相抵消來達到正確顯示的目的。做基本上要防止了下列情況的發生:
父容器被放到了(100,-50),而字節點應該在(10,10),所以把他放到(90,60)[父節點的相對位置]。
這種錯誤通常放生在容器不可見時。

9、把世界的地面放在Y=0

這樣可以更方便的把對象放到地面上,並且在遊戲邏輯中,可以把世界作爲2D空間來處理(如果合適的話),例如AI和物理模擬。

10、使遊戲可以從每個Scene啓動

這將大大的降低測試的時間。爲了達到所有場景可運行,你需要做兩件事:
首先,如果需要前面場景運行產生的一些數據,那麼要模擬出它們。
其次,生成在場景切換時必要保存的對象,可以是這樣:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. myObject = FindMyObjectInScene();  
  2.    
  3. if (myObjet == null)  
  4. {  
  5.    myObject = SpawnMyObject();  
  6. }  


                                                                                                                                                                   

美術

11、把角色和地面物體的中心點(Pivot)放在底部,不要放在中間

這可以使你方便的把角色或者其他對象精確的放到地板上。如果合適的話,它也可能使得遊戲邏輯、AI、甚至是物理使用2D邏輯來表現3D。

12、統一所有的模型的面朝向(Z軸正向或者反向)

對於所有具有面朝向的對象(例如角色)都應該遵守這一條。在統一面朝向的前提下,很多算法可以簡化。

13、在開始就把Scale搞正確

請美術把所有導入的縮放係數設置爲1,並且把他們的Transform的Scale設置爲1,1,1。可以使用一個參考對象(一個Unity的Cube)來做縮放比較。爲你的遊戲選擇一個世界的單位係數,然後堅持使用它。

14、爲GUI組件或者手動創建的粒子製作一個兩個面的平面模型

設置這個平面面朝向Z軸正向,可能簡化Billboard和GUI創建。

15、製作並使用測試資源

  • 爲SkyBox創建帶文字的方形貼圖;
  • 一個網格(Grid);
  • 爲Shader測試使用各種顏色的平面:白色,黑色,50%灰度,紅,綠,藍,紫,黃,青;
  • 爲Shader測試使用漸進色:黑到白,紅到綠,紅到藍,綠到藍;
  • 黑白格子;
  • 平滑的或者粗糙的法線貼圖;
  • 一套用來快速搭建場景的燈光(使用Prefa);

                                                                                                                                                                     

Prefabs

16、所有東西都使用Prefab

只有場景中的“目錄”對象不使用Prefab。甚至是那些只使用一次的唯一對象也應該使用Prefab。這樣可以在不動用場景的情況下,輕鬆修改他們。(一個額外的好處是,當你使用EZGUI時,這可以用來創建穩定的Sprite Atlases)

17、對於特例使用單獨的Prefab,而不要使用特殊的實例對象

如果你有兩種敵人的類型,並且只是屬性有區別,那麼爲不同的屬性分別創建Prefab,然後鏈接他們。這可以:
  • 在同一個地方修改所有類型
  • 在不動用場景的情況下進行修改
如果你有很多敵人的類型,那麼也不要在編輯器中使用特殊的實例。一種可選的方案是程序化處理它們,或者爲所有敵人使用一個核心的文件/Prefab。使用一個下拉列表來創建不同的敵人,或者根據敵人的位置、玩家的進度來計算。

18、在Prefab之間鏈接,而不要鏈接實例對象

當Prefab放置到場景中時,它們的鏈接關係是被維護的,而實例的鏈接關係不被維護。儘可能的使用Prefab之間的鏈接可以減少場景創建的操作,並且減少場景的修改。

19、如果可能,自動在實例對象之間產生鏈接關係

如果你確實需要在實例之間鏈接,那麼應該在程序代碼中去創建。例如,Player對象在Start時需要把自己註冊到GameManager,或者GameManager可以在Start時去查找Player對象。
對於需要添加腳本的Prefab,不要用Mesh作爲根節點。當你需要從Mesh創建一個Prefab時,首先創建一個空的GameObject作爲父對象,並用來做根節點。把腳本放到根節點上,而不要放到Mesh節點上。使用這種方法,當你替換Mesh時,就不會丟失所有你在Inspector中設置的值了。
使用互相鏈接的Prefab來實現Prefab嵌套。Unity並不支持Prefab的嵌套,在團隊合作中第三方的實現方案可能是危險的,因爲嵌套的Prefab之間的關係是不明確的。

20、使用安全的流程來處理Prefab分支

我們用一個名爲Player的Prefab來講解這個過程。
用下面這個流程來修改Player:
  1. 複製Player Prefab;
  2. 把複製出來的Prefab重命名爲__Player_Backup;
  3. 修改Player Prefab;
  4. 測試一切工作正常,刪除__Player_Backup;
不要把新複製的命名爲Player_New,然後修改它。
有些情況可能更復雜一些。例如,有些修改可能涉及到兩個人,上述過程有可能使得場景無法工作,而所有人必須停下來等他們修改完畢。如果修改能夠很快完成,那麼還用上面這個流程就可以。如果修改需要花很長時間,則可以使用下面的流程:
  • 第一個人:
    1. 複製Player Prefab;
    2. 把它重命名爲__Player_WithNewFeature或者__Player_ForPerson2;
    3. 在複製的對象上做修改,然後提交給第二個人;
  • 第二個人:
    1. 在新的Prefab上做修改;
    2. 複製Player Prefab,並命名爲__Player_Backup;
    3. 把__Player_WithNewFeature拖放到場景中,創建它的實例;
    4. 把這個實例拖放到原始的Player Prefab中;
    5. 如果一切工作正常,則可使刪除__Player_Backup和__Player_WithNewFeature;

                                                                                                                                                                       

擴展和MonoBehaviourBase

21、擴展一個自己的Mono Behaviour基類,然後自己的所有組件都從它派生

這可以使你方便的實現一些通用函數,例如類型安全的Invoke,或者是一些更復雜的調用(例如random等等)。

22、爲Invoke, StartCoroutine and Instantiate 定義安全調用方法

定義一個委託任務(delegate Task),用它來定義需要調用的方法,而不要使用字符串屬性方法名稱,例如:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void Invoke(Task task, float time)  
  2. {  
  3.    Invoke(task.Method.Name, time);  
  4. }  

23、爲共享接口的組件擴展

有些時候把獲得組件、查找對象實現在一個組件的接口中會很方便。
下面這種實現方案使用了typeof,而不是泛型版本的函數。泛型函數無法在接口上工作,而typeof可以。下面這種方法把泛型方法整潔的包裝起來。
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. //Defined in the common base class for all mono behaviours  
  2. public I GetInterfaceComponent<I>() where I : class  
  3. {  
  4.    return GetComponent(typeof(I)) as I;  
  5. }  
  6.    
  7. public static List<I> FindObjectsOfInterface<I>() where I : class  
  8. {  
  9.    MonoBehaviour[] monoBehaviours = FindObjectsOfType<MonoBehaviour>();  
  10.    List<I> list = new List<I>();  
  11.    
  12.    foreach(MonoBehaviour behaviour in monoBehaviours)  
  13.    {  
  14.       I component = behaviour.GetComponent(typeof(I)) as I;  
  15.    
  16.       if(component != null)  
  17.       {  
  18.          list.Add(component);  
  19.       }  
  20.    }  
  21.    
  22.    return list;  
  23. }  

24、使用擴展來讓代碼書寫更便捷

例如:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public static class CSTransform   
  2. {  
  3.    public static void SetX(this Transform transform, float x)  
  4.    {  
  5.       Vector3 newPosition =   
  6.          new Vector3(x, transform.position.y, transform.position.z);  
  7.    
  8.       transform.position = newPosition;  
  9.    }  
  10.    ...  
  11. }   

25、使用防禦性的GetComponent()

有些時候強制性組件依賴(通過RequiredComponent)會讓人蛋疼。例如,很難在Inspector中修改組件(即使他們有同樣的基類)。下面是一種替代方案,當一個必要的組件沒有找到時,輸出一條錯誤信息。
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public static T GetSafeComponent<T>(this GameObject obj) where T : MonoBehaviour  
  2. {  
  3.    T component = obj.GetComponent<T>();  
  4.    
  5.    if(component == null)  
  6.    {  
  7.       Debug.LogError("Expected to find component of type "   
  8.          + typeof(T) + " but found none", obj);  
  9.    }  
  10.    
  11.    return component;  
  12. }  


                                                                                                                                                    

風格

26、避免對同一件事使用不同的處理風格

在很多情況下,某件事並不只有一個慣用手法。在這種情況下,在項目中明確選擇其中的一個來使用。下面是原因:
  • 一些做法並不能很好的一起協作。使用一個,能強制統一設計方向,並明確指出不是其他做法所指的方向;
  • 團隊成員使用統一的風格,可能方便大家互相的理解。他使得整體結構和代碼都更容易理解。這也可以減少錯誤;
幾組風格的例子:
  • 協程與狀態機(Coroutines vs. state machines);
  • 嵌套的Prefab、互相鏈接的Prefab、超級Prefab(Nested prefabs vs. linked prefabs vs. God prefabs);
  • 數據分離的策略;
  • 在2D遊戲的使用Sprite的方法;
  • Prefab的結構;
  • 對象生成策略;
  • 定位對象的方法:使用類型、名稱、層、引用關係;
  • 對象分組的方法:使用類型、名稱、層、引用數組;
  • 找到一組對象,還是讓它們自己來註冊;
  • 控制執行次序(使用Unity的執行次序設置,還是使用Awake/Start/Update/LateUpdate,還是使用純手動的方法,或者是次序無關的架構);
  • 在遊戲中使用鼠標選擇對象/位置/目標:SelectionManager或者是對象自主管理;
  • 在場景變換時保存數據:通過PlayerPrefs,或者是在新場景加載時不要銷燬的對象;
  • 組合動畫的方法:混合、疊加、分層;
                                                                                                                                                     

時間

27、維護一個自己的Time類,可以使遊戲暫停更容易實現

做一個“Time.DeltaTime”和""Time.TimeSinceLevelLoad"的包裝,用來實現暫停和遊戲速度縮放。這使用起來略顯麻煩,但是當對象運行在不同的時鐘速率下的時候就方便多了(例如界面動畫和遊戲內動畫)。

                                                                                                                                                     

生成對象

28、不要讓遊戲運行時生成的對象搞亂場景層次結構

在遊戲運行時,爲動態生成的對象設置好它們的父對象,可以讓你更方便的查找。你可以使用一個空的對象,或者一個沒有行爲的單件來簡化代碼中的訪問。可以給這個對象命名爲“DynamicObjects”。

                                                                                                                                                    

類設計

29、使用單件(Singleton)模式

從下面這個類派生的所有類,將自動獲得單件功能:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class Singleton<T> : MonoBehaviour where T : MonoBehaviour  
  2. {  
  3.    protected static T instance;  
  4.    
  5.    /** 
  6.       Returns the instance of this singleton. 
  7.    */  
  8.    public static T Instance  
  9.    {  
  10.       get  
  11.       {  
  12.          if(instance == null)  
  13.          {  
  14.             instance = (T) FindObjectOfType(typeof(T));  
  15.    
  16.             if (instance == null)  
  17.             {  
  18.                Debug.LogError("An instance of " + typeof(T) +   
  19.                   " is needed in the scene, but there is none.");  
  20.             }  
  21.          }  
  22.    
  23.          return instance;  
  24.       }  
  25.    }  
  26. }  
單件可以作爲一些管理器,例如ParticleManager或者AudioManager亦或者GUIManager。
  • 對於那些非唯一的prefab實例使用單件管理器(例如Player)。不要爲了堅持這條原則把類的層次關係複雜化,寧願在你的GameManager(或其他合適的管理器中)中持有一個它們的引用。
  • 對於外部經常使用的共有變量和方法定義爲static,這樣你可以這樣簡便的書寫“GameManager.Player”,而不用寫成“GameManager.Instance.player”。

30、在組件中不要使用public成員變量,除非它需要在inspector中調節

除非需要設計師(策劃or美術)去調節的變量,特別是它不能明確表明自己是做什麼的變量,不要聲明爲public。如果在這些特殊情況下,無法避免,則可使用兩個甚至四個下劃線來表明不要從外部調節它,例如:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public float __aVariable;   

31、把界面和遊戲邏輯分開

這一條本質上就是指的MVC模式。

所有的輸入控制器,只負責向相應的組件發送命令,讓它們知道控制器被調用了。舉一個控制器邏輯的例子,一個控制器根據玩家的狀態來決定發送哪個命令。但是這樣並不好(例如,如果你添加了多個控制器,那將會導致邏輯重複)。相反的,玩家對象應該根據當前狀態(例如減速、驚恐)來設置當前的速度,並根據當前的面朝向來計算如何向前移動。控制器只負責做他們自己狀態相關的事情,控制器不改變玩家的狀態,因此控制前甚至可以根本不知道玩家的狀態。另外一個例子,切換武器。正確的方法是,玩家有一個函數:“SwitchWeapon(Weapon newWeapon)”供GUI調用。GUI不應該維護所有對象的Transform和他們之間的父子關係。

所有界面相關的組件,只負責維護和處理他們自己狀態相關的數據。例如,顯示一個地圖,GUI可以根據玩家的位移計算地圖的顯示。但是,這是遊戲狀態數據,它不屬於GUI。GUI只是顯示遊戲狀態數據,這些數據應該在其他地方維護。地圖數據也應該在其他地方維護(例如GameManager)。

遊戲玩法對象不應該關心GUI。有一個例外是處理遊戲暫停(可能是通過控制Time.timeScale,其實這並不是個好主意)。遊戲玩法對象應該知道遊戲是否暫停。但是,這就是全部了。另外,不要把GUI組件掛到遊戲玩法對象上。

這麼說吧,如果你把所有的GUI類都刪了,遊戲應該可以正確編譯。

你還應該達到:在不需要重寫遊戲邏輯的前提下,重寫GUI和輸入控制。

32、分離狀態控制和簿記變量

簿記變量只是爲了使用起來方便或者提高查找速度,並且可以根據狀態控制來覆蓋。將兩者分離可以簡化:
  • 保存遊戲狀態
  • 調試遊戲狀態
實現方法之一是爲每個遊戲邏輯定義一個”SaveData“類,例如:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. [Serializable]  
  2. PlayerSaveData  
  3. {  
  4.    public float health; //public for serialisation, not exposed in inspector  
  5. }   
  6.    
  7. Player  
  8. {  
  9.    //... bookkeeping variables  
  10.    
  11.    //Don’t expose state in inspector. State is not tweakable.  
  12.    private PlayerSaveData playerSaveData;   
  13. }  

33、分離特殊的配置

假設我們有兩個敵人,它們使用同一個Mesh,但是有不同的屬性設置(例如不同的力量、不同的速度等等)。有很多方法來分離數據。下面是我比較喜歡的一種,特別是對於對象生成或者遊戲存檔時,會很好用。(屬性設置不是狀態數據,而是配置數據,所以我們不需要存檔他們。當對象加載或者生成是,屬性設置會自動加載。)
  • 爲每一個遊戲邏輯類定義一個模板類。例如,對於敵人,我們來一個“EnemyTemplate”,所有的屬性設置變量都保存在這個類中。
  • 在遊戲邏輯的類中,定義一個上述模板類型的變量。
  • 製作一個敵人的Prefab,以及兩個模板的Prefab:“WeakEnemyTemplate”和"StrongEnemyTemplate"。
  • 在加載或者生成對象是,把模板變量正確的複製。
這種方法可能有點複雜(在一些情況下,可能不需要這樣)。
舉個例子,最好使用泛型,我們可以這樣定義我們的類:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class BaseTemplate  
  2. {  
  3.    ...  
  4. }  
  5.    
  6. public class ActorTemplate : BaseTemplate  
  7. {  
  8.    ...  
  9. }  
  10.    
  11. public class Entity<EntityTemplateType> where EntityTemplateType : BaseTemplate  
  12. {  
  13.    EntityTemplateType template;  
  14.    ...  
  15. }  
  16.    
  17. public class Actor : Entity <ActorTemplate>  
  18. {  
  19.    ...  
  20. }  

34、除了顯示用的文本,不要使用字符串

特別是不要用字符串作爲對象或者prefab等等的ID標識。一個很遺憾的例外是動畫系統,需要使用字符串來訪問相應的動畫。

35、避免使用public的數組

舉例說明,不要定義一個武器的數組,一個子彈的數組,一個粒子的數組,這樣你的代碼看起來像這樣:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void SelectWeapon(int index)  
  2. {   
  3.    currentWeaponIndex = index;  
  4.    Player.SwitchWeapon(weapons[currentWeapon]);  
  5. }  
  6.    
  7. public void Shoot()  
  8. {  
  9.    Fire(bullets[currentWeapon]);  
  10.    FireParticles(particles[currentWeapon]);     
  11. }  
這在代碼中還不是什麼大問題,但是在Inspector中設置他們的值的時候,就很難不犯錯了。
我們可以定義一個類,來封裝這三個變量,然後使用一個它的實例數組:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. [Serializable]  
  2. public class Weapon  
  3. {  
  4.    public GameObject prefab;  
  5.    public ParticleSystem particles;  
  6.    public Bullet bullet;  
  7. }   
這樣代碼看起來很整潔,但是更重要的是,在Inspector中設置時就不容易犯錯了。

36、在結構中避免使用數組

舉個例子,一個玩家可以有三種攻擊形式,每種使用當前的武器,併發射不同的子彈、產生不同的行爲。
你可以把三個子彈作爲一個數組,並像下面這樣組織邏輯:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void FireAttack()  
  2. {  
  3.    /// behaviour  
  4.    Fire(bullets[0]);  
  5. }  
  6.    
  7. public void IceAttack()  
  8. {  
  9.    /// behaviour  
  10.    Fire(bullets[1]);  
  11. }  
  12.    
  13. public void WindAttack()  
  14. {  
  15.    /// behaviour  
  16.    Fire(bullets[2]);  
  17. }   
使用枚舉值可以讓代碼看起來更好一點:
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void WindAttack()  
  2. {  
  3.    /// behaviour  
  4.    Fire(bullets[WeaponType.Wind]);  
  5. }   
但是這對Inspector一點也不好。
最好使用單獨的變量,並且起一個好的變量名,能夠代表他們的內容的含義。使用下面這個類會更整潔。
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. [Serializable]  
  2. public class Bullets  
  3. {  
  4.    public Bullet FireBullet;  
  5.    public Bullet IceBullet;  
  6.    public Bullet WindBullet;  
  7. }  
這裏假設沒有其他的Fire、Ice、Wind的數據。

37、把數據組織到可序列化的類中,可以讓inspector更整潔

有些對象有一大堆可調節的變量,這種情況下在Inspector中找到某個變量簡直就成了噩夢。爲了簡化這種情況,可以使用一下的步驟:
  • 把這些變量分組定義到不同的類中,並讓它們聲明爲public和serializable;
  • 在一個主要的類中,把上述類的實例定義爲public成員變量;
  • 不用在Awake或者Start中初始化這些變量,因爲Unity會處理好它們;
  • 你可以定義它們的默認值;
這可以把變量分組到Inspector的分組頁籤中,方便管理。
[csharp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. [Serializable]  
  2. public class MovementProperties //Not a MonoBehaviour!  
  3. {  
  4.    public float movementSpeed;  
  5.    public float turnSpeed = 1; //default provided  
  6. }  
  7.    
  8. public class HealthProperties //Not a MonoBehaviour!  
  9. {  
  10.    public float maxHealth;  
  11.    public float regenerationRate;  
  12. }  
  13.    
  14. public class Player : MonoBehaviour  
  15. {  
  16.    public MovementProperties movementProeprties;  
  17.    public HealthPorperties healthProeprties;  
  18. }  


                                                                                                                                                                 

文本

38、如果你有很多的劇情文本,那麼把他們放到一個文件裏面。

不要把他們放到Inspector的字段中去編輯。這些需要做到不打開Unity,也不用保存Scene就可以方便的修改。

39、如果你計劃實現本地化,那麼把你的字符串分離到一個統一的位置。

有很多種方法來實現這點。例如,定義一個文本Class,爲每個字符串定義一個public的字符串字段,並把他們的默認值設爲英文。其他的語言定義爲子類,然後重新初始化這些字段爲相應的語言的值。
另外一種更好的技術(適用於文本很大或者支持的語言數量衆多),可以讀取幾個單獨的表單,然後提供一些邏輯,根據所選擇的語言來選取正確的字符串。

                                                                                                                                                                       

測試與調試

40、實現一個圖形化的Log用來調試物理、動畫和AI。

這可以顯著的加速調試工作。詳見這裏

41、實現一個HTML的Log。

在很多情況下,日誌是非常有用的。擁有一個便於分析的Log(顏色編碼、有多個視圖、記錄屏幕截圖等)可以使基於Log的調試變動愉悅。詳見這裏

42、實現一個你自己的幀速率計算器。

沒有人知道Unity的FPS計算器在做什麼,但是肯定不是計算幀速率。實現一個你自己的,讓數字符合直覺並可視化。

43、實現一個截屏的快捷鍵。

很多BUG是圖形化的,如果你有一個截圖,就很容易報告它。一個理想的系統,應該在PlayerPrefes中保存一個計數,並根據這個計數,使得所有成功保存的截屏文件都不被覆蓋掉。截屏文件應該保存在工程文件夾之外,這可以防止人們不小心把它提交到版本庫中。

44、實現一個打印玩家座標的快捷鍵。

這可以在彙報位置相關的BUG時明確它發生在世界中的什麼位置,這可以讓Debug容易一些。

45、實現一些Debug選項,用來方便測試。

一些例子:
  • 解鎖所有道具;
  • 關閉所有敵人;
  • 關閉GUI;
  • 讓玩家無敵;
  • 關閉所有遊戲邏輯;

46、爲每一個足夠小的團隊,創建一個適合他們的Debug選項的Prefab。

設置一個用戶標識文件,單不要提交到版本庫,在遊戲運行時讀取它。下面是原因:
  • 團隊的成員不會因爲意外的提交了自己的Debug設置而影響到其他人。
  • 修改Debug設置不需要修改場景。

47、維護一個包含所有遊戲元素的場景。

例如,一個場景,包括所有的敵人,所有可以交互的對象等等。這樣可以不用玩很久,而進行全面的功能測試。

48、定義一些Debug快捷鍵常量,並把他們保存在統一的地方。

Debug鍵通常(方便起見)在一個地方來處理,就像其他的遊戲輸入一樣。爲了避免快捷鍵衝突,在一箇中心位置定義所有常量。一種替代方案是,在一個地方處理所有按鍵輸入,不管他是否是Debug鍵。(負面作用是,這個類可能需要引用更多的其他對象)

                                                                                                                                                           

文檔

49、爲你的設置建立文檔。
代碼應該擁有最多的文檔,但是一些代碼之外的東西也必須建立文檔。讓設計師們通過代碼去看如果進行設置是浪費時間。把設置寫入文檔,可以提高效率(如果文檔的版本能夠及時更新的話)。
用文檔記錄下面這些:
  • Layer的使用(碰撞、檢測、射線檢測——本質上說,什麼東西應該在哪個Layer裏);
  • Tag的使用;
  • GUI的depth層級(說什麼應該顯示在什麼之上);
  • 慣用的處理方式;
  • Prefab結構;
  • 動畫Layer。
                                                                                                                                                                                                                                                             

命名規則和目錄結構

50、遵從一個命名規範和目錄結構,並建立文檔

命名和目錄結構的一致性,可以方便查找,並明確指出什麼東西在哪裏。
你很有可能需要創建自己的命名規則和目錄結構,下面的例子僅供參考。

普遍的命名規則

  1. 名字應該代表它是什麼,例如鳥就應該叫做Bird
  2. 選擇可以發音、方便記憶的名字。如果你在製作一個瑪雅文化相關的遊戲,不要把關卡命名爲QuetzalcoatisReturn
  3. 保持唯一性。如果你選擇了一個名字,就堅持用它。
  4. 使用Pascal風格的大小寫,例如ComplicatedVerySpecificObject
    不要使用空格,下劃線,或者連字符,除了一個例外(詳見爲同一事物的不同方面命名一節)。
  5. 不要使用版本數字,或者標示他們進度的名詞(WIP、final)。
  6. 不要使用縮寫:DVamp@W應該寫成DarkVampire@Walk
  7. 使用設計文檔中的術語:如果文檔中稱呼一個動畫爲Die,那麼使用DarkVampire@Die,而不要用DarkVampire@Death
  8. 保持細節修飾詞在左側:DarkVampire,而不是VampireDarkPauseButton,而不是ButtonPaused。舉例說明,在Inspector中查找PauseButton,比所有按鈕都以Button開頭方便。(很多人傾向於相反的次序,認爲那樣名字可以自然的分組。然而,名字不是用來分組的,目錄纔是。名字是用來在同一類對象中可以快速辨識的。)
  9. 爲一個序列使用同一個名字,並在這些名字中使用數字。例如PathNode0, PathNode1。永遠從0開始,而不是1。
  10. 對於不是序列的情況,不要使用數字。例如 Bird0, Bird1, Bird2,本應該是Flamingo, Eagle, Swallow
  11. 爲臨時對象添加雙下劃線前綴,例如__Player_Backup

爲同一事物的不同方面命名

在覈心名稱後面添加下劃線,後面的部分代表哪個方面。例如
  • GUI中的按鈕狀態:EnterButton_Active、EnterButton_Inactive
  • 貼圖: DarkVampire_Diffuse, DarkVampire_Normalmap
  • 天空盒:JungleSky_Top, JungleSky_North
  • LOD分組:DarkVampire_LOD0, DarkVampire_LOD1

結構

場景組織、工程目錄、腳本目錄應該使用相似的模式。

目錄結構

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Materials  
  2. GUI  
  3. Effects  
  4. Meshes  
  5.    Actors  
  6.       DarkVampire  
  7.       LightVampire  
  8.       ...  
  9.    Structures  
  10.       Buildings  
  11.       ...  
  12.    Props  
  13.       Plants  
  14.       ...  
  15.    ...  
  16. Plugins  
  17. Prefabs  
  18.    Actors  
  19.    Items  
  20.    ...  
  21. Resources  
  22.    Actors  
  23.    Items  
  24.    ...  
  25. Scenes  
  26.    GUI  
  27.    Levels  
  28.    TestScenes  
  29. Scripts  
  30. Textures  
  31. GUI  
  32. Effects  
  33. ...  

場景結構

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Cameras  
  2. Dynamic Objects  
  3. Gameplay  
  4.    Actors  
  5.    Items  
  6.    ...  
  7. GUI  
  8.    HUD  
  9.    PauseMenu  
  10.    ...  
  11. Management  
  12. Lights  
  13. World  
  14.    Ground  
  15.    Props  
  16.    Structure  
  17.    ...  

腳本目錄結構

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ThirdParty  
  2.    ...  
  3. MyGenericScripts  
  4.    Debug  
  5.    Extensions  
  6.    Framework  
  7.    Graphics  
  8.    IO  
  9.    Math  
  10.    ...  
  11. MyGameScripts  
  12.    Debug  
  13.    Gameplay  
  14.       Actors  
  15.       Items  
  16.       ...  
  17.    Framework  
  18.    Graphics  
  19.    GUI  
  20.    ...  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章