Home > OS >  How can I use inheritance in Unity effectively?
How can I use inheritance in Unity effectively?

Time:04-29

I have basic understanding of inheritance but since every script I want as a component on a game object has to inherit from MonoBehaviour things get tricky.

Let's say I want to have a base class called Character and I want Player and Enemy to derive from Character. How am I suppose to add Player and Enemy script to Player and Enemy objects without getting an error?

I also tried to create empty game object and add CharacterStats to it then populate this class with different character objects with different stats. Guess what, you can't use new keyword to create objects if the script derives from Monobehaviour.

Is there some tutorials about this topic to make it more clear?

CodePudding user response:

Probably the thing to understand is that MonoBehaviour itself inherits Component - A MonoBehaviour IS a Component.

When you look at it as if Components are a part of a thing, it makes at least some sense that you can't just new a Component - could I make a new Arm? If I did, where would it got? But I could make a new Body body and then dobody.AddComponent<Arm>, right? Because then it's clear what happens to the Arm - it gets attached to the body.

Similarly you can't just new a Component because they're supposed to be a part of a GameObject. What you can do is to make a new GameObject and .AddComponent<>() to that object. Again, now it's clear where that Component is going.

I generally agree with UnholySheep's comment that you should prefer composition over inheritance because this generally makes your code more modular. That said, I definitely do also use inheritance.

The question you should be asking yourself when you're outlining your class is, "Is this new class a KIND of _____ or does this new class HAVE a _____." And it's easy I guess to say a Player is a Character and an Enemy is a Character, but do you need to subclass? Or would a Player just be a Character that also has a PlayerController? Maybe you could have an interface like IPlayerController and have a LocalPlayerController for the user and an AIPlayerController for the enemies? Lots of ways to problem-solve with programming.

Or maybe all Characters have a PlayerController and just the .activeControl bit is false for non-player characters. Then you could do things like ghost/spectate NPCs, etc.

But to your question specifically:

Let's say I want to have a base class called Character and I want Player and Enemy to derive from Character. How am I suppose to add Player and Enemy script to Player and Enemy objects without getting an error?

You would start with a Character script that inherits MonoBehaviour and implements any of the logic that is common to both classes:

public class Character : MonoBehaviour
{
    // Your common character logic
}

Then you would have subclasses inherit Character:

public class Player : Character
{
    // Your Player-specific logic here
}

and

public class Enemy : Character
{
    // Your Enemy-specific logic here
}

Then you either add the Enemy and Player scripts to a GameObject in the editor or you can do it programmatically by getting a reference to the target GameObject (by setting the reference in editor or by creating a new GameObject in a script) and then you call targetGameObject.AddComponent<YourComponentToAdd>();.

CodePudding user response:

Using inheritance well comes from planning and iterating. Perhaps your Character class doesn't work well as a class for your enemies. I tend to have the player in their own class (unless its multiplayer) with enemies using their own base class. Below is an example of some inheritance I use in a 2D game I'm working on.

Base Class: Enemy.cs

public abstract class Enemy : MonoBehaviour
{
  public Vector2 flyInPos;
  public Vector2 startPos;

  public bool isFlyingIn;

  public float flyInDuration = 5000f;
  public float flyInTime = 0f;

  public abstract void Destroy();

  public abstract void DestroyWithoutScore();

  public abstract void Attack();

  public abstract void Attack2();

  public abstract void Attack3();

  public abstract void FlyOut();

  public virtual void FlyIn()
  {
    isFlyingIn = true;
  }
}

Every enemy needs to have these elements. You'll notice most of the methods in my base class are marked abstract so that each class that inherits can have a unique set of Attacks, and Flying patterns. One thing to notice is that the base class is inheriting from MonoBehaviour. Here is how I implemented inheritance in one of my enemies. Sorry its still a WIP.

Inherited Class: ShootingDrone.cs

public class ShootingDrone : Enemy
{
  public GameObject projectileSpawner;
  public GameObject projectile;

  public void Start()
  {
    startPos = transform.position;
    Scheduler.Invoke(Attack, 5f, gameObject);
  }

  public void Update()
  {
    if(isFlyingIn)
    {
      transform.position = Vector3.Lerp(startPos, flyInPos, flyInTime / flyInDuration);
      flyInTime  = Time.deltaTime;
    }
  }

  public override void Attack()
  {
    Shoot();
    Scheduler.Invoke(Shoot, 0.25f, gameObject);
    Scheduler.Invoke(Shoot, 0.5f, gameObject);
  }

  public override void Attack2()
  {
    Attack();
  }

  public override void Attack3()
  {
    Attack();
  }

  public void Shoot()
  {
    GameObject newProjectile = Instantiate(projectile, projectileSpawner.transform);
    newProjectile.transform.parent = null;
  }

  public override void FlyOut()
  {
    throw new System.NotImplementedException();
  }

  public override void DestroyWithoutScore()
  {
    throw new System.NotImplementedException();
  }

  public override void Destroy()
  {
    Destroy(gameObject);
  }
}

The base class of Enemy inherits from MonoBehaviour. So when Shooting Drone inherits from Enemy it also is inheriting from MonoBehaviour.

For your problem of trying to add a new MonoBehaviour. The reason for this is that a MonoBehavior HAS to be attached to something. Imagine a Rigidbody with no GameObject, it doesn't work. If you wanted to make a "new" CharacterStats the way you would do that is:

CharacterStats stats = gameObject.AddComponent<CharacterStats>();

If you don't want your CharacterStats as a component, then simply remove the MonoBehavior inheritance from the class and instantiate it as new CharacterStats.

There are a variety of tutorials that cover inheritance, but depending on how new you are to the subject I would start with the Unity official inheritance tutorial. I do think this tutorial is too brief but I also like CircutStreams tutorial since it also mentions implementing Interfaces which can be a better solution to inheritance in many cases.

  • Related