1. 程式人生 > >使用Unity3D的50個技巧:Unity3D最佳實踐

使用Unity3D的50個技巧:Unity3D最佳實踐

歡迎轉載,請註明出處:燕良@遊戲開發。另外,歡迎各路高手加入我的QQ群:264656505,切磋交流技術。遊戲引擎能吃嗎


關於這些技巧

這些技巧不可能適用於每個專案。
  • 這些是基於我的一些專案經驗,專案團隊的規模從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啟動

這將大大的降低測試的時間。為了達到所有場景可執行,你需要做兩件事: 首先,如果需要前面場景執行產生的一些資料,那麼要模擬出它們。 其次,生成在場景切換時必要儲存的物件,可以是這樣:
myObject = FindMyObjectInScene();
 
if (myObjet == null)
{
   myObject = SpawnMyObject();
}

美術

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),用它來定義需要呼叫的方法,而不要使用字串屬性方法名稱,例如:
public void Invoke(Task task, float time)
{
   Invoke(task.Method.Name, time);
}

23、為共享介面的元件擴充套件

有些時候把獲得元件、查詢物件實現在一個元件的介面中會很方便。 下面這種實現方案使用了typeof,而不是泛型版本的函式。泛型函式無法在介面上工作,而typeof可以。下面這種方法把泛型方法整潔的包裝起來。
//Defined in the common base class for all mono behaviours
public I GetInterfaceComponent<I>() where I : class
{
   return GetComponent(typeof(I)) as I;
}
 
public static List<I> FindObjectsOfInterface<I>() where I : class
{
   MonoBehaviour[] monoBehaviours = FindObjectsOfType<MonoBehaviour>();
   List<I> list = new List<I>();
 
   foreach(MonoBehaviour behaviour in monoBehaviours)
   {
      I component = behaviour.GetComponent(typeof(I)) as I;
 
      if(component != null)
      {
         list.Add(component);
      }
   }
 
   return list;
}

24、使用擴充套件來讓程式碼書寫更便捷

例如:
public static class CSTransform 
{
   public static void SetX(this Transform transform, float x)
   {
      Vector3 newPosition = 
         new Vector3(x, transform.position.y, transform.position.z);
 
      transform.position = newPosition;
   }
   ...
} 

25、使用防禦性的GetComponent()

有些時候強制性元件依賴(通過RequiredComponent)會讓人蛋疼。例如,很難在Inspector中修改元件(即使他們有同樣的基類)。下面是一種替代方案,當一個必要的元件沒有找到時,輸出一條錯誤資訊。
public static T GetSafeComponent<T>(this GameObject obj) where T : MonoBehaviour
{
   T component = obj.GetComponent<T>();
 
   if(component == null)
   {
      Debug.LogError("Expected to find component of type " 
         + typeof(T) + " but found none", obj);
   }
 
   return component;
}


風格

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)模式

從下面這個類派生的所有類,將自動獲得單件功能:
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
   protected static T instance;
 
   /**
      Returns the instance of this singleton.
   */
   public static T Instance
   {
      get
      {
         if(instance == null)
         {
            instance = (T) FindObjectOfType(typeof(T));
 
            if (instance == null)
            {
               Debug.LogError("An instance of " + typeof(T) + 
                  " is needed in the scene, but there is none.");
            }
         }
 
         return instance;
      }
   }
}
單件可以作為一些管理器,例如ParticleManager或者AudioManager亦或者GUIManager。
    • 對於那些非唯一的prefab例項使用單件管理器(例如Player)。不要為了堅持這條原則把類的層次關係複雜化,寧願在你的GameManager(或其他合適的管理器中)中持有一個它們的引用。
    • 對於外部經常使用的共有變數和方法定義為static,這樣你可以這樣簡便的書寫“GameManager.Player”,而不用寫成“GameManager.Instance.player”。

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

