This is what polymorphism is for.
C# offers up polymorphism in 2 distinct ways:
Class Inheritance -
you have a base class that all like types inherit from. Such as a ‘Weapon’ class that all weapons inherit from. The base class will have all the properties that are intrinsic to all weapons, and the sub classes just implement the how stuff works. (there’s a function called ‘Activate’, but a sword plays a swinging animation on Activate, where as a gun shoots a projectile on strike… which in turn creates another weapon, a bullet, which will too ‘Activate’ when it hits something).
Interface Implementation -
With an ‘interface’ you define the… well, interface (names & types of methods & properties that an object should have). Then your class can implement said interface. Such as a ‘IWeapon’ interface that all weapons implement.
Remember you can inherit interfaces from interfaces adding more to the contract. So you could have IWeapon as well as IRangedWeapon that have properties specific to ranged weapons… like range.
I personally use a combination. I define an interface for my various types, but always have a base class to inherit from. This way the base class can act as boiler plate code so I don’t have to do ALL the implementation of the interface (like the properties) every time I implement it… instead I just inherit from the base class.
Here is an example where we have a IWeapon interface, and a Sword weapon.
//I like all my component based interfaces to have a generalized IComponent interface it inherits from
public interface IComponent
{
Component component { get; set; }
GameObject gameObject { get; }
Transform transform { get; }
bool enabled { get; set; }
}
public interface IWeapon : IComponent
{
float Damage { get; set; }
void Activate();
}
public abstract class AbstractWeapon : MonoBehaviour, IWeapon
{
//note, MonoBehaviour already implements most of the properties that IComponent require
[SerializeField()]
private float _damage;
public Component component { get { return this; } }
public float Damage
{
get { return _damage; }
set { _damage = value; }
}
public abstract void Activate();
}
public class Sword : AbstractWeapon
{
public override void Activate()
{
//do sword swing
}
}
BUT if say I have some class that needs to be a weapon, but I need to also inherit from some other class as well, I can instead implement that interface and tack on the implementation needed.
Lets say for instance we have a IProjectile interface, and projectiles aren’t always necessarily a weapon (maybe you can throw innocuous things…). But you need a projectile that is also a weapon, like a bullet.
public interface IProjectile : IComponent
{
float Range { get; }
void Launch();
}
public class AbstractProjectile : MonoBehaviour, IProjectile
{
[SerializeField()]
private float _range;
public Component component { get { return this; } }
public float Range
{
get { return _range; }
set { _range = value; }
}
public abstract void Launch();
}
public class Bullet : AbstractProjectile, IWeapon
{
[SerializeField()]
private float _damage;
public float Damage
{
get { return _damage; }
set { _damage = value; }
}
public override void Launch()
{
//shoot the bullet
}
public void Activate()
{
//bullet struck an enemy
}
}
Then of course if you have various types of swords that vary only by the amount of damage they wield. They all get the Sword component, BUT you just set the damage value to what it needs to be.
You still need the prefabs (or some type of factory class), because things like the models that represent the sword/bullet/whatnot, need to be set up. And that isn’t a property on a class… those are usually sets of gameobjects and components.
So now you can set up a GameObject with a Sword component that has the graphics for a sword as a child of it, and configured to be a ‘Great Sword’ or something.
Now if you want to have some stats to distinguish certain swords by say… rarity. Ok, have a ‘Rarity’ property on the Sword class. Or even the IWeapon interface if all weapons are rare… or shit, if ALL items are rare, have an IRareItem interface that IWeapon also implements, that defines the rarity property.
public interface IRareItem : IComponent
{
float Rarity { get; set; }
}
public abstract class AbstractWeapon : MonoBehaviour, IWeapon, IRareItem
{
//note, MonoBehaviour already implements most of the properties that IComponent require
[SerializeField()]
private float _damage;
[SerializeField()]
private float _rarity;
public Component component { get { return this; } }
public float Damage
{
get { return _damage; }
set { _damage = value; }
}
public float Rarity
{
get { return _rarity; }
set { _rarity = value; }
}
public abstract void Activate();
}
NOW, the only downside to all this interface business is that Unity 4 does not support interfaces when calling ‘GetComponent’.
Unity 5 DOES have support thankfully!
In Unity4, I just create an extension method for it:
public static class ComponentUtil
{
public static T GetFirstLikeComponent<T>(this GameObject obj)
{
if (obj == null) return default(T);
foreach (object comp in obj.GetComponents<Component>())
{
if (comp is T) return (T)comp;
}
return default(T);
}
}
You can actually find a lot of this stuff as I use them in my spacepuppy framework.
This is my ComponentUtil:
Or this is my IComponent: