Ich habe darüber nachgedacht, wie das Überschreiben von Verhaltensweisen in einem komponentenbasierten Entitätssystem implementiert werden kann. Ein konkretes Beispiel: Eine Entität hat eine Heidekomponente, die beschädigt, geheilt, getötet usw. werden kann. Die Entität hat auch eine Rüstungskomponente, die den Schaden begrenzt, den ein Charakter erleidet.
Hat jemand zuvor solche Verhaltensweisen in einem komponentenbasierten System implementiert?
Wie hast du es gemacht?
Wenn das noch nie jemand gemacht hat, warum denkst du dann? Ist etwas besonders Falsches daran, das Verhalten von Komponenten zu überschreiben?
Unten finden Sie eine grobe Skizze, wie ich mir das vorstellen würde. Komponenten in einer Entität werden geordnet. Diejenigen an der Front haben die Möglichkeit, zuerst eine Schnittstelle zu warten. Ich gehe nicht näher darauf ein, wie das gemacht wird, dynamic_cast
sondern gehe einfach davon aus, dass es böse s verwendet (es tut es nicht, aber der Endeffekt ist der gleiche, ohne dass RTTI erforderlich ist).
class IHealth
{
public:
float get_health( void ) const = 0;
void do_damage( float amount ) = 0;
};
class Health : public Component, public IHealth
{
public:
void do_damage( float amount )
{
m_damage -= amount;
}
private:
float m_health;
};
class Armor : public Component, public IHealth
{
public:
float get_health( void ) const
{
return next<IHealth>().get_health();
}
void do_damage( float amount )
{
next<IHealth>().do_damage( amount / 2 );
}
};
entity.add( new Health( 100 ) );
entity.add( new Armor() );
assert( entity.get<IHealth>().get_health() == 100 );
entity.get<IHealth>().do_damage( 10 );
assert( entity.get<IHealth>().get_health() == 95 );
Gibt es etwas besonders Naives an der Art und Weise, wie ich dies vorschlage?
quelle
IHealth
undIKnockback
. Es wäre nicht sinnvoll, diese beiden Komponenten in einer einzigen Klassenhierarchie zu verbinden. Mehrfachvererbung ist immer problematisch Ich hatte überlegt, Shield eine Proxy-Mitgliedsklasse verwenden zu lassen, die vonIHealth
allen Aufrufen abgeleitet ist und diese dann an Shield weiterleitet. Bei dieser Implementierungstechnik gibt es keinen MI auf Kosten eines zusätzlichen nicht virtuellen Methodenaufrufs (den der Optimierer möglicherweise inline ausführen kann). In jedem Fall ist die API (add
,get
,next
, usw.) ist das gleiche.Antworten:
Ich denke, Sie machen es etwas zu kompliziert oder nicht kompliziert genug.
Eine Richtung, die ich vorschlagen würde, wäre, auseinander zu brechen, um Gesundheit zu erlangen und Schnittstellen zu beschädigen.
Vielleicht sieht Ihre Rüstungskomponente so aus:
Alternativ (und wahrscheinlich was ich tun würde) könnten Sie Ihre Komponenten etwas größer machen und nur eine generische "Entity" -Basisklasse haben, von der Ihre spezifischen Typen abgeleitet sind. Dies würde die Funktionalität von Gesundheit und Rüstung enthalten, und Sie könnten daraus bestimmte Entitäten implementieren.
quelle
Ihre Gedanken sind am richtigen Ort, aber ich denke, Sie versuchen, das Komponentensystem auf eine Detailebene zu bringen, die es nicht benötigt. Ich würde sagen, machen Sie eine Komponente namens Gesundheit, wenn Sie nur Gesundheit brauchen. Wenn Sie jedoch eine Gesundheitskomponente benötigen, die auch Rüstungen enthält, machen Sie sie zu einer CHealthAndArmor-Komponente, die im Allgemeinen immer noch zur API von Health passt, sodass sie wie jede andere verwendet werden kann, bieten Sie jedoch die Möglichkeit, auch die Rüstungsstufen zu ändern, wenn benötigt .. Wenn es sich vollständig um einen Ladezeitwert handelt, besteht keine Notwendigkeit, dies ist nur die interne Funktionsweise dieser bestimmten Gesundheitskomponente.
Ehrlich gesagt sind die Bedenken, die Sie haben (aus diesem und Ihrem anderen Beitrag), wie fein die Detailgenauigkeit ist, in die Sie Komponenten aufteilen. Wenn Sie jemals feststellen, dass Sie eine Komponente in einer anderen Komponente erben oder einkapseln, sollten Sie überlegen, was Sie tun. In komponentenbasierten Architekturen versuchen Sie im Allgemeinen, eine solche Vererbung zu vermeiden.
Hoffe das hilft
quelle
Ihr Komponentenmaterial ist zu viel OOP. Sehen Sie hier, was ich mache:
https://github.com/thelinuxlich/starwarrior_CSharp
Und dies ist das Entity System-Framework, das ich auf C # portiert habe: https://github.com/thelinuxlich/artemis_CSharp
quelle
Ich verwende ein ereignisgesteuertes Komponentensystem und hatte das gleiche Problem. Grundsätzlich abonniere ich Komponentenfunktionen für andere Komponentenereignisse. Für ein Ereignis kann ich x Funktionen abonnieren lassen und Ereignisse werden in der Reihenfolge ausgelöst, in der sie abonniert wurden. Ereignisse geben auch einen Parameter an, der eine dynamische Klasse ist und als Referenz übergeben wird. All dies bedeutet, dass ich eine Statistikkomponente und eine Gesundheitskomponente erstellen kann. Ich abonniere beide eine verletzte Nachricht, aber da Stats zuerst abonniert wird, wird der Rüstungsstatus der Stat-Komponente verwendet und der Schadenswert innerhalb des Parameterobjekts geändert. Die Gesundheitskomponente steht als nächstes in der Abonnementliste und zum Zeitpunkt des Aufrufs wurde der Schadenswert bereits verringert.
Ich kann nicht genug über das Ereignis sprechen, mit Komponenten umzugehen. Es ermöglicht wirklich das Zusammensetzen von Funktionen über Komponenten, bei denen sie sich nicht kennen. Der Benutzer richtet nur die Ereignisabonnements ein, um die Funktionalität aufzubauen. Würde niemals von diesem System zurückblicken.
quelle