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

Inheritance Issue

Discussion in 'Scripting' started by jakejolli, Feb 20, 2015.

  1. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    So I'm having an issue based on the design discussed here.

    I'll reiterate them here.

    I have 2 hierarchies of classes as follows:

    ActionManager : Monobehavior
    PlayerActionManager : ActionManager
    EnemyActionManager : ActionManager

    and

    StatsManager : MonoBehavior
    PlayerStats : StatManager
    EnemyStats : StatManager

    My ActionManager class (which is abstract) has a stats object of type StatManager. Herein lies the problem.

    In PlayerActionManager, I want it to be an instance of PlayerStats, and in EnemyActionManager, I want it to be an instance of EnemyStats.

    My first question is: Is there a way to tell a derived class that an object inherited from its base class should actually be a sub-type of the object it inherited. For example, can I tell PlayerActionManager that its stats object (type StatManager in ActionManager) must be of type PlayerStats?

    To be clear on what I'm trying to do:

    StatManager has variables and objects common to both PlayerStats and EnemyStats, but each of the child classes add new variables. One of those new variables in PlayerStats is wpnDam.

    As I've said, stats is of type StatManager, which doesn't have a wpnDam variable, so I'm unable to access wpnDam from inside PlayerActionManager.

    Code (CSharp):
    1. stats.dam = Randomizer.randomize(stats.wpnDam / stats.wpnDamMod, stats.wpnDam) + stats.str + Randomizer.randomize(stats.str / stats.strMod, stats.str);
    I get a hint that there is no definition for wpnDam in StatsManager (which, of course, is true).

    I would just define stats in each sub-class, but I have a method TakeDamage in ActionManager which needs access to it, because both the player and enemies take damage in the same way.

    I appreciate any help.

    Thanks.
     
    Last edited: Feb 20, 2015
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Why not just have two variables, one of type PlayerStats and the other of type EnemyStats?

    The other alternative is to cast. Something like this

    Code (CSharp):
    1. EnemyStats enemyStats = (EnemyStats)statsManager;
     
  3. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,613
    Make ActionManager generic: class ActionManager<T> : MonoBehaviour where T : StatsManager.

    Then instead of having a variable in ActionManager of type StatsManager, have it of type T.

    Then have PlayerActionManager derive from ActionManager<PlayerStats>.
     
  4. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    Thanks, I think I might be able to make that work.
     
  5. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    This looks like something I would like to use, but I would like to understand it a little better before doing so.

    What exactly does declaring PlayerActionManager as follows do?
    Code (CSharp):
    1. class PlayerActionManager : ActionManager<PlayerStats>{
    2. ...
    3. }
    Does that mean I'm replacing every occurrence of the symbol T in the class with whatever I plug in in the subclass (in this case, PlayerStats)?

    If so, does this mean I can specify multiple generic types using something like

    Code (CSharp):
    1. ActionManager<T, U> : MonoBehaviour
    And specify multiple types in subclasses just by replacing T and U with the types I want to use?

    Also, how would I rewrite my TakeDamage method to use the generic type? Or would I even have to?

    Code (CSharp):
    1. public void TakeDamage(float damageIn){
    2.         stats.def = stats.armorDef + Randomizer.randomize(stats.con / stats.conMod, stats.con);
    3.        
    4.         float totalDamage = damageIn - stats.def;
    5.         totalDamage = (totalDamage >= 1) ? totalDamage : 1;
    6.        
    7.         Debug.Log ("takeDamage: def is " + stats.def + " damageIn is " + damageIn + " totalDamage is " + totalDamage);
    8.         stats.hp -= totalDamage;
    9.         if (stats.hp <= 0) {
    10.             GetComponentInChildren<Animator>().SetTrigger("Die");
    11.             Destroy(gameObject.rigidbody, 0);
    12.             Destroy(gameObject.collider);
    13.         }
    14.         Debug.Log("takeDamage: health is " + stats.hp);
    15.     }
     
    Last edited: Feb 20, 2015
  6. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,613
    Broadly speaking, yes. It's not a straight 'text substitution,' but T is a placeholder for 'some type that obeys the constraints' (in this case 'is derived from StatsManager') and so can be used in any situation where you'd use a type, like declaring a variable.

    Yes.

    If your TakeDamage method uses ONLY members that are defined on the base StatsManager type, and you've guaranteed that T is going to be StatsManager (or derived from it), then the compiler is guaranteed that whatever type you actually end up passing in is going to be suitable. So you may well not need to change your TakeDamage method at all.
     
  7. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    I've changed the code to use T instead of StatManager, and now PlayerActionManager and EnemyActionManager are happy, but....

    Without changing the code, the code analyzer doesn't like me trying to access any members in StatManager using stats.someMember. It says that T has no member someMember. Also, Unity won't compile it.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public abstract class ActionManager<T> : MonoBehaviour {
    5.  
    6.     public T stats; //used to be public StatManager stats
    7.  
    8.     public abstract void Attack();
    9.  
    10.     public abstract void Die();
    11.  
    12.     public void TakeDamage(float damageIn){
    13.         stats.def = stats.armorDef + Randomizer.randomize(stats.con / stats.conMod, stats.con);
    14.      
    15.         float totalDamage = damageIn - stats.def;
    16.         totalDamage = (totalDamage >= 1) ? totalDamage : 1;
    17.      
    18.         Debug.Log ("takeDamage: def is " + stats.def + " damageIn is " + damageIn + " totalDamage is " + totalDamage);
    19.         stats.hp -= totalDamage;
    20.         if (stats.hp <= 0) {
    21.             GetComponentInChildren<Animator>().SetTrigger("Die");
    22.             Destroy(gameObject.rigidbody, 0);
    23.             Destroy(gameObject.collider);
    24.         }
    25.         Debug.Log("takeDamage: health is " + stats.hp);
    26.     }
    27. }
    How can I solve this?

    P.S. Note that TakeDamage is defined in ActionManager
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    constrain T

    Code (csharp):
    1.  
    2. public abstract class ActionManager<T> : MonoBehaviour where T : StatsManager
    3.  
     
  9. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    Beautiful. I think this is exactly what I was trying to do.

    By putting on this constraint, am I also constraining subclasses to use only StatManager or its subtypes in place of T?
     
  10. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,613
    You're constraining anyone who uses the ActionManager class, and provides a type to it, such that the type they provide inherits from (or is) StatsManager. It doesn't matter whether that usage is as a base class, as a member variable, or whatever you like.
     
  11. jakejolli

    jakejolli

    Joined:
    Mar 22, 2014
    Posts:
    54
    Wow, this is going to be super useful, and would have taken me forever to find out on my own.

    Thanks guys. This community is great.