Search Unity

Can I dynamically set public class type C#?

Discussion in 'Scripting' started by Julius.J, Sep 8, 2013.

  1. Julius.J

    Julius.J

    Joined:
    Jul 24, 2013
    Posts:
    33
    Hey All,

    I'm working with a game that I would like to utilize both the "is a" and "has a" setup scheme. I have a classes setup unit->abilities->movement and so on. I've set this up because I want all my units to have preset values when they are setup or upgraded all written in one script. Then I can just do

    Code (csharp):
    1. public Archer archer = new Archer();
    and have all its relevant data. This works fine but I don't know how to reference the class between generic scripts. I.E If I have a GenericMovement script I'd like to grab archer.Speed; . If I make say a UnitTypeArcher script that just does the above code I would need to make a ArcherMovement that specifically has reference to the script type if UnitTypeArcher. So is there a way that I can dynamically reference the class or script form GenericMovement so GenericMovement will work on all my movable units?

    Right now they only way I can figure out how to do it is to use Interfacing, but its kinda clunky.

    Code (csharp):
    1.  
    2. public class Tower : Abilities
    3. {
    4.     public const int COST = 20;
    5.    
    6.     public Tower ()
    7.     {
    8.         MaxHp = 200;
    9.         CurrentHp = MaxHp;
    10.     }
    11.    
    12. }
    13.  
    14. public class Archer : Movement
    15. {
    16.     public enum Upgrades
    17.     {
    18.         Archer,
    19.         StrongerArcher,
    20.         FireArcher,
    21.         BoomArcher
    22.     }
    23.    
    24.     public Archer ()
    25.     {
    26.         Upgrade(Upgrades.Archer);
    27.     }
    28.    
    29.     public void Upgrade (Upgrades upgrade)
    30.     {
    31.         switch (upgrade)
    32.         {
    33.             case Upgrades.Archer:
    34.                 MaxHp = 50;
    35.                 CurrentHp = MaxHp;
    36.                 Speed = 1.2f;
    37.                 AttSpeed = 1.0f;
    38.                 AddAbility(Ability.AbilityTypes.physical, Ability.AbilityKinds.offensive, 5);
    39.                 break;
    40.         }
    41.     }
    42. }
    43.  
     
  2. chronos78

    chronos78

    Joined:
    Dec 6, 2009
    Posts:
    154
    You could implement interfaces and then use abstract classes where generic functionality is required and derived classes for specific functionally.

    For example:
    Code (csharp):
    1.  
    2. /* Warning: The following code is completely of the top of my head and untested or checked for correctness in an IDE. Just trying to illustrate the concept. */
    3.  
    4. public interface IUnit
    5. {
    6.     float Speed { get; }
    7.     void Move(Vector3 direction);
    8.     void Upgrade(Upgrades upgrade)
    9. }
    10.  
    11. public abstract class Unit : IUnit
    12. {
    13.     public float Speed { get; set; }
    14.  
    15.     protected Transform Transform;
    16.    
    17.     public Unit(Transform transform)
    18.     {
    19.         Transform = transform;
    20.     }
    21.  
    22.     public virtual void Move(Vector3 direction)
    23.     {
    24.         Transform.position += direction * Speed;
    25.     }
    26.  
    27.     public abstract void Upgrade(Upgrades upgrade);
    28. }
    29.  
    30. public class Archer : Unit
    31. {
    32.     public Unit(Transform transform) : base(transform) {}
    33.  
    34.     public override void Move(Vector3 direction)
    35.     {
    36.         // Unit specific code goes here
    37.  
    38.         base(direction);
    39.     }
    40.  
    41.     public void Upgrade(Upgrades upgrade)
    42.     {
    43.         // Unit specific code goes here
    44.     }
    45. }
    46.  
    You could then store references of type IUnit and be sure that Move() and Upgrades() would always work no matter what the derived unit type ends up being.

    Code (csharp):
    1.  
    2.     IUnit archer = new Archer([Transform to your gameObject]);
    3.  
    This allows you to avoid to use any sort of switching or condition checking to ensure the right methods for the unit type are run.

    Hopefully this helps you point you in the right direction.
     
    Last edited: Sep 9, 2013
  3. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Bear in mind that you can only inherit from one base class, so where applicable perhaps you should prefer interfaces to abstract base classes. Interfaces cannot supply function bodies, but you can provide those using extension methods. Even so, you can't provide data - for that, the derived class needs to implement the interface member.

    I've found this can work really well though, using interfaces for things like "IDamageable", "ITargetable", and the like; so it's easy for something iterating over entities to find potential targets and check that they're not already destroyed.

    You should also consider the component style though - the way Unity works. Instead of deriving types for your fundamental game objects, use a standard type for the object but attach components that modify its behaviour. Your archer can have a movement component attached, with appropriate values, and other units will have different values or different components. This generally becomes non-polymorphic, though with the right component system design you can still make component meaningfully derive from each other.

    Be careful not to overengineer though - try to figure out what your game really needs, whether you really need the polymorphism, or whether it's complexity that you'd be better off without. It depends a lot on the scale of your game.
     
  4. Julius.J

    Julius.J

    Joined:
    Jul 24, 2013
    Posts:
    33
    Hey chronos78 gfoot,

    Thank you very much for the reply!

    The path you suggested is the direction I've been playing with. I haven't delved into the using abstract classes yet but it looks interesting. Right now I just have an interface that I attach to any script that I add new archer, new wall, ect to and then pass through the interface any interaction code I need to use.

    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3.  
    4. public interface IUnitInteraction
    5. {
    6.     void Damage (List<Ability> abilities);
    7.     bool IsAlive ();
    8. }
    9.  
    Then on my archer fsm:

    Code (csharp):
    1.  
    2.     public void Damage (List<Ability> abilities) {
    3.         archer.Damage(abilities);
    4.     }
    5.    
    6.     public bool IsAlive ()
    7.     {
    8.         return archer.IsAlive();
    9.     }
    10.  
    All this said I'm looking at moving away form the "is a" structure. In unityscript you can just set a var to a different class type based on and enum value so that's how I got away with it before. Then I just made a unitType class with the dynamic class and referenced it. Probably not the best way to do it. I'm looking at migrating to a more pure "has a" methodology now, but its just so nice to have unit setup centralized for game balancing.

    gfoot : My reference for "has a" is indeed the attach components ideology. I do like making components that are moderately versatile and then using them as such. The only thing that I struggle with is maintaining scope like you said. Its nice to have a basic classing system so I know all of my units are somewhat similar and I can pretty dynamically adjust them knowing there type. I just was hoping for a clean way to integrate the two. I could still have say a movement script, attack script ect but then set the movement speed turn speed ect via the unit class which I could modify form any script that has reference to it. For now I'm just doing it with interfacing and a fsm that can pass turn speed ect to the movement script rather then the movement script having a direct reference to the class itself.
     
    Last edited: Sep 9, 2013
  5. Julius.J

    Julius.J

    Joined:
    Jul 24, 2013
    Posts:
    33
    Hey Guys,

    I thought I should post my final solution to this issue!

    After having the issue in the back of my head for awhile I came up with a solution that I like. I'd love to hear any potential pitfalls in my logic, but it seems to work.

    The Solution

    First I make a unique MonoBehaviour class for each of my units I.E. ArcherManager that has the new Archer class instance
    Code (csharp):
    1. public Archer archer = new Archer();
    Then I use RequireComponent to populate the attached components that the archer unit will need. In my generic scripts say movement I create a public refrence to my movment class
    Code (csharp):
    1. public Movement movement;
    Since the Archer class extends movement I can set
    Code (csharp):
    1. unitMovement.movement = archer;
    from my manager. Bam now the movment script can get movment.Speed (I.E. archer.Speed) and if I change archer.Speed then movment.Speed will also change!

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [RequireComponent (typeof  (UnitMovement), typeof (ArcherBrain))]
    6. public class ArcherManager : MonoBehaviour
    7. {
    8.     public Archer archer = new Archer();
    9.     public UnitMovement unitMovement;
    10.     public ArcherBrain archerBrain;
    11.    
    12.     void Start ()
    13.     {
    14.         unitMovement = (UnitMovement)GetComponent(typeof(UnitMovement));
    15.         if(unitMovement != null) unitMovement.movement = archer;
    16.        
    17.         archerBrain = (ArcherBrain)GetComponent(typeof(ArcherBrain));
    18.         if(archerBrain != null) archerBrain.archer = archer;
    19.     }
    20. }
    21.  
    22.  
    And then in the generic movement script (wrote this for preview):

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class UnitMovement: MonoBehaviour {
    6.    
    7.     public Movement movement;
    8.     public int currentHp;
    9.    
    10.     // Use this for initialization
    11.     void Update ()
    12.     {
    13.         if (movement != null)
    14.         {
    15.             currentHp = movement.CurrentHp;
    16.         }
    17.     }
    18. }
    19.  
    CurrentHp is derived form Uint witch Movement extends, but you get the idea. From here I can then setup the ArcherManager to save and load just the things that change in the Archer class I.E. currentHP UpgaredType Target ect and the rest of the scripts can use that data to just startup and go!

    Again if I'm missing something obvious or there is a major potential pitfall please let me know!

    All this said it does turn my code a bit spaghetti, which I don't like.
     
    Last edited: Sep 10, 2013