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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Confused about Inheritance within Movodevelop

Discussion in 'Scripting' started by RoryScanlan, Sep 9, 2015.

  1. RoryScanlan

    RoryScanlan

    Joined:
    Jan 25, 2014
    Posts:
    139
    Hi,

    Sooo I think I understand the basic concept of OOP but maybe I don't (Im a new coder :p) also inheritance seems to be dealt with slightly differently within Movodevelop, so can someone please look over this and give me some pointers as to whether i'm doing it correctly :p

    I understand that you can inherit using (Class1 : Class2), but if I want Class to inherit monodevelop, then this is not the correct procedure?

    Basically im tryin to create multiple enemies, some of which could be different 'types'.
    I have therefore created a 'EnemyBaseClass' which I will add to all enemy prefrabs.
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class EnemyBaseClass : MonoBehaviour {
    6.  
    7.     #region Variables
    8.     //Navmesh Variables
    9.     private NavMeshAgent agent;
    10.  
    11.     //Generic Variables
    12.     private Vector3 spawnLocation;
    13.     private bool walking = false;
    14.     private int enemyHealth, enemyId;
    15.     public bool Walking{ get { return walking; } }
    16.     public int EnemyHealth{get{return enemyHealth;} set{enemyHealth = value;}}
    17.     public int EnemyId{ get { return enemyId; } set { enemyId = value; } }
    18.  
    19.     //Attack Variables
    20.     private bool attacking = false;
    21.     private float enemyAttackSpeed, enemyAttackDamage, enemyAttackRange, enemySightRange, closestPlayerDistance;
    22.     private GameObject closestEnemy;
    23.     public float EnemyAttackSpeed{get{return enemyAttackSpeed;} set{enemyAttackSpeed = value;}}
    24.     public float EnemyAttackDamage {get{return enemyAttackDamage;} set{enemyAttackDamage = value;}}
    25.     public float EnemyAttackRange{get{return enemyAttackRange;} set{enemyAttackRange = value;}}
    26.     public float EnemySightRange{get{return enemySightRange;} set{enemySightRange = value;}}
    27.     public float ClosestPlayerDistance{get{return closestPlayerDistance;} set{closestPlayerDistance = value;}}
    28.     public GameObject ClosestEnemy{get{return closestEnemy;} set{closestEnemy = value;}}
    29.     #endregion
    30.  
    31.     void Start()
    32.     {
    33.         agent = GetComponent<NavMeshAgent> ();
    34.         spawnLocation = transform.position;
    35.         Debug.Log (spawnLocation);
    36.     }
    37.  
    38.     void Update()
    39.     {
    40.         ClosestEnemy = FindClosestEnemy ();
    41.         ClosestPlayerDistance = Vector3.Distance (transform.position, ClosestEnemy.transform.position);
    42.     }
    43.  
    44.     public virtual void ChasePlayer()
    45.     {
    46.         if (closestEnemy != null) {
    47.             NavMeshHit hit;
    48.             if (NavMesh.SamplePosition (ClosestEnemy.transform.position, out hit, 10, NavMesh.AllAreas)) {
    49.                 if(Vector3.Distance(transform.position, hit.position) > enemyAttackRange)
    50.                 {
    51.                     agent.SetDestination (hit.position);
    52.                     agent.Resume();
    53.                 }else
    54.                 if(Vector3.Distance(transform.position, hit.position) <= enemyAttackRange && attacking == false)
    55.                 {
    56.                     agent.Stop();
    57.                     StartCoroutine("AttackPlayer");
    58.                 }
    59.             }
    60.         }
    61.     }
    62.  
    63.     public virtual IEnumerator AttackPlayer()
    64.     {
    65.         attacking = true;
    66.         Debug.Log ("Starting Attack");
    67.         yield return new WaitForSeconds (EnemyAttackSpeed);
    68.         Debug.Log ("Ending Attack");
    69.         attacking = false;
    70.     }
    71.  
    72.     public virtual void MoveToRandomVector()
    73.     {
    74.         walking = true;
    75.         Vector2 randomVect = randomVector2();
    76.         Vector3 randomVect3 = Vector3.zero;
    77.         randomVect3.x = randomVect.x;
    78.         randomVect3.z = randomVect.y;
    79.         randomVect3.y = Terrain.activeTerrain.SampleHeight (randomVect3) + (transform.localScale.y);
    80.         StartCoroutine(WalkAndIdle(randomVect3));
    81.     }
    82.  
    83.     IEnumerator WalkAndIdle(Vector3 destination)
    84.     {
    85.         agent.SetDestination (destination);
    86.         while (Vector3.Distance(transform.position, destination) > 5)
    87.         {
    88.             yield return new WaitForSeconds(0.1f);
    89.         }
    90.         StartCoroutine ("Idle");
    91.     }
    92.  
    93.     IEnumerator Idle()
    94.     {
    95.         agent.Stop ();
    96.         agent.ResetPath ();
    97.         agent.avoidancePriority = 1;
    98.         float randomIdle = Random.Range (10, 50);
    99.         for (int i = 0; i < randomIdle; i++) {
    100.             yield return new WaitForSeconds(0.1f);
    101.             Debug.Log("Idling");
    102.         }
    103.         agent.avoidancePriority = 2;
    104.         walking = false;
    105.     }
    106.    
    107.     Vector2 randomVector2()
    108.     {
    109.         int randomChoice = Random.Range (0, 4);
    110.         Vector2 randomVect = Vector3.zero;
    111.         switch (randomChoice)
    112.         {
    113.         case 0:
    114.             float randomValue1 = Random.Range (spawnLocation.x, spawnLocation.x + 25);
    115.             float randomValue2 = Random.Range (spawnLocation.z, spawnLocation.z + 25);
    116.             randomVect = new Vector2(randomValue1, randomValue2);
    117.             return randomVect;
    118.        
    119.         case 1:
    120.             float randomValue3 = Random.Range (spawnLocation.x, spawnLocation.x - 25);
    121.             float randomValue4 = Random.Range (spawnLocation.z, spawnLocation.z - 25);
    122.             randomVect = new Vector2(randomValue3, randomValue4);
    123.             return randomVect;
    124.        
    125.         case 2:
    126.             float randomValue5 = Random.Range (spawnLocation.x, spawnLocation.x + 25);
    127.             float randomValue6 = Random.Range (spawnLocation.z, spawnLocation.z - 25);
    128.             randomVect = new Vector2(randomValue5, randomValue6);
    129.             return randomVect;
    130.        
    131.         case 3:
    132.             float randomValue7 = Random.Range (spawnLocation.x, spawnLocation.x - 25);
    133.             float randomValue8 = Random.Range (spawnLocation.z, spawnLocation.z + 25);
    134.             randomVect = new Vector2(randomValue7, randomValue8);
    135.             return randomVect;
    136.         } return randomVect;
    137.     }
    138.  
    139.     //Find closest enemy
    140.     GameObject FindClosestEnemy() {
    141.         GameObject[] gos;
    142.         gos = GameObject.FindGameObjectsWithTag("Player");
    143.         GameObject closest = null;
    144.         float distance = Mathf.Infinity;
    145.         Vector3 position = transform.position;
    146.         foreach (GameObject enemy in gos) {
    147.             Vector3 diff = enemy.transform.position - position;
    148.             float curDistance = diff.sqrMagnitude;
    149.             if (curDistance < distance) {
    150.                 closest = enemy;
    151.                 distance = curDistance;
    152.             }
    153.         }
    154.         return closest;
    155.     }
    156. }
    157.  
    I then have another 'EnemyMelee_Class' that will be added to the enemy prefrabs along side the EnemyBaseClass.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Enemy_Melee : MonoBehaviour {
    5.  
    6.     EnemyBaseClass enemyBaseClass;
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.         //Inherit the EnemyBaseClass and set the variables
    11.         enemyBaseClass = GetComponent<EnemyBaseClass> ();
    12.         enemyBaseClass.EnemyHealth = 100;
    13.         enemyBaseClass.EnemyAttackSpeed = 1;
    14.         enemyBaseClass.EnemyAttackDamage = 10;
    15.         enemyBaseClass.EnemyAttackRange = 2.5f;
    16.         enemyBaseClass.EnemySightRange = 25;
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update () {
    21.         if (enemyBaseClass.ClosestEnemy != null)
    22.         {
    23.             if (enemyBaseClass.ClosestPlayerDistance < enemyBaseClass.EnemySightRange)
    24.             {
    25.                 enemyBaseClass.ChasePlayer ();
    26.             } else
    27.             {
    28.                 if(enemyBaseClass.Walking == false)
    29.                 {
    30.                     enemyBaseClass.MoveToRandomVector ();
    31.                 }
    32.             }
    33.         }
    34.     }
    35. }
    36.  
    Is the correct procedure or even close? I'm sorry if this question has been asked 100 times before but there are many conflicting answers on the internet at the moment, some if which use virtual classes and the new Class() method, which i thaught you should not do within Monodevelop
     
    Last edited: Sep 9, 2015
  2. justoon

    justoon

    Joined:
    Nov 7, 2013
    Posts:
    9
    Inheritance in Unity works just like any other C# implementation but for one major difference - classes that inherit from MonoBehaviour do not have or use explicit constructors. Everything is managed by the Unity engine and so you have slightly different concepts such as Awake(), Start(), OnDestroy() that control the lifecycle of your GameObjects.

    With respect to your question, you will want to make 2 adjustments:
    1. In your BaseEnemyClass, don't mark fields that will be used by subclasses as private, otherwise they will be inaccessible. You will want to mark them as protected.

    2. Your subclasses need to be declared like so:
    Code (csharp):
    1.  
    2. public class Enemy_Melee : EnemyBaseClass
    3.  
    Now the Enemy_Melee class will inherit from the EnemyBaseClass and will also be a MonoBehaviour due to the chain of inheritance. Everything you declare (methods, variables) in EnemyBaseClass that is not private will now be accessible in your subclass.

    So, in order to access your parent class in Enemy_Melee, you will use the following convention:
    Code (csharp):
    1.  
    2. void Start()
    3. {
    4.   base.EnemyHealth = 100;
    5. }
    6.  
    Another thing to consider - let's say you want the standard implementation of an Enemy's walk behavior to play a specific animation and move at a set speed, but you want your Melee Enemy (or another specialization) to do something different, you will want to understand the concept of virtual and override methods.

    Example: the base enemy walks at a standard pace, but the melee enemy walks faster by default:

    BaseEnemyClass:
    Code (csharp):
    1.  
    2. public virtual void Walk()
    3. {
    4.   animator.SetSpeed(1f);
    5.   rigidBody.AddForce(1f);
    6. }
    7.  
    then Enemy_Melee:
    Code (csharp):
    1.  
    2. public override void Walk()
    3. {
    4.   animator.SetSpeed(1.5f);
    5.   rigidBody.AddForce(1.5f);
    6. }
    7.  
    This way, when you call Walk() on your enemy class, you don't have to know anything about the details, the subclass hierarchy will figure out the right method to call.
     
    RoryScanlan and ThermalFusion like this.
  3. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    One thing to add to what @justoon said is that when you inherit from the base enemy, you do not put both the base enemy and the melee enemy on the game object, only the latter.
     
    RoryScanlan likes this.
  4. RoryScanlan

    RoryScanlan

    Joined:
    Jan 25, 2014
    Posts:
    139
    Thank you both for a very clear,comprehensive and help-full answer!! I will make the adjustments to my game and report back :)
     
    Last edited: Sep 9, 2015
  5. RoryScanlan

    RoryScanlan

    Joined:
    Jan 25, 2014
    Posts:
    139
    Ok Here is the changes i made, everything seems to work :) I now just add the EnemyMelee_Class to the gameObject and then instantiate away :)

    For some reason though i could not get anything to fire within Update() on the EnemyBaseClass so i had to rearrange a few things. Anyway's here is the code, any feedback is welcome :)

    EnemyBaseClass
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class EnemyBaseClass : MonoBehaviour {
    6.  
    7.     #region Variables
    8.     //Navmesh Variables
    9.     protected NavMeshAgent agent;
    10.  
    11.     //Generic Variables
    12.     protected Vector3 spawnLocation;
    13.     protected bool walking = false, searchingForPlayer = false;
    14.     protected float enemyHealth;
    15.     protected int enemyId;
    16.  
    17.     //Attack Variables
    18.     private bool attacking = false;
    19.     protected float enemyAttackSpeed, enemyAttackDamage, enemyAttackRange, enemySightRange, closestPlayerDistance;
    20.     protected GameObject closestEnemy;
    21.  
    22.     //Drop Variables
    23.     protected int[] dropableLootID;
    24.     protected int[] dropWeights;
    25.     private Item dropLoot;
    26.     #endregion
    27.  
    28.     public virtual void ChasePlayer()
    29.     {
    30.         if (closestEnemy != null) {
    31.             Debug.Log ("ChasePLayerCAlled");
    32.             NavMeshHit hit;
    33.             if (NavMesh.SamplePosition (closestEnemy.transform.position, out hit, 10, NavMesh.AllAreas)) {
    34.                 if(Vector3.Distance(transform.position, hit.position) > enemyAttackRange)
    35.                 {
    36.                     agent.SetDestination (hit.position);
    37.                     agent.Resume();
    38.                 }else
    39.                 if(Vector3.Distance(transform.position, hit.position) <= enemyAttackRange && attacking == false)
    40.                 {
    41.                     agent.Stop();
    42.                     StartCoroutine("AttackPlayer");
    43.                 }
    44.             }
    45.         }
    46.     }
    47.  
    48.     public virtual IEnumerator AttackPlayer()
    49.     {
    50.         attacking = true;
    51.         Debug.Log ("Starting Attack");
    52.         yield return new WaitForSeconds (enemyAttackSpeed);
    53.         Debug.Log ("Ending Attack");
    54.         attacking = false;
    55.     }
    56.  
    57.     public virtual void AttackMe(float damage)
    58.     {
    59.         enemyHealth -= damage;
    60.     }
    61.  
    62.     public virtual void MoveToRandomVector()
    63.     {
    64.         walking = true;
    65.         Vector2 randomVect = randomVector2();
    66.         Vector3 randomVect3 = Vector3.zero;
    67.         randomVect3.x = randomVect.x;
    68.         randomVect3.z = randomVect.y;
    69.         randomVect3.y = Terrain.activeTerrain.SampleHeight (randomVect3) + (transform.localScale.y);
    70.         StartCoroutine(WalkAndIdle(randomVect3));
    71.     }
    72.  
    73.     IEnumerator WalkAndIdle(Vector3 destination)
    74.     {
    75.         agent.SetDestination (destination);
    76.         while (Vector3.Distance(transform.position, destination) > 5)
    77.         {
    78.             yield return new WaitForSeconds(0.1f);
    79.         }
    80.         StartCoroutine ("Idle");
    81.     }
    82.  
    83.     IEnumerator Idle()
    84.     {
    85.         agent.Stop ();
    86.         agent.ResetPath ();
    87.         agent.avoidancePriority = 1;
    88.         float randomIdle = Random.Range (10, 50);
    89.         for (int i = 0; i < randomIdle; i++) {
    90.             yield return new WaitForSeconds(0.1f);
    91.             Debug.Log("Idling");
    92.         }
    93.         agent.avoidancePriority = 2;
    94.         walking = false;
    95.     }
    96.      
    97.     Vector2 randomVector2()
    98.     {
    99.         int randomChoice = Random.Range (0, 4);
    100.         Vector2 randomVect = Vector3.zero;
    101.         switch (randomChoice)
    102.         {
    103.         case 0:
    104.             float randomValue1 = Random.Range (spawnLocation.x, spawnLocation.x + 25);
    105.             float randomValue2 = Random.Range (spawnLocation.z, spawnLocation.z + 25);
    106.             randomVect = new Vector2(randomValue1, randomValue2);
    107.             return randomVect;
    108.          
    109.         case 1:
    110.             float randomValue3 = Random.Range (spawnLocation.x, spawnLocation.x - 25);
    111.             float randomValue4 = Random.Range (spawnLocation.z, spawnLocation.z - 25);
    112.             randomVect = new Vector2(randomValue3, randomValue4);
    113.             return randomVect;
    114.          
    115.         case 2:
    116.             float randomValue5 = Random.Range (spawnLocation.x, spawnLocation.x + 25);
    117.             float randomValue6 = Random.Range (spawnLocation.z, spawnLocation.z - 25);
    118.             randomVect = new Vector2(randomValue5, randomValue6);
    119.             return randomVect;
    120.          
    121.         case 3:
    122.             float randomValue7 = Random.Range (spawnLocation.x, spawnLocation.x - 25);
    123.             float randomValue8 = Random.Range (spawnLocation.z, spawnLocation.z + 25);
    124.             randomVect = new Vector2(randomValue7, randomValue8);
    125.             return randomVect;
    126.         } return randomVect;
    127.     }
    128.  
    129.     public virtual void dropItemsKillEnemy()
    130.     {
    131.         int totalWeights = 0;
    132.         int dropId = 0;
    133.         for (int i = 0; i < dropableLootID.Length; i++)
    134.         {
    135.             totalWeights += dropWeights[i];
    136.         }
    137.         int randomValue = Random.Range (0, totalWeights);
    138.         for (int i = 0; i < dropableLootID.Length; i++)
    139.         {
    140.             if(randomValue < dropWeights[i])
    141.             {
    142.                 dropId = i;
    143.             }
    144.         }
    145.  
    146.         Item_Database itemDataBase = GameObject.FindGameObjectWithTag ("ItemDataBase").GetComponent<Item_Database> ();
    147.         dropLoot = itemDataBase.ItemDatabase_List [dropId];
    148.         dropLoot.DropMe (transform.position, transform.localRotation);
    149.         Destroy (gameObject);
    150.     }
    151.  
    152.     //Find closest enemy
    153.     public virtual IEnumerator FindClosestEnemy() {
    154.         searchingForPlayer = true;
    155.         GameObject[] gos;
    156.         gos = GameObject.FindGameObjectsWithTag("Player");
    157.         GameObject closest = null;
    158.         float distance = Mathf.Infinity;
    159.         Vector3 position = transform.position;
    160.         foreach (GameObject enemy in gos) {
    161.             Vector3 diff = enemy.transform.position - position;
    162.             float curDistance = diff.sqrMagnitude;
    163.             if (curDistance < distance) {
    164.                 closest = enemy;
    165.                 distance = curDistance;
    166.             }
    167.             yield return new WaitForSeconds(0.1f);
    168.         }
    169.         closestEnemy = closest;
    170.         searchingForPlayer = false;
    171.     }
    172. }
    173.  
    174.  
    and the EnemyMelee_Class
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Enemy_Melee : EnemyBaseClass {
    6.  
    7.  
    8.     public int[] DropableLootId;
    9.     public int[] Dropweights;
    10.  
    11.     // Use this for initialization
    12.     void Start () {
    13.  
    14.         base.agent = gameObject.GetComponent<NavMeshAgent> ();
    15.         base.spawnLocation = transform.position;
    16.         base.enemyHealth = 100;
    17.         base.enemyAttackSpeed = 1;
    18.         base.enemyAttackDamage = 10;
    19.         base.enemyAttackRange = 2.5f;
    20.         base.enemySightRange = 25;
    21.         base.dropableLootID = DropableLootId;
    22.         base.dropWeights = Dropweights;
    23.         Debug.Log (base.enemyHealth);
    24.     }
    25.  
    26.     // Update is called once per frame
    27.     void Update () {
    28.         if(base.searchingForPlayer == false)
    29.         {
    30.             base.StartCoroutine("FindClosestEnemy");
    31.             Debug.Log (base.closestEnemy);
    32.         }
    33.  
    34.         if (base.closestEnemy != null)
    35.         {
    36.             base.closestPlayerDistance = Vector3.Distance (transform.position, closestEnemy.transform.position);
    37.             if (base.closestPlayerDistance < base.enemySightRange)
    38.             {
    39.                 base.ChasePlayer ();
    40.             } else
    41.             {
    42.                 if(base.walking == false)
    43.                 {
    44.                     base.MoveToRandomVector ();
    45.                 }
    46.             }
    47.         }
    48.  
    49.         if (enemyHealth <= 0) {
    50.             base.dropItemsKillEnemy();
    51.         }
    52.     }
    53. }
    54.  
    55.  
     
    Last edited: Sep 9, 2015
  6. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    If you're relatively new to coding, you might want to check out Component based approach instead OOP. There are many topics about it, pros and cons of each approach, but for starters you could check out this thread, I hope it helps you:

    http://forum.unity3d.com/threads/trying-to-rewrite-scripts-and-implement-oop.350310/#post-2267558

    Also a bit offtopic, but you are declaring properties like:
    Code (CSharp):
    1. private float enemyAttackSpeed, enemyAttackDamage, enemyAttackRange, enemySightRange, closestPlayerDistance;
    2.     private GameObject closestEnemy;
    3.     public float EnemyAttackSpeed{get{return enemyAttackSpeed;} set{enemyAttackSpeed = value;}}
    4.     public float EnemyAttackDamage {get{return enemyAttackDamage;} set{enemyAttackDamage = value;}}
    5.     public float EnemyAttackRange{get{return enemyAttackRange;} set{enemyAttackRange = value;}}
    6.     public float EnemySightRange{get{return enemySightRange;} set{enemySightRange = value;}}
    7.     public float ClosestPlayerDistance{get{return closestPlayerDistance;} set{closestPlayerDistance = value;}}
    8.     public GameObject ClosestEnemy{get{return closestEnemy;} set{closestEnemy = value;}}
    It is enough to write:
    Code (CSharp):
    1. public float AttackSpeed { get; set; }
    Compiler will add a private var automatically so you don't have to write.

    Also, note that using properties is actually using functions under the hood. It basically looks like:
    Code (CSharp):
    1. private float attackSpeed;
    2. public float get_AttackSpeed()
    3. {
    4.      return attackSpeed;
    5. }
    6.  
    7. public void set_AttackSpeed(float value)
    8. {
    9.      attackSpeed = value;
    10. }
    That means in some performance bottleneck issues it might be wiser to access the variable directly instead calling functions. But then again, it all depends on your design in case you want to call another function in setter once you set new values.

    Good luck!
     
    LaneFox likes this.