I am trying to use virtual and abstract methods to make my game architecture better.
I'm using C# and Unity for this example.
I use a ShipComponent as a base Class because I want all the child classes to do the same thing.
But sometimes I want a certain ShipComponent to do something else.
The code will make it a lot clearer:
ShipComponent.cs:
public abstract class ShipComponent : MonoBehaviour
{
[HideInInspector] public ShipControl shipControl;
public virtual void Init(ShipControl control)
{
this.shipControl = control;
}
public virtual void IsPlayer()
{
SetListeners();
}
public abstract void IsNotPlayer();
public abstract void ReEnable();
public abstract void SetListeners();
}
One of the many child classes that inherits from ShipComponent:
public class Rudder : ShipComponent
{
[Header("Settings")]
public Transform rudder;
[Header("Debug Info")]
[SerializeField] float rudderSpeed;
[SerializeField][Range(-45, 45)] int setRudderAngle = 0;
[SerializeField][Range(-45f, 45f)] float realRudderAngle = 0f;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
rudder = transform.GetChild(0).GetChild(4);
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
}
public override void IsNotPlayer()
{
PlayerShipControl.OnRudderChange -= SetRudder;
}
public override void ReEnable()
{
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnRudderChange -= SetRudder;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnRudderChange += SetRudder;
}
void OnDisable()
{
PlayerShipControl.OnRudderChange -= SetRudder;
StopAllCoroutines();
}
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods everytime I create a new ShipComponent class.
It seems messy and theres a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
Is there a way to dynamically set delegate listeners up?
So I could set them in the base class ShipComponent?
Instead of setting each component individually?
Another script that inherits from ShipComponent for completeness:
public class Guns : ShipComponent
{
IEnumerator mouseAimCycle;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
InitCannons();
SetListeners();
}
public override void ReEnable()
{
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
mouseAimCycle = AimCycle();
StartCoroutine(mouseAimCycle);
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnFireGuns -= TryFire;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnFireGuns += TryFire;
}
public override void IsNotPlayer()
{
StopCoroutine(mouseAimCycle);
PlayerShipControl.OnFireGuns -= TryFire;
}
void OnDisable()
{
PlayerShipControl.OnFireGuns -= TryFire;
StopAllCoroutines();
}
Calling the ShipComponent virtual and abstract methods:
public class ShipControl : MonoBehaviour
{
// Contains Ship + Cargo + Crew and a ref to this ShipControl
public ShipWrapper shipWrapper { get; private set; }
ShipComponent[] shipComponents;
// Gather all ShipComponents and Initialize them.
public void Start()
{
shipComponents = transform.GetComponents<ShipComponent>();
foreach (ShipComponent comp in shipComponents)
{
comp.Init(this);
}
}
// Call this to check if this is players current ship and set the components accordingly.
public void UpdateIsPlayer()
{
if (!shipWrapper.ship.IsPlayer)
foreach (ShipComponent component in shipComponents)
component.IsNotPlayer();
else
foreach (ShipComponent component in shipComponents)
component.IsPlayer();
}
And PlayerShipControl, which I use for input, broadcasting the input through delegates, and the theory is that only the players currently controlled ship will be listening for this input:
public class PlayerShipControl : MonoBehaviour
{
public static event Action<Transform> SetCamToPlayerShip;
public static event Action SetShipPanelUI;
public static event Action<bool> ToggleAnchorIcon, ToggleFlagIcon, ToggleAutofireIcon, ToggleBoatsIcon;
public static event Action OnFireGuns;
public static event Action<int> OnRudderChange;
public static event Action<int> OnSailStateChange;
public static event Action<bool> OnAllAnchorsCommand;
public static event Action<bool> OnAllBoatsCommand;
bool anchor, flag, autofire, boats;
ShipControl shipControl;
void Update()
{
if (Input.GetKeyUp(KeyCode.W)) // Raise Sails SailState++
{
OnSailStateChange?.Invoke(1);
}
if (Input.GetKeyUp(KeyCode.S)) // Furl Sails SailState--
{
OnSailStateChange?.Invoke(-1);
}
if (Input.GetKey(KeyCode.D))
{
OnRudderChange?.Invoke(1);
}
if (Input.GetKey(KeyCode.A))
{
OnRudderChange?.Invoke(-1);
}
if (Input.GetKeyDown(KeyCode.M))
{
OnRudderChange?.Invoke(0);
}
// Drop All Anchors
if (Input.GetKeyDown(KeyCode.V))
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Haul All Anchors
if (Input.GetKeyDown(KeyCode.H))
{
anchor = false;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Drop All Boats
if (Input.GetKeyDown(KeyCode.B))
{
boats = true;
ToggleBoatsIcon?.Invoke(boats);
OnAllBoatsCommand?.Invoke(boats);
}
// Take In All Boats
if (Input.GetKeyDown(KeyCode.U))
{
OnAllBoatsCommand?.Invoke(false);
// TO DO When all boats are back on deck, boatIcon + boatsBoolFlag should be turned off again.
}
if (Input.GetKeyDown(KeyCode.Space))
{
OnFireGuns?.Invoke();
}
}
}
Its a long string of scripts sometimes though so I have left out all the managers and such.
Ship ship inside shipWrapper.ship is a custom data class that stores the info about the ship, not a Monobehaviour, but it holds a bool called IsPlayer aswell. Nothing else of interest I can think of.
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods every time I create a new ShipComponent class. It seems messy and there's a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
In the show example you have more differences between implementations then ones described. Without seeing the full code it is hard to suggest something meaningful.
Few notes on the current code:
In Rudder you don't need to specify IsPlayer because the following:
public override void IsPlayer()
{
base.IsPlayer();
}
does not add anything extra, so you can just skip implementation in the derived class.
Based on provided examples it seems that ReEnable can be defined as virtual in base class with default implementation set to calling SetListeners (the same approach as you have with Init and IsPlayer).
PlayerShipControl.Update possibly can be improved by moving handlers to dictionary. Something along this lines:
public class PlayerShipControl : MonoBehaviour
{
// ...
Dictionary<KeyCode, Action> keyActions = new() // not sure about the type
{
{ KeyCode.W, () => OnSailStateChange?.Invoke(1) },
// ...
{ KeyCode.V, () =>
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
},
// ...
};
void Update()
{
foreach (var kvp in keyActions)
{
if (Input.GetKeyUp(kvp.Key))
{
kvp.Value();
break;
}
}
}
}
Related
The following is attached to my player and would call upon whatever object is hit to use the objects function. Think the player controls the pointing and clicking, but the object controls whatever the object will do, such as turn on a light.
void Interact()
{
RaycastHit interactablehit;
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out interactablehit, MaxDistance))
{
// if raycast hits, then it checks if it hit an object with the tag Interactable.
if (interactablehit.transform.tag == "Interactable")
{
object = interactablehit.transform.name;
interactablehit.transform.gameObject.GetComponent<interactablehit.transform.name>().ObjectInteract();
}
}
}
This is a job for either an interface or a common base class:
public interface IInteractable
{
void ObjectInteract();
}
and then have your different implementations like e.g.
public class ExampleInteractable : MonoBevahiour, IInteractable
{
public void ObjectInteract()
{
Debug.Log("Hello!");
}
}
or
public class ToggleLightInteractable : MonoBevahiour, IInteractable
{
[SerializeField] private Light light;
public void ObjectInteract()
{
light.enabled = !light.enabled;
}
}
and then simply do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
var interactable = interactablehit.transform.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ObjctInteract();
}
}
}
Or the same with a common base class, usefull if you want/need some shared behaviour or default implementations
public class BaseInteractable
{
public virtual void ObjectInteract()
{
Debug.Log("Hello!");
// For demo reasons lets say per default this can be interacted with only once
Destroy(this);
}
}
and then have your different implementations like e.g.
public class ExampleInteractable : BaseInteractable
{
public override void ObjectInteract()
{
// completely override the default behaviour
Debug.Log("Hello World!");
// this can be interacted with forever since the default behavior is not executed
}
}
or
public class ToggleLightInteractable : BaseInteractable
{
[SerializeField] private Light light;
public override void ObjectInteract()
{
// keep the default behavior and only extend it with something additional
base.ObjectInteract();
light.enabled = !light.enabled;
}
}
and then do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
if (interactablehit.transform.TryGetComponent<BaseInteractable>(out var interactable))
{
interactable.ObjctInteract();
}
}
}
If you really really for what reason ever (there shouldn't be any good one) need to stick to string you can (you shouldn't) use SendMessage like
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
interactablehit.transform.gameObject.SendMessage(interactablehit.transform.name, options: SendMessageOptions.DontRequireReceiver);
}
}
which would require your object to be called exactly like the method you want to call, regardless of how the component is called.
I have some things to clarify for myself, so please bear with me.
Let's say I want to have an object that will explode after a certain period of time. If the object explodes while the person is still holding it, you get killed.
Let's consider the following implementation:
public interface IKillable
{
void Kill();
}
public interface IExplodable : IKillable
{
float TimeLeft { get; set; }
void Timer(float timeToExplode);
}
public abstract class Bomb: IExplodable
{
public float TimeLeft { get; set; }
public void Kill()
{
//Destroy the object
}
public void Timer(float timeLeft)
{
if (TimeLeft <= 0) {
Kill();
}
}
}
What if I also want to add let's say a "Backpack" instead of "Bomb" that will have the exact same functionality or any other item that can explode (and kill)?
Is inheriting a "Backpack from Bomb" reasonable in this case?
Probably copying the code won't follow SOLID principles?
Having a base class that provides common functionality for objects is both common and recommended in Unity.
Instead of calling the class Bomb, call it something more generic, like ExplodableDevice (as #zmbq answered). I would, however, make it more explicit that the device explods due to a timer, so perhaps TimerExplodableDevice. Notice that the base class should inherit from MonoBehaviour as otherwise you wouldn't be able to use those objects fully (as c# doesn't allow multiple inheritance).
An example of such implementation would be:
public interface IKillable
{
void Kill();
}
public interface IExplodable : IKillable
{
float TimeLeft { get; set; }
void Timer(float timeToExplode);
}
public abstract class TimerExplodableDevice: MonoBehaviour, IExplodable
{
public float TimeLeft { get; set; }
public virtual void Kill()
{
//Destroy the object
}
public virtual void Timer(float timeLeft)
{
if (TimeLeft <= 0)
{
Kill();
}
}
}
// this should be in a "Devices" folder, or otherwise be called appropriately
public class Backpack : TimerExplodableDevice
{
void Start()
{
TimeLeft = 100;
}
}
// this should be in a "Devices" folder, or otherwise be called appropriately
public class Bomb : TimerExplodableDevice
{
void Start()
{
TimeLeft = 10;
}
}
You can create an abstract ExplodableDevice class and have Bomb and Backpack inherit from it.
Let's say I have an ai or player, I want him to be able to use different weapons.
My design with weapons:
public class Weapon()
{
public virtual void FireWeapon(){} // this is useless for melee weapons
public virtual void SwingMelee(){} // this is useless for guns
public virtual void Reload(){} // this is also useless for melee weapons
}
Then in the ai controller class I simply call the function I want him to do.
This is where the ugly part is (I think)...
Controller class have a list containing some different weapons of ai and a weapon which is being used.
public class WeaponController
{
private List<Weapon> someWeapons;
private Weapon aWeapon;
public void Main()
{
if(/*"Some action or a button click" &&*/ aWeapon.CanSwingMelee() )
aWeapon.SwingMelee();
if(/*"Some action or a button click" &&*/ aWeapon.CanReload() )
aWeapon.Reload();
}
}
What is the better way to implement this? do you have any advices?
Seems that for every different action in a new weapon, I need to implement a function in the most parent Weapon class and I don't think it's a good idea...
The capability of an in-game object can be represented by an interface; you can check if a capability is present by attempting to cast to the interface. What's more, these interfaces can overlap, e.g. both melee and ranged weapons might both have an Attack method.
So for example:
public interface IWeapon
{
void Attack();
}
public interface IRangedWeapon
{
bool IsInRange(ITargetable target);
}
public interface IRequiresAmmunition
{
void Reload();
int AmmoRemaining { get; set; }
}
public class Sword : IWeapon
{
public virtual void Attack() { //code }
}
public class Rifle : IWeapon, IRequiresAmmunition, IRangedWeapon
{
public virtual void Attack() { //code }
public virtual void Reload() { //code }
public virtual int AmmoRemaining { get { } set { } }
public virtual bool IsInrange (ITargetable target) { //code }
}
public class LaserGun: IWeapon, IRangedWeapon
{
public virtual void Attack() { //code }
public virtual bool IsInrange (ITargetable target) { //code }
}
public class WeaponController
{
private List<IWeapon> someWeapons;
private IWeapon aWeapon;
private ITargetable currentTarget;
public void Weapon_OnUse()
{
if (!currentTarget.IsHostile) return;
if (this.IsInMeleeRange(currentTarget))
{
aWeapon.Attack();
return;
}
var w = aWeapon as IRangedWeapon;
if (w != null && w.IsInRange(currentTarget)
{
aWeapon.Attack();
return;
}
context.HUD.Warn("Out of range");
}
public void Weapon_OnReload()
{
var w = aWeapon as IRequiresAmmunition;
if (w != null)
{
w.Reload();
context.HUD.DisplayAmmo(w.AmmoRemaining);
}
}
}
This seems like what abstract classes and inheritance is for:
public abstract class Weapon {
public abstract void Attack();
public abstract void Reload();
}
public class MeleeWeapon : Weapon {
public override void Attack() {
// swing sword
}
public override void Reload() {
// ignore reload
}
}
public class GunWeapon : Weapon {
public override void Attack() {
// fire gun
}
public override void Reload() {
// load weapon from inventory
}
}
public class WeaponController {
private List<Weapon> someWeapons;
private Weapon aWeapon;
public void Main() {
if (/*"Some action or a button click" */)
aWeapon.Attack();
else if (/* some other button click */)
aWeapon.Reload();
}
}
I don't recommend an approach that requires you to create new interfaces for every new behavior and check the type of the weapon. What about something like this:
(This is a very rough draft.)
public abstract class Weapon
{
protected Weapon(WeaponCommandStrategy[] commandStrategies)
{
CommandStrategies = commandStrategies;
}
protected IEnumerable<WeaponCommandStrategy> CommandStrategies { get; }
public void Use(WeaponCommand command)
{
var strategy = CommandStrategies.FirstOrDefault(c => c.Command == command);
strategy?.Execute();
}
}
public enum WeaponCommand
{
Fire,
Swing,
Reload
}
public abstract class WeaponCommandStrategy
{
public WeaponCommand Command { get; private set; }
protected WeaponCommandStrategy(WeaponCommand command)
{
Command = command;
}
public abstract void Execute();
}
Now you can give a weapon whatever behaviors you want it to have in the form of various instances of WeaponCommandStrategy. If a command is sent to a weapon, it executes it. If it doesn't support a command it ignores it. You could add a property to a weapon exposing the available commands so that you could display a list of available commands.
public class Sword : Weapon
{
// Perhaps use dependency injection here
public Sword()
: base(new WeaponCommandStrategy[] { new SwordSwingStrategy() })
{
}
}
public class SwordSwingStrategy : WeaponCommandStrategy
{
public SwordSwingStrategy() : base(WeaponCommand.Swing) { }
public override void Execute()
{
// Do whatever it does
}
}
This essentially makes a Weapon a composition of various things that a weapon can do. If several weapons behave similarly they can share strategies vs. having code duplicated between various weapons.
I am trying to do a state machine to controll ai behaviour in Unity 3D.
My question is regarding inheritance. Im trying to set up some base logic that handles how and why states shoul be changed. But further down the inheritance line i need different kind of characters to be able to do character speicfic things. But im not able to do this with inheritnance.
Can someone confirm that my thinking is not how its done? then i know to find another solution.
PSEUDO CODE:
// STATE CONTROLLERS CONTROLL THE CHARACETER BY CHOOSING WITCH STATE THEY SHOULD BE IN
abstract class StateController {
StateBase state;
int HitPoints;
int Hunger:
abstract void Update()
{
CheckIfStateShouldChange();
state.UpdateState(this);
}
}
WolfStateController : StateController {
WolfState state;
override void Update()
{
base.Update();
state.Update(this);
}
}
SheepStateController : StateController {
SheepState state;
override void Update()
{
base.Update();
state.Update(this);
}
}
// STATES CONTAINS LOGIC FOR BEHAVIOUR IN A CERTAIN STATE
StateBase {
virtual void UpdateState( StateController controller)
{
// Does things all inheriting classes should do
}
}
WolfState : StateBase {
override void UpdateState( WolfStateController wolfstate)
{
base.UpdateState(WolfStateController wolfstate)
//Does wolf specific things that needs to be done in all WolfStates
}
}
WolfStalkAndHuntState : WolfState {
override void UpdateState( WolfStateController wolfstate)
{
base.UpdateState(WolfStateController wolfState);
//Hunts sheep and attacks on sight
}
}
SheepState : StateBase {
override void UpdateState( SheepStateController sheepState)
{
//Does sheepy things
}
}
SheepReproduceState : SheepState {
override void UpdateState( SheepStateController sheepState)
{
base.UpdateState(SheepStateController sheepState);
// Looks for mate and gets freaky
}
}
I would suggest something like this:
Controller Class:
public class SoliderController : MonoBehaviour
{
[HideInInspector] public sState currentState;
[HideInInspector] public FireState fireState;
[HideInInspector] public IdleState idleState;
[HideInInspector] public ChaseState chaseState;
private void Awake()
{
fireState = new FireState(this);
idleState = new IdleState(this);
chaseState = new ChaseState(this);
}
private void Start ()
{
currentState = idleState;
}
private void Update()
{
currentState.Update();
}
Interface:
public abstract class sState
{
public abstract void Update();
public abstract void ToChaseState();
public abstract void ToIdleState();
public abstract void ToFireState();
}
Example Class
public class IdleState : sState
{
private readonly SoliderController controller;
public IdleState(SoliderController soliderController)
{
controller = soliderController;
}
public override void Update()
{
Patrol();
//Condition to change state
if (*expresion1*)
ToChaseState();
if (*expresion2*)
ToFireState();
}
private void Patrol()
{
//Your Logic for the behvaiour wanted.
}
public override void ToChaseState()
{
controller.currentState = controller.chaseState;
}
public override void ToFireState()
{
controller.currentState = controller.fireState;
}
public override void ToIdleState()
{
Debug.LogWarning("Can't transition to same state");
}
}
This way checking for changes is way easier and also you can impletement state specific behaviours as well. Also, adding a new state goes really easy, you just implement the new Class and ToNewState method in the interface.
Hope it helped.
I have two projects: ClientProj and ServerProj, which both share a SharedLibrary containing the basics of my game.
Inside this library I have the class GameObject which is the base class from which many other game items inherit.
Inside GameObject is a SetPosition() method.
Here's my problem: When I run SetPosition() on the client, I wish to add some additional code / override the method completely. The code I wish to add however relates to classes that are only present in the ClientProj namespace, which the SharedLibrary knows nothing about.
Is there any clean way to override or extend the library methods?
Updated: Note that the instances of GameObject and all things that inherit it are defined, contained and handled all within the SharedLibrary namespace. For the most part the ClientProj and ServerProj only handle networking, users and input/output.
You can use the Proxy pattern and have the game objects inherit from the proxy class instead of the real class:
SharedLibrary:
public class GameObject
{
public virtual void SetPosition() { ... }
}
public class DelegatingGameObject : GameObject
{
public GameObject Inner;
public override void SetPosition() { Inner.SetPosition(); }
}
public class Tree : DelegatingGameObject
{
}
ClientLibrary:
class ClientGameObject : GameObject
{
public override void SetPosition()
{
if (isMonday) base.SetPosition();
}
}
var tree = new Tree { Inner = new ClientGameObject() };
tree.SetPosition();
SharedLibrary:
public class GameObject
{
public virtual void SetPosition() { Console.WriteLine("GameObject.SetPosition"); }
public static event Func<GameObject> Factory;
internal static GameObject CreateBase() { var factory = Factory; return (factory != null) ? factory() : new GameObject(); }
}
internal class GameObjectBase : GameObject
{
private readonly GameObject baseGameObject;
protected GameObjectBase() { baseGameObject = GameObject.CreateBase(); }
public override void SetPosition() { baseGameObject.SetPosition(); }
}
internal class Tree : GameObjectBase
{
public override void SetPosition()
{
Console.WriteLine("Tree.SetPosition");
base.SetPosition();
}
}
public static class Game
{
public static void Start()
{
new Tree().SetPosition();
}
}
ClientLibrary:
internal class ClientGameObject : GameObject
{
public override void SetPosition()
{
Console.WriteLine("ClientGameObject.SetPosition Before");
base.SetPosition();
Console.WriteLine("ClientGameObject.SetPosition After");
}
}
internal static class Program
{
static void Main(string[] args)
{
GameObject.Factory += () => new ClientGameObject();
Game.Start();
}
}
Make SetPosition method virtual and use override keyword to override its behaviour in ClientProj.
You can do it virtual in base class, override in derived, and in overriden method call your methods and after base class method.
A psudocode can look like this:
public class GameObject
{
public virtual void SetPosition()
{
//do something here
}
}
public class Derived: GameObject
{
public override void SetPosition()
{
// do something specific to Derived
base.SetPosition(); // CALL BASE CLASS METHOD AFTER
}
}