Ich bin ein Student und Spieleentwicklung ist ein kleines Hobby für mich. Ich schreibe ein neues Spiel und habe nach einem neuen Ansatz für die Entwicklung der Spiel-Engine gesucht, um sie flexibler zu gestalten (und mir eine solide Grundlage für zukünftige Bemühungen zu bieten). Ich habe ein paar Artikel darüber gefunden, wie ein besserer Ansatz darin besteht, Ihre Logik zu modellieren, anstatt sich auf eine tiefe Vererbung zu verlassen, um Spielentitäten darzustellen (was zu starken Wurzel- und Blattknoten in Ihrem Vererbungsbaum sowie in einigen Fällen zu einer Diamantvererbung führen kann) in Komponenten (noch besser, Verhalten und Attribute).
Hier ist eine Präsentation, die dies veranschaulicht: http://www.gdcvault.com/free/gdc-canada-09 "Theorie und Praxis der Spielobjektkomponente Architec ..."
Ich implementiere dies mit XNA und C #. Ich habe eine Entitätsklasse:
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);
}
}
}
Wobei "AttributeType" und "BehaviorType" nur Aufzählungen sind:
public enum AttributeType
{
POSITION_2D,
VELOCITY_2D,
TEXTURE_2D,
RGB_8888
}
Die Verhaltensklasse ist eine Superklasse aller anderen Verhaltensweisen. Verhaltensweisen sollen eigenständig und modular sein (z. B. könnte ich ein SpriteBatchRenderBehavior haben, das nur weiß, wie eine Entität in SpriteBatch gerendert wird, das in jedem neuen Spiel verwendet werden sollte, das dieses Framework verwendet).
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);
}
Eine Entität verfügt also über eine Reihe von beschrifteten Attributen (POSITION_2D, TEXTURE_2D usw.). Jedes Verhalten kann über get / set-Methoden auf die Attribute seines Besitzers zugreifen. Ich stoße auf ein Problem, bei dem Attribute aus meiner Entität zwischen Verhaltensweisen geteilt werden müssen (nicht sicher, ob dies tatsächlich ein Problem ist). Wenn ich ein Verhalten habe, das die Position einer Entität in der Spielwelt ändert, ändert es grundsätzlich das Positionsattribut der Entität. Ich füge dann das SpriteBatchDrawBehavior zur Entität hinzu, was von der Entität abhängt, die eine Position hat (damit sie weiß, wo die Entität gezeichnet werden soll). Mein Problem ist, wenn ich das Zeichenverhalten vor dem Bewegungsverhalten hinzufüge, gibt es keine Garantie dafür, dass die Entität noch ein Positionsattribut erhalten hat (in diesem Fall würde eine nicht gefundene Schlüsselausnahme ausgelöst).
Ich kann immer sicherstellen, dass die Entität eine Position hat, indem ich ein Verhalten hinzufüge, das garantiert, dass sie eine Position hat, bevor ich ein Zeichenverhalten hinzufüge (oder jedes andere Verhalten, das von diesem Attribut abhängt). Dies würde jedoch dazu führen, dass diese Verhaltensweisen so gekoppelt werden, wie sie sind sollte nicht sein.
Die andere Möglichkeit besteht darin, das Zeichenverhalten von einem Bewegungsverhalten zu erben, das von einem Positionsverhalten erbt (dies bringt uns jedoch zurück zu dem Problem der Diamantvererbung usw. und von untergeordneten Klassen, die für ihre übergeordneten Klassen keine oder nur eine geringe tatsächliche Relevanz haben als mit ähnlichen Abhängigkeiten).
Eine weitere Option besteht darin, mein Verhalten zu überprüfen: "Wenn mein Besitzer keine Position hat, erstellen Sie ein Positionsattribut für ihn". Dies wirft jedoch die Frage auf: Welcher Standardwert sollte für das neue Attribut ausgewählt werden ? etc etc. etc.
Irgendwelche Vorschläge? Übersehe ich etwas?
Vielen Dank!