Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Creating multiple enemy types using an enemy parent class

Discussion in 'Scripting' started by Rxanadu, Jun 11, 2014.

  1. Rxanadu

    Rxanadu

    Joined:
    May 24, 2012
    Posts:
    50
    I've run into an issue when setting up multiple enemies. My current enemy system does not allow for multiple enemy types, which I realized I needed for the game I'm making currently. They will all have wildly different AI routines: one walks around and shoots the player when it detects it; another just flies toward the player when the player enters a trigger around it; and yet another shuffles back and forth when the player is nearby.

    However, all of the enemies will have some things in common:
    • They have a set amount of health
    • They decrease the player's health by a set amount of hit points
    • They have Animator components used for their separate animations
    • They (may) have access to the player's health script in order to damage the player
    • They will have a set move speed
    I'm trying to create a parent class for defining these commonalities for each individual enemy in my game, but I'm uncertain as to how to design the class for use in inheritance purposes. By that, I mean, how much information would be inherited by each child class (i.e. would I set up a constructor to define which enemy type and what properties the enemy has, or would I just use simple public/protected variables from the parent class in child classes?). Would a parent class even be the way to go about doing this when I could easily use enumerators to define which enemy is which?

    Here's what I imagine the parent-child solution would look like:
    Code (CSharp):
    1. public class Enemy: MonoBehaviour{
    2. public float damageAmount;
    3. public Animator anim;
    4. public float hitPoints;
    5. public float moveRate;
    6. public PlayerHealth playerHealth;
    7.  
    8. public Enemy(float damage, float health, float speed, Animator a){
    9.     damageAmount = damage;
    10.     hitPoints = health;
    11.     moveRate = speed;
    12.     anim = a;
    13.  
    14.     playerHealth = GameObject.Find("Player").GetComponent<PlayerHealth>();
    15. }
    16. }
    Code (CSharp):
    1. [RequireComponent(typeof(Rigidbody2D))]
    2. public class EnemyChild: Enemy{
    3. EnemyChild e = new Enemy(5, 7, 20, anim);
    4.  
    5. void Update(){
    6. //do things an enemy child would
    7. rigidBody2D.AddForce(e.moveRate);
    8. }
    9.  
    10. void OnTriggerEnter2D(Collider2D other){
    11. if(other.CompareTag("Player")){
    12. e.playerHealth.DamagePlayer(e.damageAmount);
    13. }
    14. }
    15. }
    I'm not quite sure how the other method would work, but I hope this helps demonstrate what I want to do with each enemy through the game.

    If this doesn't look right or if you can think of any refinements, I'd be happy to listen.
     
  2. Glockenbeat

    Glockenbeat

    Joined:
    Apr 24, 2012
    Posts:
    669
    First off - your Enemy base class inherits from MonoBehavior which you can only create using Instantiate(), not as a new object with the "new" keyword. Unity will tell you that in the console as well once you try to do so.
    This means that you also cannot make use of the constructor which you put in there, but that should not be necessary.

    Usually you can put all your instantiation code in the Awake() or Start() methods. If you need to provide values during initialization do so via a public method which can be called.

    Apart from that you get the usual inheritance, meaning you will see all the values from your base class in the derived class as well once you instantiate the EnemyChild. What you are currently doing in your EnemyChild class is simply wrong!

    This is a very basic concept of object oriented programming in C# (Inheritance), thus I strongly recommend to have a look at the documentation about this:

    http://msdn.microsoft.com/en-us/library/ms173149(VS.80).aspx

    So, in order to make things right you can try the following:

    Code (csharp):
    1.  
    2. public abstract class Enemy: MonoBehaviour {
    3.  
    4.     public float damageAmount = 10f;    // just a default value
    5.     public float hitPoints = 100f;        // just a default value
    6.     public float moveRate = 10f;        // default value
    7.  
    8.     public PlayerHealth playerHealth;
    9.     public Animator anim;    
    10.  
    11.     public void Start() {
    12.         playerHealth = GameObject.Find("Player").GetComponent<PlayerHealth>();
    13.         anim = GetComponent<Animator>();
    14.     }
    15. }
    16.  
    (the abstract keyword in the class declaration will make sure that you cannot use the class on an object directly but first need to create a derived class from it)

    and for the derived class

    Code (csharp):
    1.  
    2. [RequireComponent(typeof(Rigidbody2D))]
    3. public class EnemyChild: Enemy{
    4.  
    5.     void Start() {
    6.         base.Start();    // calls the Start() method in the parent class!
    7.         // other start stuff here...
    8.     }
    9.    
    10.     void Update(){
    11.         //do things an enemy child would
    12.         rigidBody2D.AddForce(e.moveRate);
    13.     }
    14.    
    15.     void OnTriggerEnter2D(Collider2D other){
    16.         if(other.CompareTag("Player")){
    17.             playerHealth.DamagePlayer(damageAmount);    // note that playerHealth and damageAmount are already set in the parent class!
    18.         }
    19.     }
    20. }
    21.  
    So let's assume you put your EnemyChild class as a component on a gameObject, duplicate that 10 times and scatter them across your scene - for each of these objects you may set individual values for damageAmount, hitPoints and moveRate.

    So basically your approach was right, just the execution has room for improvement. ;-)

    And to anticipate: Next step would then be to have a look at polymorphism. ;-)
    http://msdn.microsoft.com/en-us/library/ms173152(v=vs.80).aspx
     
    ecof, DonPuno, zs8861 and 1 other person like this.
  3. novashot

    novashot

    Joined:
    Dec 12, 2009
    Posts:
    373
    Might just be me, but I'd go one step further and separate those things into different scripts.

    For me I keep Health a script to itself. That way I can use the same health system for all things that require health. My player and my enemies use the same heath-care systems... and possibly even destructible objects. I did a whole spiel on it here: http://forum.unity3d.com/threads/noob-series-one-help-with-health-system.244980/#post-1621402

    All of your enemies will require an animator component anyway to play their own animations. You could, i suspose, make an animation script with a list of possible animations with play and stop handlers that could be callable from your enemy script... or it might just be easier to grab a reference to the component in the enemy script and call animations directly when needed.

    Player Health is as easy as:
    http://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html or
    http://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html and grab a health script.

    Movement speed is one thing I probably have in a base enemy class myself.
     
  4. Rxanadu

    Rxanadu

    Joined:
    May 24, 2012
    Posts:
    50
    It worked! Thank you so much for explaining this for me.

    After switching some things around (such as removing the "e" scope from "rigidBody2D.AddForce(e.moveRate);" since the variable doesn't exist), it seems to be working. I'd always thought inheritance meant you needed to set up constructors to use it. I rarely use basic methods like abstract classes when programming in Unity, since the API tends to have tons of alternatives for such methods.

    I'll have to look into using these methods - and hopefully polymorphism - in my later projects to get more used to the tactics in the future.
     
  5. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    833
     
  6. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    833
    This information was absolute gold! I was wondering how to go about setting up enemy classes and such. :)
     
  7. DonPuno

    DonPuno

    Joined:
    Dec 22, 2020
    Posts:
    57
    Many many thanks for this valuable post!
    It helped me a lot!