除非需要設計師(策劃or美術)去調節的變數,特別是它不能明確表明自己是做什麼的變數,不要宣告為public。如果在這些特殊情況下,無法避免,則可使用兩個甚至四個下劃線來表明不要從外部調節它,例如:
public float __aVariable; 

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

這一條本質上就是指的MVC模式。 所有的輸入控制器,只負責向相應的元件傳送命令,讓它們知道控制器被呼叫了。舉一個控制器邏輯的例子,一個控制器根據玩家的狀態來決定傳送哪個命令。但是這樣並不好(例如,如果你添加了多個控制器,那將會導致邏輯重複)。相反的,玩家物件應該根據當前狀態(例如減速、驚恐)來設定當前的速度,並根據當前的面朝向來計算如何向前移動。控制器只負責做他們自己狀態相關的事情,控制器不改變玩家的狀態,因此控制前甚至可以根本不知道玩家的狀態。另外一個例子,切換武器。正確的方法是,玩家有一個函式:“SwitchWeapon(Weapon newWeapon)”供GUI呼叫。GUI不應該維護所有物件的Transform和他們之間的父子關係。 所有介面相關的元件,只負責維護和處理他們自己狀態相關的資料。例如,顯示一個地圖,GUI可以根據玩家的位移計算地圖的顯示。但是,這是遊戲狀態資料,它不屬於GUI。GUI只是顯示遊戲狀態資料,這些資料應該在其他地方維護。地圖資料也應該在其他地方維護(例如GameManager)。 遊戲玩法物件不應該關心GUI。有一個例外是處理遊戲暫停(可能是通過控制Time.timeScale,其實這並不是個好主意)。遊戲玩法物件應該知道遊戲是否暫停。但是,這就是全部了。另外,不要把GUI元件掛到遊戲玩法物件上。 這麼說吧,如果你把所有的GUI類都刪了,遊戲應該可以正確編譯。 你還應該達到:在不需要重寫遊戲邏輯的前提下,重寫GUI和輸入控制。

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

簿記變數只是為了使用起來方便或者提高查詢速度,並且可以根據狀態控制來覆蓋。將兩者分離可以簡化:
    • 儲存遊戲狀態
    • 除錯遊戲狀態
實現方法之一是為每個遊戲邏輯定義一個”SaveData“類,例如:
[Serializable]
PlayerSaveData
{
   public float health; //public for serialisation, not exposed in inspector
} 
 
Player
{
   //... bookkeeping variables
 
   //Don’t expose state in inspector. State is not tweakable.
   private PlayerSaveData playerSaveData; 
}

33、分離特殊的配置

假設我們有兩個敵人,它們使用同一個Mesh,但是有不同的屬性設定(例如不同的力量、不同的速度等等)。有很多方法來分離資料。下面是我比較喜歡的一種,特別是對於物件生成或者遊戲存檔時,會很好用。(屬性設定不是狀態資料,而是配置資料,所以我們不需要存檔他們。當物件載入或者生成是,屬性設定會自動載入。)
    • 為每一個遊戲邏輯類定義一個模板。例如,對於敵人,我們來一個“EnemyTemplate”,所有的屬性設定變數都儲存在這個類中。
    • 在遊戲邏輯的類中,定義一個上述模板型別的變數。
    • 製作一個敵人的Prefab,以及兩個模板的Prefab:“WeakEnemyTemplate”和"StrongEnemyTemplate"。
    • 在載入或者生成物件是,把模板變數正確的複製。
這種方法可能有點複雜(在一些情況下,可能不需要這樣)。 舉個例子,最好使用泛型,我們可以這樣定義我們的類:
public class BaseTemplate
{
   ...
}
 
public class ActorTemplate : BaseTemplate
{
   ...
}
 
public class Entity<EntityTemplateType> where EntityTemplateType : BaseTemplate
{
   EntityTemplateType template;
   ...
}
 
public class Actor : Entity <ActorTemplate>
{
   ...
}

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

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

35、避免使用public的陣列

