My setup’s purpose is to take a case where you would want multiple inheritances and make it modular. So you can give something physics, you can make it fly around, you can make the player control it, you can make it floaty, you can add any code to it without changing the base entity class, and without worrying about (much) inheritance. Most importantly, you can do it all at the same time.
I have this setup in every game I do. I have a class that serves as an entity (this class is huge but the bulk of it is irrelevant to this structure). In this case it is a rectangular UI element.
The entity class has several EventHandlers, such as
public event EventHandler OnUpdate;
This entity class has a public object that contains behaviors. These behaviors can be combined in such a modular way that inheritance would make impossible.
Say I want to give an entity a behavior. In this case it’s drawing a background. The behavior that’s added will be a DrawBackGround behavior.
Widget.Behaviors.Add(new DrawBackground());
Widget is our entity. Behaviors is our behavior manager. The following is the most important part of the Add() method.
public bool TryAdd<T>(T behavior, out T added_behavior) where T : WidgetBehavior
{
if (Contains(behavior.GetType()))
{
added_behavior = default;
return false;
}
if (behavior.IsSubBehavior
&& !Contains(behavior.BaseBehaviorType)) {
TryAdd((WidgetBehavior)Activator.CreateInstance(behavior.BaseBehaviorType));
}
// Note these two lines in particular
_behaviors.Add(behavior);
behavior.Parent = Parent;
added_behavior = behavior;
return true;
}
When you add a behavior to this Widget entity, it sets the given behavior’s “Entity Parent
” to be the entity. In doing so, it connects all the events. You should have it set so that when you remove the behavior, it disconnects the events as well.
~~
If this is too much to bite off, here’s a little bit of a compact example that I wrote up. Note: I did not test this;
I’ll start off with the end result.
Entity entity = new Entity();
entity.Behaviors.Add(new MoveSideWays());
This is ideal because you can remove this behavior or modify it easily. You do not have to code the behavior into the entity class.
/// <summary> This is my shoddy entity class. Best to keep it sealed so the behavior system isn't ignored. </summary>
sealed class Entity
{
// This should be public so the outside code can add behaviors to this entity.
public EntityBehaviorManager Behaviors;
// These should be accessible to our behaviors, so they are public ---
public GameTime GameTime;
public SpriteBatch SpriteBatch;
public Vector2 Position = new Vector2();
// ---
public Entity()
{
Behaviors = new EntityBehaviorManager(this);
}
public void Update(GameTime gameTime)
{
GameTime = gameTime;
OnUpdate?.Invoke(this, EventArgs.Empty);
}
public void Draw(SpriteBatch spriteBatch)
{
// Draw something using the Position value here...
// ...
// Draw.Something(spriteBatch, Position);
// ...
SpriteBatch = spriteBatch;
OnDraw?.Invoke(this, EventArgs.Empty);
}
public event EventHandler OnUpdate;
public event EventHandler OnDraw;
}
/// <summary> This is the behavior manager that will handle my entity's behaviors. </summary>
class EntityBehaviorManager
{
Entity parent;
List<EntityBehavior> behaviors = new List<EntityBehavior>();
public EntityBehaviorManager(Entity parent) => this.parent = parent;
public void Add(EntityBehavior behavior)
{
behaviors.Add(behavior);
behavior.Parent = parent;
}
}
/// <summary> The base class for all my entity behaviors. </summary>
abstract class EntityBehavior
{
Entity entity_backing;
public Entity Parent
{
get => entity_backing;
set
{
entity_backing = value;
Initialize();
ConnectEvents();
}
}
internal void Disconnect()
{
DisconnectEvents();
}
protected abstract void Initialize();
protected abstract void ConnectEvents();
protected abstract void DisconnectEvents();
public abstract object Clone();
}
class MoveSideWays : EntityBehavior
{
protected override void Initialize()
{
}
protected override void ConnectEvents()
{
Parent.OnUpdate += Move;
}
protected override void DisconnectEvents()
{
Parent.OnUpdate -= Move;
}
public override object Clone()
{
return new MoveSideWays();
}
void Move(object sender, EventArgs args)
{
// Move the entity! That was easy.
Parent.Position.X += 1f;
}
}