When learning Unity you’re told that components should only do one thing and that they shouldn’t rely on each other. It’s a great advice but inheritance, like having broad scripts like human and an elf script derive from a humanoid script, goes against that. How can I balance this? I want to make use of the inheritance tool but then I’m not sure how I should go about having some components do one thing and some components like human or elf do bunch of things at once.
Inheritance doesn’t break the idea of the script doing one thing.
Note the script doing one thing is a generalized term. It’s honestly a very nebulous term and many people will give you different opinions about their rule of thumb for “one thing”… that is if they even consider it in the first place.
For me I sort of take it as “I should be able to describe this class in a sentence”.
What is ‘PlayerMovementMotor’? Well, it’s the script that handles player movement of course!
In the same respect this could be abused… what is the “GameLogic” script? Well it’s the class that handles all logic of the game of course! Of course you can see the flaw here though… it’s probably doing too much if you’re saying things like everything or all.
So yeah… an Elf script inheriting from Humanoid isn’t a big deal. Elf scripts describes the attributes of an Elf, of which some attributes are shared amongst all Humanoids.
public class Humanoid
{
public VisualSensor Eyes;
public MovementMotor Legs;
}
public class Elf
{
public RangeWeapon Bow;
}
public class Human
{
public MeleeWeapon Sword;
}
This is of course with out getting into the topic of “is inheritance actually needed?”
I mean really, what logic is going in an Elf script that isn’t in a Human script? And what logic is shared?
What overlaps about them?
Are those overlapping bits possibly better described as a class in and of themselves?
(without using conjunctives such as “and” or “or”)
This class determines when the user has clicked and then decides where the user clicked and calculates a path from the avatar’s current position to the new destination, and also highlights whatever the cursor is hovering over, and downloads music from the internet and also does my taxes! oh and a pony!
I love the idea of components, i strive my hardest to achieve that golden level where my objects are nothing but a sum of thier parts. I ultimately struggle, this may be that my ability or exerience is just not there yet.
In the example of human and elf i would strive to have components such as walk, jump, talk, punch, eat, die… etc etc. All these components group together to describe a humanoid without the need to have Humanoid class. In theory…
In reality though things like state and shared dependancies get in the way of this ideal. Take the example of a character controller; walk, run, jump, swim, climb and roll to name a few all depend on manipulating the same velocity and there has to be some awareness of the state. You cant jump if youre swimming for example. But shouldnt the swim and jump components work independant of each other?
Honestly i think the most important goal is getting your game built, and i dont think its a sin to use OO tools at your disposal to help you achieve this as long as youre not fighting against the editor.
Well yes. Inheritance and composition are opposed principles. (Sort of, not really, but kind of).
The trick with programming principles, and with life in general, is to strike a happy medium. Achieve balance in all things. And avoid extremism and excess.
Here is a good break down of why inheritance is bad and composition is good. Note that I have been too lazy to produce the other video, that explains the good use cases for inheritance.
Inheritance is really useful in a couple of cases. The first is when an entire group of classes need some common functionality. Inheriting from MonoBehaviour lets all classes hook into the Unity API. Lordofduct probably has a base class in his frame work that fits between MonoBehaviour and everything else, this allows him to tie into the Space Puppy frame work effectively.
The second use case is where Liskov substitution applies. If you have multiple components that do the same basic job, inheritance is a good idea. That way you can share the base class implementation and interface. (Note that you can also just share the interface only if desired.)
More formally, inheritance is good if:
- Multiple classes do the same job
- The classes do not do any other jobs
- The classes are designed to be interchangeable
- The classes share some basic implementation
A classic game development example is a gun in a FPS game. No matter what gun a player picks up, its going to have the same basic functionality. You put ammo in, you point it at something, you pull the trigger, and some effect happens. At the same time a gun never has any other use. It makes sense to have a base gun class that every other gun derives from.
Another example of where I use inheritance heavily is on steering behaviours. The engine component has the responsibility of moving the transform. And that is all it does. Each engine component is fully interchangeable. So it makes sense to inherit from a common engine base class.
Design patterns are tools: They have their times and places, and you may use more than one in a project. Using a screwdriver in a project doesn’t forbid use of a hammer. The idea is to fill up your toolbox and experiment with your tools so that you later know when it’s appropriate to use each one.
Also, no, Inheritance doesn’t invalidate any part of a class doing one specific job. It enforces it, used correctly. Rather than a humanoid script also attempting to account for circumstances where a humanoid might be an elf, you use inheritance to make sure that the humanoid class takes care of humanoid stuff, and the elf class takes care of elf stuff.
Inheritance vs. Composition is one of those programmer minefields I normally try to stay out of, but as far as I’m concerned, there’s situations where each one is favorable.
I was making a looseHeath() function in my humanoid base class when it hit me, humanoids aren’t the only thing that have health, breakable objects can also have health, so I wasn’t sure if I should keep the looseHealth() in my humanoid class AND have a health component for the rest (like objects) or just ditch the inheritance idea and go all out components.
After reading your comments about using both and not going extreme on either side, I guess what I should do is make components for things that I know can be re-used for multiple objects (like a health component) and when I create functions for base classes (like humanoid), make sure that the functions wouldn’t make sense on anything else but a humanoid (like a wave() function).
you might benefit from looking into interfaces…
LooseHealth probably shouldn’t be a member of humanoid. Life/health normally deserves its own component.
I’ve no logic behind this, it’s just the way things fall out in pretty much every game I’ve worked on.
I would have a “HealthMeter” class distinct from Humanoid.
My ‘Humanoid’ might have a field to reference the ‘HealthMeter’, maybe even attribute it to ‘RequireComponent(typeof(HealthMeter))’. But I wouldn’t put the ‘LoseHealth’ (loose is the antonym of tight) method directly on ‘Humanoid’. That’s going against your previous statement of a class doing one thing…
… that is unless you mean to say Humanoid ONLY deals with health. In which case, that’s a weird name for a class that controls health.
Give you an example.
This is the HealthMeter from my last game I made the other weekend for a gamejam:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.Scenario;
using com.spacepuppy.Utils;
using com.mansion.Entities.Weapons;
namespace com.mansion
{
public class HealthMeter : SPComponent
{
public enum StatusType
{
Healthy,
Injured,
Critical,
Dead
}
#region Fields
[SerializeField()]
private float _health;
[SerializeField()]
[Tooltip("0 or negative means infinite.")]
private float _maxHealth;
[SerializeField()]
[Range(0f, 1f)]
private float _injuredRatio = 0.7f;
[SerializeField()]
private float _criticalRatio = 0.35f;
[SerializeField()]
private bool _destroyEntityOnOutOfHealth;
[SerializeField()]
[Tooltip("Occurs on any strike that does not cause death.")]
private Trigger _onStrike;
[SerializeField()]
[Tooltip("Occurs when health reaches 0")]
private Trigger _onDeath;
#endregion
#region Properties
public float Health
{
get { return _health; }
set
{
this.SetHealth(value);
}
}
public float MaxHealth
{
get { return _maxHealth; }
set
{
_maxHealth = value;
if(_maxHealth > 0f && _maxHealth < _health)
{
_health = _maxHealth;
}
}
}
public bool DestroyEntityOnOutOfHealth
{
get { return _destroyEntityOnOutOfHealth; }
set { _destroyEntityOnOutOfHealth = value; }
}
public StatusType Status
{
get
{
if(_maxHealth <= 0f || float.IsInfinity(_maxHealth) || float.IsNaN(_maxHealth))
{
return (_health > 0f) ? StatusType.Healthy : StatusType.Dead;
}
else
{
var ratio = _health / _maxHealth;
if (ratio > _injuredRatio)
return StatusType.Healthy;
else if (ratio > _criticalRatio)
return StatusType.Injured;
else if (ratio > 0f)
return StatusType.Critical;
else
return StatusType.Dead;
}
}
}
public Trigger OnStrike
{
get { return _onStrike; }
}
public Trigger OnDeath
{
get { return _onDeath; }
}
#endregion
#region Methods
public void SetHealth(float health, bool signalDeath = false)
{
bool wasAlive = (_health > 0f);
if (_maxHealth > 0f)
_health = Mathf.Clamp(health, 0f, _maxHealth);
else
_health = Mathf.Max(health, 0f);
if (signalDeath && wasAlive && _health == 0f)
this.OnDie(null);
}
/// <summary>
/// Strike the health meter, returns true if died.
/// </summary>
/// <param name="wpn"></param>
/// <returns></returns>
public bool Strike(float damage)
{
if (_health <= 0f) return false;
_health = Mathf.Max(_health - damage, 0f);
if (_maxHealth > 0f && _health > _maxHealth)
{
_health = _maxHealth;
return false;
}
if (_health == 0f)
{
this.OnDie(null);
return true;
}
else if (_onStrike.Count > 0)
{
_onStrike.ActivateTrigger();
return false;
}
return false;
}
/// <summary>
/// Strike the health meter, returns true if died.
/// </summary>
/// <param name="wpn"></param>
/// <returns></returns>
public bool Strike(IWeapon wpn)
{
if (wpn == null) return false;
if (_health <= 0f) return false;
_health = Mathf.Max(_health - wpn.Damage, 0f);
if (_maxHealth > 0f && _health > _maxHealth)
{
_health = _maxHealth;
return false;
}
if (_health == 0f)
{
this.OnDie(wpn);
return true;
}
else if (_onStrike.Count > 0)
{
_onStrike.ActivateTrigger();
return false;
}
return false;
}
private void OnDie(object implementOfDeath)
{
if (_onDeath.Count > 0) _onDeath.ActivateTrigger(implementOfDeath);
if(_destroyEntityOnOutOfHealth)
{
var e = SPEntity.Pool.GetFromSource(this);
GameObjectUtil.KillEntity(e.gameObject);
}
}
#endregion
}
}
Straight forward class, all it does is health stuff.
Then a weapon striking an entity I do something like this:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.Scenario;
using com.spacepuppy.Utils;
namespace com.mansion.Entities.Weapons
{
public class GunWeapon : SPComponent, IWeapon
{
#region Fields
[SerializeField()]
private DiscreteFloat _ammoCount = 20;
[SerializeField()]
private int _clipSize = 6;
[SerializeField()]
private int _ammoInClip = 6;
[SerializeField()]
private float _damage = 10f;
[SerializeField()]
private Trigger _onFire;
#endregion
#region Properties
public int AmmoCount
{
get { return _ammoCount; }
set { _ammoCount = value; }
}
public int ClipSize
{
get { return _clipSize; }
set { _clipSize = value; }
}
public int AmmoInClip
{
get { return _ammoInClip; }
set { _ammoInClip = value; }
}
#endregion
#region Methods
public void Reload()
{
_ammoInClip = (int)Mathf.Min((float)_ammoCount, (float)_clipSize);
Debug.Log(_ammoInClip);
}
/// <summary>
/// Returns true if enemy died from strike
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public bool Fire(Vector3 dir)
{
_ammoCount--;
_ammoInClip--;
if(_onFire.Count > 0) _onFire.ActivateTrigger();
RaycastHit hit;
if (Physics.Raycast(this.transform.position, dir, out hit, float.PositiveInfinity, Constants.MASK_HITBOX, QueryTriggerInteraction.Collide))
{
var e = SPEntity.Pool.GetFromSource<IEntity>(hit.collider);
HealthMeter h;
if (e != null && e.GetComponent<HealthMeter>(out h))
{
return h.Strike(this);
}
}
return false;
}
#endregion
#region IWeapon Interface
public float Damage
{
get { return _damage; }
set { _damage = value; }
}
#endregion
}
}
Note entity is a script that all my complex entities get. It’s like saying “this gameobject and its children are a THING”.
The LoseHealth() method should definitely be on a separate Health component. The Humanoid class can require a Health component, then wrap and defer all methods to that health component.
class Health : MonoBehaviour
{
[SerializeField] private int health;
public void LoseHealth(int amount)
{
health -= amount;
// other stuff
}
}
[RequireComponent(typeof(Health))]
class Humanoid : MonoBehaviour
{
[SerializeField] private Health HealthComponent;
public void LoseHealth(int amount)
{
var health = GetComponent<HealthComponent>();
health.LoseHealth(amount);
}
}
It is ok for classes to be a composite of other classes. What we don’t want is for the Humanoid class to have its own implementation of LoseHealth(), and the Health component to have one as well. There should only be one implementation of a responsibility. All others should defer to those implementations.
I have. When I first looked into interfaces a long time ago I learned that interfaces are there to guarantee that a class that implements said interface has all the required functions and variables.
This is what I remember interfaces were for, however, now when I looked into interfaces again I found no one that used them for that purpose, instead I only found people using it to help organize their class, something that won’t really open up any new possibilities for me.
So, are interfaces just there to help you organize your code or can you also use them to mark classes with something like: IEdible og IDrivable which means you can do stuff like make an array of IDrivables and useful stuff like that? I just found it weird that when I found a person questioning the purpose of interfaces the only answer he got was that they’re just for organizing your script, which I found odd because I thought you could do much more with them than just organize your scripts.
That is correct. Things that implement interfaces are then “of that type”. You can do GetComponents() then iterate and slap() each one, regardless of the actual component class. The fact that they each implement ISlappable guarantees they have the slap() method, so it’s a way to relate classes and make a common API without sharing a parent class or even caring what the true class type is.
You could argue that any code design choice is for organization, but the real end goal is to organize them in such a way that they are as intuitive, readable, and self explanatory as possible.
Yes
Yes.
Both of these things are valid uses for interfaces. But both miss the core point of an interface. Which is to seperate your interface from your implementation.
If you implement and interface IDamagable on a concrete class Humanoid, then every other script can refer to the IDamagable instead of Humanoid.
You can use this pattern with multiple interfaces when behaviour is too interlocked to split into effective components.
Thank you guys so much for all of your help! You’ve helped me build up my C# confidence so I think I’m somewhat ready to start implementing your recommendations.
From everything I’ve read so far from you guys I’ve created my own little rule of thumb for components and inheritance.
Components: Should not rely on each other, only do one thing and be made in a sense that they could be used by any kind of object whether it be an elephant or a screwdriver.
Inheritance: Functions from an inherited class should only be useful for its own class and not to other objects. If the function could be useful to other objects it should rather be a component.
Now the only thing I need from you guys is a good rule of thumb for interfaces.
As much as an IDamagable interface would be useful it would be much wiser to have a health component that takes care of the damage instead because interfaces are bound to classes, so an object composed of independent components would make it hard for me to decide which one of those components should implement the IDamagable interface.
So, do you guys have a rule of thumb for interfaces that are similar to those I mentioned? I won’t take them too seriously, it’s just hard to dive in with nothing to go on.
Interfaces aren’t bound to classes…that’s the point of an Interface.
In fact, IDamageable is the exact name of an interface I use often, rather than having a “health” component. It lets me specify that an object will have certain functions a damageable object should have, while leaving the exact implementation of the logic to each class that implements IDamageable. I use Interfaces for circumstances where I want an object to be able to switch out behaviour on the fly with low overhead. In fact, one of the most common uses for an Interface is for Dependency Injection.
An Interface lets you separate a class’ logic from its implementation, and essentially use a that implementation like a class. This is more powerful than most people ever realize. For example, rather than keeping a List of things that ARE a certain thing, you can keep a List of things that DO certain things. Suddenly, you’re free from being aware of Object as a class, and only have to assume it fulfills the contract you’ve set forth in IObject. If it doesn’t, then you know where the source of the problem is.
Even better, because Unity’s Service Locator (GetComponent, FindObjectOfType, etc) methods work with Interfaces, you can use them WITH your component system for some serious flexibility.
Its also worth pointing out that you can have multiple components that implement the same interface on a single GameObject. This is useful for IDamagable. You might have one Component that reduces health. And another Component that plays a sound. And another Component that changes an Animation. All of this can be done with a single interface.
And since I’ve brought it up, you can also reverse this pattern with Event. Which is also super useful.
What I meant by “bound to classes” is that an interface has to be implemented by some class to work, it can’t be a component by itself. Let’s say I have an ICollectable interface, it has to be implemented by some class but what if my object that I want to have the ability to be collected is only composed of small components like Rotate and LifeTime. It wouldn’t make sense to have neither Rotate or LifeTime implement ICollectable, what do you do in those situations? Do you make a new component that implements ICollectable?
Hey, that’s actually pretty cool! That’ll definitively come in handy.