Überschreiben des Komponentenverhaltens

7

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_castsondern 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?

deft_code
quelle
Bitte sehen Sie meine Antwort hier: gamedev.stackexchange.com/questions/13916/… . Wenn das Verknüpfen mit eigenen Antworten verpönt ist, entfernen Mods dies bitte.
Raine
2
Gibt es einen bestimmten Grund, warum Sie eine zusätzliche Schnittstelle für IHealth erstellen und dann die Mehrfachvererbung weiter unten verwenden, anstatt IHealth von Component erben zu lassen und dann Armor and Health nur von IHealth erben zu lassen?
TravisG
@heishe: Ich hatte Angst, dass eine Komponente zwei unterschiedliche Schnittstellen implementieren möchte, nämlich IHealthund IKnockback. 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 von IHealthallen 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.
Deft_code

Antworten:

3

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:

class Armor : public Component, public IDamageReceiver
{
public:
   void do_damage( float amount )
   {
      // just assume that subtract_health isn't used in client code maybe
      this.get<IHealth>().subtract_health( amount / 2 ); 
   }
};

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.

Tetrad
quelle
Ihre Lösung spricht mein Problem nicht ganz an. Ich möchte die Rüstungskomponente hinzufügen, ohne eine der bereits vorhandenen Komponenten anzupassen. Richtig gemacht, ich denke, Sie könnten einfach zwei Rüstungskomponenten hinzufügen und es würde das Richtige tun (Schaden auf 25% reduzieren). Mit anderen Worten, mit einer allgemeinen Override-Lösung könnte ich die Overrides überschreiben.
Deft_code
3
Ich stimme Tetrad hier irgendwie zu. Ich denke, Sie machen Ihr System viel zu komplex und detailliert. Ich würde sagen, wenn Sie Komponente X überschreiben möchten, suchen Sie Komponente X in den relevanten Entitäten und tauschen Sie sie gegen Komponente Y aus, die das tut, was Sie wollen.
1
@deft_code Basierend auf dem, was Sie gesagt haben und Ihrem anderen Beitrag über Komponenten vor diesem. Ich denke, Sie möchten einen Komponententyp 'CharacterEffects' einführen (ohne Power-Ups, da diese manchmal schlecht sein können :)), mit dem Sie (De-) Buffs erstellen können, die auf einen Charakter angewendet werden können. Dies erfordert weiterhin, dass die Gesundheitskomponente aktualisiert wird, um nach zugehörigen Effekten zu suchen, um festzustellen, ob sie zutreffen. Es kommt jedoch darauf an, dass ein neuer Komponententyp benötigt wird, anstatt vom Zustand zu erben und den Zustand nicht zu ersetzen. Alternativ können Sie Ihr Komponentensystem über einen Stapel verfügen, damit die Reihenfolge der Ausführung / Auflösung garantiert ist.
James
2

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

James
quelle
1
Wenn Sie eine HealthAndArmor-Komponente erstellen, wird der Zweck einer komponentenbasierten Architektur zunichte gemacht.
Mitchell
@Mitchell Das hängt alles wirklich vom Spiel ab. Wenn Ihre Rüstung nichts anderes als zusätzliche Gesundheit ist, wie in den alten Spielen im Doom / Quake-Stil, wäre es eine völlige Verschwendung, diese Dinge aufzuteilen. Ihre Komponenten sollten zu Ihrer Umgebung und Ihrem Spiel passen.
James
Ich bin anderer Meinung, die Aufteilung ermöglicht eine einfache Änderung in der Zukunft, und der Wechsel von "Rüstung und Gesundheit" zu "Gesundheit" erfordert nur das Entfernen von Rüstungen anstelle des Entfernens von HealthAndArmor und das Hinzufügen von Gesundheit. Es mag unbedeutend erscheinen, aber es kann sehr ärgerlich werden, wenn Sie viel Prototypen erstellen. Aber ich denke, es hängt auch von Ihrem Entity Manager ab.
Mitchell
@Mitchell Ich denke, wir müssen uns dann einfach nicht einigen. Ich habe Komponenten immer als etwas gesehen, das Sie auf das betreffende Spiel / die betreffende Anwendung zuschneiden. Sicher, Sie sammeln im Laufe der Zeit eine schöne Sammlung davon, je nachdem, für wie viele Projekte Sie das System verwenden / wiederverwenden, aber für mich besteht der gesamte Sinn des Komponentensystems darin, kompliziertere Objekte aus einfachen und spezifischen kleineren Objekten zu erstellen.
James
0

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.

user441521
quelle