C#程式員整理的Unity 3D筆記(十三):Unity 3D基於組件的思想,
如果你接觸過《設計模式》、軟體架構的編程思想,就會知道優秀的設計準則:“組合優於繼承的”。
這句話很簡短,但開始學習OOP的時候,真切的是—-不太好理解(以我個人當初學習為例)。
OOP的繼承思想
在設計主角(Player)的時候,為了能夠複用A、B、C的功能,我開始把A、B、C按照繼承來寫,多了一些Virutal\Override\Protected等修飾符,功能沒有任何問題,就是有些彆扭。如Start、Update方法,只能在A中採用模板方法處理,萬一B、C、Player中直接用了Start、Update方法,會導致奇奇怪怪的問題;同時在繼承的基類中,無形之間多了一些包袱,對於Player不得不使用A、B、C的函數、變數(非private的)。
整個關係變為了:
- Player is a A
- Player is a B
- Player is a C
心理上疙疙瘩瘩的,總覺得有點彆扭。
OOP的組合思想
以前使用組合思想較多的是構建樹、樹葉模型,例如電信中的網元模型。這種思想,屬於Unity 3D的核心思想–組件。在Player、A、B、C中可自由使用Start、Update函數(請不考慮執行順序,指令碼組件的先後順序外部可調整,但是意義不大),最重要的是,關係理順了—主角變成更積極、主動。
- Player have a A
- Player have a B
- Player have a C
在Unity 3D中,可複用的幾乎全部為封裝為了組件,eg: transform、rigibody、render、camera、***.cs指令碼;為了配合方便的使用非內建的組件,可使用gameObject.AddComponent<T>()、gameObject.GetComponent<T>()來添加、獲得組件(一般是自訂的指令碼)。
這裡我舉一個實際的例子,在《Unity 3D手機遊戲開發》第二版的“太空射擊遊戲”中,有一個需求,需要給遊戲中可複用的GameObject添加自動銷毀的功能(通過時間計時器,或者觸發器添加),代碼很簡單,不到100行,要添加的GameObject有5、6個,雖然工作量不大,但總不能每個都拷貝一遍代碼吧。
開始我是按照OOP繼承做的,
看了幾天,很不爽,後來重構為如:
這樣使得自動銷毀組件的功能發揮的更加靈活、機動,即不必拘泥於靜態繼承思想來實現。
從這個重構過程中,我學到Unity 3D組件思想的閃閃發光……
附錄:完整的自動銷毀組件代碼:
public class AutoDestoryComponent : MonoBehaviour
{
#region ICanCache
public ParticleSystem[] m_pss = null;
public int m_life = 1; //3條命
public float m_AutoDeadTime = 3;//3s自動銷毀
private int m_life_Base = 3; //3條命【恢複用】
private float m_AutoDeadTime_Base = 3;//3s自動銷毀【恢複用】【-1:表示不自動銷毀,如Enemy】
void Update()
{
//需要自動銷毀
if (m_AutoDeadTime_Base >= 0)
{
m_AutoDeadTime -= Time.deltaTime;
if (m_AutoDeadTime <= 0)
{
InnerDead();
return;
}
}
if (m_life <= 0)
{
InnerDead();
}
}
/// <summary>
/// 設定自動銷毀資料
/// </summary>
/// <param name="life_base">預設生命值</param>
/// <param name="autoDeadTime_base">-1不自動銷毀;其他資料代表銷毀時間(單位s)</param>
public void SetBasePara(int life_base = 1, float autoDeadTime_base = -1)
{
m_AutoDeadTime = m_AutoDeadTime_Base = autoDeadTime_base;
m_life = m_life_Base = life_base;
}
//是否啟用
public bool IsUse { get; set; }
//死後位置
public Vector3 DeathPosition
{
get
{
return new Vector3(2000, 2000, 2000);
}
}
//複活
public void Init(Vector3 position, Quaternion rotation)
{
transform.gameObject.SetActive(true);
transform.position = position;
transform.rotation = rotation;
IsUse = true;
foreach (ParticleSystem item in m_pss)
{
item.Play(true);
}
//有些繞
m_life = m_life_Base;
m_AutoDeadTime = m_AutoDeadTime_Base;
}
private void InnerDead()
{
IsUse = false;
transform.position = DeathPosition;
foreach (ParticleSystem item in m_pss)
{
item.Stop(true);
}
this.gameObject.SetActive(false);
}
#endregion
}
包括系統內建的Audio、Transform、Camera、Image、Button等等。GameObject是一個容器,沒有Image的GameObject,只要建立一個空的GameObject,添加Image Component極為Image GameObject對象的。
也即是在Unity3D中,很少用GameObject.ID的概念,而是用GameObject.Tag、GameObject.name來區分不同的GameObject,且Tag、name不唯一。
結論:在Unity3D中,萬事萬物都是Component。