Search Unity

Inheritance is hard. Give me a hand, please?

Discussion in 'Getting Started' started by AidanofVT, Oct 26, 2020.

  1. AidanofVT

    AidanofVT

    Joined:
    Nov 10, 2019
    Posts:
    104
    So, I have this class:

    Code (CSharp):
    1. public class MobileUnit : Unit {
    2.     AidansMovementScript moveConductor;
    3.  
    4.     void Awake () {
    5.         string prefab = gameObject.name;
    6.         prefab = prefab.Remove(prefab.IndexOf("("));
    7.         prefab = "Sprites/" + prefab;
    8.         if (photonView.Owner.ActorNumber == 1) {
    9.             GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(prefab + "_white");
    10.         }
    11.         else if (photonView.Owner.ActorNumber == 2) {
    12.             GetComponent<SpriteRenderer>().sprite = Resources.Load<Sprite>(prefab + "_orange");
    13.         }
    14.         if (this.GetType() == typeof(MobileUnit)) {
    15.             if (photonView.IsMine) {
    16.                 gameObject.AddComponent<MobileUnit_local>();
    17.             }
    18.             else {
    19.                 gameObject.AddComponent<MobileUnit_remote>();
    20.             }
    21.             Destroy(this);    
    22.         }
    23.     }
    24.  
    25.     private void Start() {
    26.         gameState = GameObject.Find("Goliad").GetComponent<GameState>();
    27.         MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
    28.         moveConductor = gameObject.GetComponent<AidansMovementScript>();
    29.     }
    30.  
    31.     public virtual void move (Vector3 target, Transform movingTransform = null) {
    32.         moveConductor.setDestination(target, movingTransform);
    33.     }
    34.  
    35. }
    ...and then I have this subordinate class:

    Code (CSharp):
    1. public class MobileUnit_local : MobileUnit {
    2.     AidansMovementScript moveConductor;
    3.  
    4.     void Start () {
    5.         gameState = GameObject.Find("Goliad").GetComponent<GameState>();
    6.         MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
    7.         moveConductor = GetComponent<AidansMovementScript>();
    8.         gameState.enlivenUnit(gameObject);
    9.     }
    10.  
    11.     public override void activate () {
    12.         gameState.activateUnit(gameObject);
    13.         gameObject.transform.GetChild(0).gameObject.SetActive(true);
    14.         transform.GetChild(0).GetChild(0).GetComponent<RectTransform>().localScale = new Vector3(1,1,1) * (Camera.main.orthographicSize / 5);
    15.     }
    16.  
    17.     public override void deactivate () {
    18.         gameObject.transform.GetChild(0).gameObject.SetActive(false);
    19.         gameState.deactivateUnit(gameObject);
    20.     }
    21.  
    22. }
    ... and I call "move" in the subordinate class like so:

    Code (CSharp):
    1. unit.GetComponent<MobileUnit>().move(destination, optionalTransform);
    ~~~~~~~~~~~~~~~~~~~~
    I get a null reference exception: moveConductor hasn't been defined. After probing around, I discovered that the MobileUnit.move call goes to a MobileUnit class, not a MobileUnit_local class. This happens even if I call this.move() from in the MobileUnit_local Start() function. I'm not great at inheritance, but I'm pretty sure that I should be able to define methods in parent methods and have them implicitly present in child methods.

    What am I not understanding?
     
  2. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    I don't believe this issue has anything to do with inheritance, rather it's just that your
    moveConductor
    script reference is null.
    You're trying to assign it via
    GetComponent
    :
    Code (CSharp):
    1. moveConductor = gameObject.GetComponent<AidansMovementScript>();
    Does your GameObject have an
    AidansMovementScript
    component attached onto it?

    A different issue I'm seeing involving inheritance here is that you have two
    Start
    methods.
    MobileUnit
    defines a
    Start
    method, and
    MobileUnit_local
    inherits
    MobileUnit
    , while also defining its own
    Start
    method.
    If you want to extend the
    Start
    method from
    MobileUnit
    , just mark it as
    protected virtual
    , so that child-classes can access and override it:
    Code (CSharp):
    1. public class MobileUnit : Unit {
    2.    protected virtual void Start() {
    3.       //Initialization stuff for MobileUnit here...
    4.    }
    5. }
    6.  
    7. public class MobileUnit_local : MobileUnit {
    8.    protected override void Start() {
    9.       base.Start(); //This calls the Start method in the parent MobileUnit class, so that its initialization logic can also run.
    10.  
    11.       //Initialization stuff for MobileUnit_local here...
    12.    }
    13. }
    In the same vein, you can also remove the duplicate
    moveConductor
    reference defined in
    MobileUnit_Local
    , since there's already one defined in the parent
    MobileUnit
    anyway, as well as these three duplicate lines in
    MobileUnit_Local
    's
    Start
    method...
    Code (CSharp):
    1. gameState = GameObject.Find("Goliad").GetComponent<GameState>();
    2. MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
    3. moveConductor = GetComponent<AidansMovementScript>();
    ...Because they're already defined in
    MobileUnit
    's
    Start
    method, and will run when calling
    base.Start()
    from
    MobileUnit_Local
    .

    Did you mean to override the
    move
    method in the
    MobileUnit_Local
    class? That will do what you're describing here.
    Otherwise, this is the intended behaviour. Child-classes do implicitly have the methods of parent-classes, but unless they're overridden, it's the method implementation in the parent class that will run, since the method doesn't actually exist in the child class explicitly.
     
    Last edited: Oct 26, 2020
    JoeStrout likes this.
  3. AidanofVT

    AidanofVT

    Joined:
    Nov 10, 2019
    Posts:
    104
    @Vryken Thank you for the thorough response. So, help me understand this: here's another case of inheritance in my project:

    Code (CSharp):
    1. public class Unit : MonoBehaviourPun {
    2.     protected GameState gameState;
    3.     protected GameObject MeatReadout;
    4.     public int maxMeat = 30;
    5.     public int meat = 10;
    6.     int meatCost = 10;
    7.  
    8.     void Awake () {
    9.     ...
    10.     }
    11.    
    12.     void Start() {
    13.         gameState = GameObject.Find("Goliad").GetComponent<GameState>();
    14.         MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
    15.     }
    16.  
    17.     [PunRPC]
    18.     public virtual void die() {
    19.         gameState.deadenUnit(gameObject);
    20.         GameObject orb = (GameObject)Resources.Load("Orb");
    21.         for (; meat > 0; --meat) {
    22.             Vector3 place = new Vector3(transform.position.x + Random.Range(-.1f, 0.1f), transform.position.y + Random.Range(-.5f, 0.5f), 0);
    23.             GameObject lastOrb = Instantiate(orb, place, transform.rotation);
    24.             lastOrb.GetComponent<Rigidbody2D>().AddForce((lastOrb.transform.position - transform.position).normalized * 2);
    25.         }
    26.         Destroy(gameObject);
    27.     }
    28. }
    Code (CSharp):
    1. public class Unit_local : Unit {
    2.  
    3.     void Start() {
    4.         gameState = GameObject.Find("Goliad").GetComponent<GameState>();
    5.         gameState.enlivenUnit(gameObject);
    6.         MeatReadout = transform.GetChild(0).GetChild(0).GetChild(0).gameObject;
    7.     }
    8.  
    9.     public override void activate () {
    10.         gameState.activateUnit(gameObject);
    11.         gameObject.transform.GetChild(0).gameObject.SetActive(true);
    12.         transform.GetChild(0).GetChild(0).GetComponent<RectTransform>().localScale = new Vector3(1,1,1) * (Camera.main.orthographicSize / 5);
    13.     }
    14.  
    15.     public override void deactivate () {
    16.         gameObject.transform.GetChild(0).gameObject.SetActive(false);
    17.         gameState.deactivateUnit(gameObject);
    18.     }
    19.  
    20. }
    So, same deal as before:
    • Virtual method die() in the base class is not overridden in child class.
    • die() refers to things that aren't defined in the base class.
    • die() called in an instance of child class (not pictured, but I tested it)
    This works! What is different here than in the case of move()?
     
  4. AidanofVT

    AidanofVT

    Joined:
    Nov 10, 2019
    Posts:
    104
    SOLVED: I just forget to name moveConductor as a Public or Protected object. Sorry @Vryken for taking your time with this one; I'm sure you understand that sometimes big dialogues are needed to spot small errors. I guess the real mystery is why VS Code didn't highlight the error last night, and Unity allowed the scripts to run.