How to have Rigidbody2D assigned through inheritance

I’m learning about inheritance and created a parent class called Character. Then there’s a child class called Player. I was trying to assign the rigidbody2D in the parent class through code, but I kept getting a NullReferenceException error saying the instance of rb is not assigned for the Player.

public class Character : MonoBehaviour {

    public int maxHealth;
    public int currentHealth;
    public HealthBar healthBar;

    [SerializeField] protected float damageInterval = 2f;
    protected float currentDamageInterval;

    public Rigidbody2D rb;

    void Start() {
        rb = GetComponent<Rigidbody2D>();
        currentHealth = maxHealth;
        healthBar.SetMaxHealth(maxHealth);
    }

    public virtual void Update() {
        //subtract 1 real life second
        if (currentDamageInterval > 0)
            currentDamageInterval -= Time.deltaTime;
    }

    public bool CanTakeDamage() {
        return (currentDamageInterval < 0);
    }

    public virtual void TakeDamage(int damage) {
        currentHealth -= damage;
        healthBar.SetHealth(currentHealth);

        int yDamageVelocity = 15;
        rb.velocity = new Vector2(rb.velocity.x, yDamageVelocity);
        currentDamageInterval = damageInterval;
    }
}
public class Player : Character {


    [SerializeField] private Transform groundCheck;
    [SerializeField] private LayerMask hazardLayer;

    private SpriteRenderer sr;
    private Color[] colors = { Color.red, Color.white };
    private Coroutine damageFlash;

    void Start() {
        sr = GetComponent<SpriteRenderer>();
        maxHealth = 10;
    }

    public override void Update() {
        base.Update();
        if (CanTakeDamage())
            if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer))
                TakeDamage(1);

        //for testing purposes
        if (Input.GetKeyDown(KeyCode.H))
            TakeDamage(1);
    }

    public override void TakeDamage(int damage) {
        base.TakeDamage(damage);

        if (damageFlash != null)
            StopCoroutine(damageFlash);
        damageFlash = StartCoroutine(DamageFlashing(1f, .1f));
    }

    IEnumerator DamageFlashing(float duration, float interval) {
        int index = 0;
        //var is a replacement for WaitForSeconds bc it would be redundant
        var wait = new WaitForSeconds(interval);

        for (float elapsedTime = 0; elapsedTime < duration; elapsedTime += interval) {
            sr.color = colors[index % 2];
            index++;
            yield return wait;
        }
        damageFlash = null;
    }
}

Initially because the Player’s rigidbody2D was not assigned, the overrided portion of the TakeDamage method in the Player class could not run. It’s odd because in the Character’s Start method, I assign rb = GetComponent<Rigidbody2D>() which I thought would in turn assign the Player’s rigidbody2D to rb. I ended up making the Rigidbody2D rb public at the top in the Character class and then assigned the Player’s rigidbody2D in the inspector. Now everything works.

But I was wondering if there’s a way to assign the Player’s (or any child class’s) rigidbody2D in the Character class by code instead? Or a cleaner way to do this?

You are hiding your Character.Start() method with your Player.Start() method. Your IDE should be showing a warning, honestly.

Make it virtual, and override it in your inherited class, like you are doing in your other virtual methods.

Mind you, this kind of pattern is generally not as useful in games as it is to use Interfaces. Composition over Inheritance, as they say.

2 Likes

No, the IDE would not show any warning here since both methods are declared private. So the IDE and compiler couldn’t care less about them. That’s why you should declare the Unity callbacks as protected whenever you plan to use deeper inheritance. So the base implementation can actually be reached from an overridden / hiding implementation of a derived class. Technically Unity callbacks don’t need to be virtual since Unity calls them directly on the actual instance. However they need to be protected or public in order to be accessible in the derived class. So you can do base.Start(); inside the derived Start.

Though declaring them virtual protected is generally cleaner.

3 Likes

This works! Thank you.

I will look into using composition. This is a small game so I may stick to this for now and get more acquainted with inheritance. Then on my next game I will use composition.