2020年9月16日 星期三

[筆記] Unity3D - 繼承類別不要有與基礎類別同名的屬性

  最近在寫遊戲時遭遇一點困難:在繼承基礎類別時,需要將其附屬的資料類別一併繼承,而在建立繼承類別的實體後,遇到初始化基礎類別的資料類別的問題,原因只是因為兩者的欄位 (field) 同名。有問題的程式碼像這樣:
class Character : MonoBehaviour
{
    protected CharacterData data;

    ...
}

class CharacterData : ScriptableObject
{
    public float movingVelocity = 10.0f;
    public float rotatingVelocity = 10.0f;
}

class Player : Character
{
    public new PlayerData data;

    private void Start()
    {
        base.data = data;
    }

    ...
}

[CreateAssetMenu(fileName = "PlayerData",
    menuName = "Scriptable Object/PlayerData", order = 1)]
class PlayerData : CharacterData
{
    public float firingPeriod = 0.1f;
}
Unity 在編譯後會出現錯誤訊息,"The same field name is serialized multiple times in the class or its parent class. This is not supported: Base(Player) data":

即使可以忽略錯誤訊息將 PlayerData 的 scriptable object 設給 data 欄位:
但只要重新編譯後,該欄位還是會變回 None,即使為基礎類別的 data 加上 [HideInInspector] 的性質。因為 Unity 無法為一個物件同名的欄位初始化多次(一次是 Player 裡的 data,一次是繼承的 Character 裡的 data)。
  其實解法很簡單,一個解法是直接命名成不同的名稱,在 Character 裡的叫做 characterData,在 Player 裡的 playerData。另一個解法是將 Character 裡的 data 設為 private,並提供對應的 setter 與 getter,因為屬性 (property) 不會被 Unity 序列化:
class Character : MonoBehaviour
{
    private CharacterData _data;
    public CharacterData data {
        get => _data;
        set => _data = value;
    }

    ...
}

class Player : Character
{
    public new PlayerData data;

    private void Start()
    {
        base.data = data;
    }

    ...
}
而我傾向前者的解法,也就是直接改名,因為修改成本最低,也可以保留 Character 作為獨立物件的能力,像是可以直接在 inspector 將 CharacterData 設給它的 characterData 欄位。如果採用後者的作法就不行了。
  在這篇的例子中,我把 CharacterData 設為 private 只是希望存取權限限制在自己和繼承的類別,如果原本是 public 也是可以,只是在 inspector 中會出現兩個欄位,加上 [HideInInspector] 即可:

  想寫這篇文章是因為我一開始把問題想的太複雜,還大興土木改了資料的架構,後來回頭檢視是不是繞了遠路,冷靜想想才發現問題其實很簡單。

沒有留言:

張貼留言