舉例說明,不要定義一個武器的陣列,一個子彈的陣列,一個粒子的陣列,這樣你的程式碼看起來像這樣:
public void SelectWeapon(int index)
{ 
   currentWeaponIndex = index;
   Player.SwitchWeapon(weapons[currentWeapon]);
}
 
public void Shoot()
{
   Fire(bullets[currentWeapon]);
   FireParticles(particles[currentWeapon]);   
}
這在程式碼中還不是什麼大問題,但是在Inspector中設定他們的值的時候,就很難不犯錯了。
我們可以定義一個類,來封裝這三個變數,然後使用一個它的例項陣列:
[Serializable]
public class Weapon
{
   public GameObject prefab;
   public ParticleSystem particles;
   public Bullet bullet;
} 
這樣程式碼看起來很整潔,但是更重要的是,在Inspector中設定時就不容易犯錯了。

36、在結構中避免使用陣列

舉個例子,一個玩家可以有三種攻擊形式,每種使用當前的武器,併發射不同的子彈、產生不同的行為。 你可以把三個子彈作為一個數組,並像下面這樣組織邏輯:
public void FireAttack()
{
   /// behaviour
   Fire(bullets[0]);
}
 
public void IceAttack()
{
   /// behaviour
   Fire(bullets[1]);
}
 
public void WindAttack()
{
   /// behaviour
   Fire(bullets[2]);
} 
使用列舉值可以讓程式碼看起來更好一點:
public void WindAttack()
{
   /// behaviour
   Fire(bullets[WeaponType.Wind]);
} 
但是這對Inspector一點也不好。
最好使用單獨的變數,並且起一個好的變數名,能夠代表他們的內容的含義。使用下面這個類會更整潔。
[Serializable]
public class Bullets
{
   public Bullet FireBullet;
   public Bullet IceBullet;
   public Bullet WindBullet;
}
這裡假設沒有其他的Fire、Ice、Wind的資料。

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

有些物件有一大堆可調節的變數,這種情況下在Inspector中找到某個變數簡直就成了噩夢。為了簡化這種情況,可以使用一下的步驟:
  • 把這些變數分組定義到不同的類中,並讓它們宣告為public和serializable;
  • 在一個主要的類中,把上述類的例項定義為public成員變數;
  • 不用在Awake或者Start中初始化這些變數,因為Unity會處理好它們;
  • 你可以定義它們的預設值;
這可以把變數分組到Inspector的分組頁籤中,方便管理。
[Serializable]
public class MovementProperties //Not a MonoBehaviour!
{
   public float movementSpeed;
   public float turnSpeed = 1; //default provided
}
 
public class HealthProperties //Not a MonoBehaviour!
{
   public float maxHealth;
   public float regenerationRate;
}
 
public class Player : MonoBehaviour
{
   public MovementProperties movementProeprties;
   public HealthPorperties healthProeprties;
}


文字

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. 不要使用縮寫:[email protected]應該寫成[email protected]
  7. 使用設計文件中的術語:如果文件中稱呼一個動畫為Die,那麼使用[email protected],而不要用[email protected]
  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

結構

場景組織、工程目錄、指令碼目錄應該使用相似的模式。

目錄結構

Materials
GUI
Effects
Meshes
   Actors
      DarkVampire
      LightVampire
      ...
   Structures
      Buildings
      ...
   Props
      Plants
      ...
   ...
Plugins
Prefabs
   Actors
   Items
   ...
Resources
   Actors
   Items
   ...
Scenes
   GUI
   Levels
   TestScenes
Scripts
Textures
GUI
Effects
...

場景結構

Cameras
Dynamic Objects
Gameplay
   Actors
   Items
   ...
GUI
   HUD
   PauseMenu
   ...
Management
Lights
World
   Ground
   Props
   Structure
   ...

指令碼目錄結構

ThirdParty
   ...
MyGenericScripts
   Debug
   Extensions
   Framework
   Graphics
   IO
   Math
   ...
MyGameScripts
   Debug
   Gameplay
      Actors
      Items
      ...
   Framework
   Graphics
   GUI
   ...