I am a college student, and game development is a little hobby for me. I am writing a new game and I have been looking for a new approach to developing the game engine as to make it more flexible (and allow to provide myself with a solid foundation for future endeavors). I found a few articles about how rather than relying on deep inheritance to represent game entities (Which can lead to heavy root and leaf nodes in your inheritance tree, as well as diamond inheritance in some cases), a better approach is to model your logic in components (Better yet, behaviors and attributes).
Here is a presentation that illustrates this: http://www.gdcvault.com/free/gdc-canada-09 "Theory and Practice of the Game Object Component Architec..."
I'm implementing this using XNA and C#. I have an Entity class:
class Entity
{
private Game1 mGame;
private Dictionary<AttributeType, object> mAttributes;
private Dictionary<BehaviorType, Behavior> mBehaviors;
public Entity(Game game)
{
mGame = game;
mAttributes = new Dictionary<AttributeType, object>();
mBehaviors = new Dictionary<BehaviorType, Behavior>();
}
public Game GetGame()
{
return mGame;
}
//Attributes
public void SetAttribute<T>(AttributeType attributeType, T value)
{
if (mAttributes.ContainsKey(attributeType))
{
mAttributes[attributeType] = value;
OnMessage<T>(MessageType.ATTRIBUTE_UPDATED, attributeType, value);
}
else
{
mAttributes.Add(attributeType, value);
OnMessage<T>(MessageType.ATTRIBUTE_CREATED, attributeType, value);
}
}
public T GetAttribute<T>(AttributeType attributeType)
{
if (!mAttributes.ContainsKey(attributeType))
{
throw new KeyNotFoundException("GetAttribute: Attribute with type: " + attributeType.ToString() + " not found.");
}
return (T)mAttributes[attributeType];
}
public bool HasAttribute(AttributeType attributeType)
{
return mAttributes.ContainsKey(attributeType);
}
//Behaviors
public void SetBehavior(BehaviorType behaviorType, Behavior behavior)
{
if (mBehaviors.ContainsKey(behaviorType))
{
mBehaviors[behaviorType] = behavior;
}
else
{
mBehaviors.Add(behaviorType, behavior);
}
}
public Behavior GetBehavior(BehaviorType behaviorType)
{
if (!mBehaviors.ContainsKey(behaviorType))
{
throw new KeyNotFoundException("GetBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
}
return mBehaviors[behaviorType];
}
public bool HasBehavior(BehaviorType behaviorType)
{
return mBehaviors.ContainsKey(behaviorType);
}
public Behavior RemoveBehavior(BehaviorType behaviorType)
{
if (!mBehaviors.ContainsKey(behaviorType))
{
throw new KeyNotFoundException("RemoveBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
}
Behavior behavior = mBehaviors[behaviorType];
mBehaviors.Remove(behaviorType);
return behavior;
}
public void OnUpdate(GameTime gameTime)
{
foreach (Behavior behavior in mBehaviors.Values)
{
behavior.OnUpdate(gameTime);
}
}
public void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data)
{
foreach (Behavior behavior in mBehaviors.Values)
{
behavior.OnMessage<T>(messageType, attributeType, data);
}
}
}
Where "AttributeType" and "BehaviorType" are just enums:
public enum AttributeType
{
POSITION_2D,
VELOCITY_2D,
TEXTURE_2D,
RGB_8888
}
The behavior class is a super-class of all other behaviors; behaviors are meant to be self contained and modular (For example, I might have a SpriteBatchRenderBehavior which knows only how to render an entity to the SpriteBatch, which should be usable in any new game that uses this framework).
abstract class Behavior
{
//Reference to owner to access attributes
private Entity mOwner;
public Behavior(Entity owner)
{
mOwner = owner;
}
public Entity GetOwner()
{
return mOwner;
}
protected void SetAttribute<T>(AttributeType attributeType, T value)
{
mOwner.SetAttribute<T>(attributeType, value);
}
protected T GetAttribute<T>(AttributeType attributeType)
{
return (T)mOwner.GetAttribute<T>(attributeType);
}
public abstract void OnUpdate(GameTime gameTime);
public abstract void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data);
}
So an entity has a bag of labeled attributes (POSITION_2D, TEXTURE_2D, etc), each behavior can access the attributes of their owner through get/set methods. I'm running into a problem where attributes from my entity need to be shared between behaviors (not sure if this IS actually a problem). Basically, if I have a behavior that changes the position of an entity in the game world, it modifies the position attribute of the entity. I then add the SpriteBatchDrawBehavior to the entity, which depends on the entity having a position (so it knows where to draw the entity). My problem is, if I add the draw behavior before the movement behavior, there is no guarantee that the entity will have acquired a position attribute yet (in which case a key not found exception would be thrown).
I can always make sure the entity has a position by adding a behavior that guarantees it WILL have a position before adding a draw behavior (or any other behavior that would depend on this attribute), however this would make these behaviors coupled in a way they shouldn't be.
The other option is to inherit the draw behavior from a movement behavior which inherits from a position behavior (but this brings us back to the problem with diamond inheritance, etc, and child classes that have little to no ACTUAL relevance to their parent classes, other than having similar dependencies).
One more option is to write a check in my behaviors like: "If my owner doesn't have a position, then make a position attribute for him", but then this raises the question: which default value should be chosen for the new attribute? etc etc.
Any suggestions? Am I overlooking something?
Thanks!