Powerups in einem komponentenbasierten System durchführen

29

Ich fange gerade erst an, mich wirklich mit komponentenbasiertem Design zu beschäftigen. Ich weiß nicht, wie ich das "richtig" anstellen soll.

Hier ist das Szenario. Der Spieler kann einen Schild ausrüsten. Der Schild wird als Blase um den Spieler gezogen, hat eine eigene Kollisionsform und verringert den Schaden, den der Spieler durch Flächeneffekte erleidet.

Wie wird ein solcher Schild in einem komponentenbasierten Spiel aufgebaut?

Ich bin verwirrt, dass der Schild offensichtlich drei Komponenten aufweist.

  • Schadensminderung / Filterung
  • Ein Sprite
  • Ein Collider.

Um es noch schlimmer zu machen, könnten verschiedene Schildvarianten noch mehr Verhaltensweisen haben, die alle Komponenten sein könnten:

  • Steigert die maximale Gesundheit des Spielers
  • Gesundheitsregeneration
  • Ablenkung des Projektils
  • etc

  1. Überdenke ich das? Sollte der Schild nur eine Superkomponente sein?
    Ich denke wirklich, das ist eine falsche Antwort. Wenn Sie also denken, dass dies der richtige Weg ist, erklären Sie es bitte.

  2. Sollte der Schild eine eigene Entität sein, die den Standort des Spielers verfolgt?
    Dies könnte es schwierig machen, die Schadensfilterung zu implementieren. Außerdem verwischen die Linien zwischen angehängten Komponenten und Entitäten.

  3. Sollte der Schild eine Komponente sein, die andere Komponenten beherbergt?
    Ich habe so etwas noch nie gesehen oder gehört, aber vielleicht ist es üblich und ich bin einfach noch nicht tief genug.

  4. Sollte der Schild nur eine Reihe von Komponenten sein, die dem Player hinzugefügt werden?
    Möglicherweise mit einer zusätzlichen Komponente, um die anderen zu verwalten, z. B. damit alle als Gruppe entfernt werden können. (Lassen Sie aus Versehen die Schadensreduzierungskomponente hinter sich, das würde jetzt Spaß machen).

  5. Noch etwas, das für jemanden mit mehr Erfahrung mit Komponenten offensichtlich ist?

deft_code
quelle
Ich habe mir erlaubt, Ihren Titel genauer zu definieren.
Tetrad

Antworten:

11

Sollte der Schild eine eigene Entität sein, die den Standort des Spielers verfolgt? Dies könnte es schwierig machen, die Schadensfilterung zu implementieren. Außerdem verwischen die Linien zwischen angehängten Komponenten und Entitäten.

Edit: Ich denke, es gibt nicht genug "autonomes Verhalten" für eine getrennte Einheit. In diesem speziellen Fall folgt ein Schild dem Ziel, arbeitet für das Ziel und überlebt das Ziel nicht. Während ich der Meinung bin, dass das Konzept eines "Schildobjekts" nichts auszusetzen hat, handelt es sich in diesem Fall um ein Verhalten, das in eine Komponente passt. Ich bin aber auch ein Verfechter rein logischer Entitäten (im Gegensatz zu vollständigen Entitätssystemen, in denen Transform- und Rendering-Komponenten zu finden sind).

Sollte der Schild eine Komponente sein, die andere Komponenten beherbergt? Ich habe so etwas noch nie gesehen oder gehört, aber vielleicht ist es üblich und ich bin einfach noch nicht tief genug.

Sehen Sie es aus einer anderen Perspektive; Durch Hinzufügen einer Komponente werden auch andere Komponenten hinzugefügt, und beim Entfernen werden auch die zusätzlichen Komponenten entfernt.

Sollte der Schild nur eine Reihe von Komponenten sein, die dem Player hinzugefügt werden? Möglicherweise mit einer zusätzlichen Komponente, um die anderen zu verwalten, z. B. damit alle als Gruppe entfernt werden können. (Lassen Sie aus Versehen die Schadensreduzierungskomponente hinter sich, das würde Spaß machen).

Dies könnte eine Lösung sein, würde die Wiederverwendung fördern, ist jedoch auch fehleranfälliger (z. B. für das von Ihnen erwähnte Problem). Es ist nicht unbedingt schlecht. Vielleicht findest du neue Zauberkombinationen mit Versuch und Irrtum :)

Noch etwas, das für jemanden mit mehr Erfahrung mit Komponenten offensichtlich ist?

Ich werde etwas näher darauf eingehen.

Ich glaube, Sie haben bemerkt, wie einige Komponenten Vorrang haben sollten, unabhängig davon, wann sie zu einer Entität hinzugefügt wurden (dies würde auch Ihre andere Frage beantworten).

Ich gehe auch davon aus, dass wir eine nachrichtenbasierte Kommunikation verwenden (aus Diskussionsgründen handelt es sich derzeit nur um eine Abstraktion über einen Methodenaufruf).

Immer wenn eine Schildkomponente "installiert" wird, werden die Meldungsbehandlungsroutinen für Schildkomponenten mit einer bestimmten (höheren) Reihenfolge verkettet.

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

Die "Statistik" -Komponente installiert einen "Schadens" -Meldungshandler im In / Invariant / Normal-Index. Verringern Sie die HP jedes Mal, wenn eine Schadensmeldung eingeht, um den entsprechenden Wert.

Ziemlich normales Verhalten (setzen Sie natürliche Schadensresistenz und / oder Rassenmerkmale ein, was auch immer).

Die Schildkomponente installiert einen "Schadens" -Meldungshandler im In / Pre / High-Index.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

Sie sehen, dass dies ziemlich flexibel ist, obwohl dies eine sorgfältige Planung beim Entwerfen der Komponenteninteraktion erfordern würde, da Sie bestimmen müssen, in welchem ​​Teil der Nachrichtenbehandlungspipeline Komponenten-Ereignishandler für Nachrichten installiert sind.

Macht Sinn? Lassen Sie mich wissen, ob ich weitere Details hinzufügen kann.

Bearbeiten: In Bezug auf Instanzen mit mehreren Komponenten (zwei Rüstungskomponenten). Sie können entweder die Gesamtzahl der Instanzen in nur einer Entitätsinstanz nachverfolgen (dies beendet jedoch den Status pro Komponente) und einfach weitere Ereignishandler hinzufügen oder sicherstellen, dass Ihre Komponentencontainer im Voraus doppelte Komponententypen zulassen.

Raine
quelle
Sie haben die erste Frage ohne Angabe von Gründen mit "Nein" beantwortet. Anderen beizubringen bedeutet, ihnen zu helfen, die Gründe für jede Entscheidung zu verstehen. IMO, die Tatsache, dass in RL ein Kraftfeld eine separate "physische Entität" von Ihrem eigenen Körper ist, reicht aus, um es als separate Entität im Code zuzulassen. Können Sie gute Gründe vorschlagen, warum es schlecht ist, diese Route zu wählen?
Ingenieur
@ Nick, ich versuche auf keinen Fall, irgendjemandem etwas beizubringen, sondern teile, was ich über das Thema weiß. Ich werde jedoch eine Begründung hinter diesem "Nein" hinzufügen, die hoffentlich diese unangenehme Ablehnung beseitigt :(
Raine
Ihr Autonomiepunkt ist sinnvoll. Aber Sie stellen fest: "In diesem Fall haben wir es mit Verhalten zu tun". True - Verhalten, bei dem ein vollständig separates physisches Objekt beteiligt ist (die Form der Schildkollision). Für mich ist eine Einheit an einen physischen Körper gebunden (oder an eine zusammengesetzte Gruppe von Körpern, die z. B. durch Gelenke verbunden sind). Wie vereinbaren Sie das? Ich für meinen Teil würde mich unwohl fühlen, wenn ich ein physisches "Dummy" -Fixier hinzufüge, das nur aktiviert wird, wenn der Spieler zufällig einen Schild benutzt. IMO unflexibel, schwer über alle Entitäten zu pflegen. Denken Sie auch an ein Spiel, bei dem Schildgürtel die Schilde auch nach dem Tod (Dune) tragen.
Ingenieur
@ Nick, die Mehrheit der Entity-Systeme hat sowohl logische als auch grafische Komponenten. In diesem Fall ist es absolut sinnvoll, eine Entity für einen Shield zu haben. In rein logischen Entitätssystemen ist "Autonomie" das Produkt der Komplexität eines Objekts, seiner Abhängigkeiten und seiner Lebensdauer. Letztendlich ist die Anforderung der König - und da es keinen wirklichen Konsens darüber gibt, was ein Entitätssystem ist, gibt es viel Raum für projektspezifische Lösungen :)
Raine,
@deft_code, lass es mich wissen, wenn ich meine Antwort verbessern kann.
Raine
4

1) Überdenke ich das? Sollte der Schild nur eine Superkomponente sein?

Vielleicht hängt es davon ab, wie wiederverwendbar der Code sein soll und ob er sinnvoll ist.

2) Sollte der Schild eine eigene Entität sein, die den Standort des Spielers verfolgt?

Es sei denn, dieser Schild ist eine Art Kreatur, die sich irgendwann selbständig bewegen kann.

3) Sollte die Abschirmung eine Komponente sein, die andere Komponenten beherbergt?

Das klingt sehr nach Einheit, daher lautet die Antwort nein.

4) Sollte der Schild nur eine Reihe von Komponenten sein, die dem Spieler hinzugefügt werden?

Es ist wahrscheinlich.

"Schadensminderung / Filterung"

  • Core Shield-Komponentenfunktionalität.

"Ein Sprite"

  • Gibt es einen Grund, warum Sie Ihrer Zeichenentität keine weitere SpriteComponent hinzufügen können (dh mehr als eine Komponente eines bestimmten Typs pro Entität)?

"Ein Collider"

  • Bist du sicher, dass du noch einen brauchst? Dies hängt von Ihrer Physik-Engine ab. Können Sie eine Nachricht an die ColliderComponent der Zeichenentität senden und diese auffordern, die Form zu ändern?

"Steigert die maximale Gesundheit des Spielers, die Regeneration der Gesundheit, die Ablenkung des Projektils usw."

  • Andere Artefakte sind möglicherweise in der Lage, dies zu tun (Schwerter, Stiefel, Ringe, Zauber / Tränke / Besuchsschreine usw.), daher sollten diese Bestandteile sein.
Den
quelle
3

Ein Schild als physische Einheit unterscheidet sich nicht von einer anderen physischen Einheit, z. B. einer Drohne, die Sie umkreist (und die tatsächlich selbst eine Art Schild sein könnte!). Machen Sie den Schild zu einer separaten logischen Einheit (damit er seine eigenen Komponenten aufnehmen kann).

Geben Sie Ihrem Schild ein paar Komponenten: eine physische / räumliche Komponente, um die Kollisionsform darzustellen, und eine DamageAffector-Komponente, die einen Verweis auf eine Entität enthält, der jedes Mal erhöhten oder verringerten Schaden zufügt (z. B. Ihren Spielercharakter) Halten des DamageAffector nimmt Schaden. So erleidet Ihr Spieler Schaden "per Proxy".

Setze die Position der Schildeinheit bei jedem Tick auf die Position des Spielers. (Schreiben Sie eine wiederverwendbare Komponentenklasse, die dies ausführt: einmal schreiben, mehrmals verwenden.)

Sie müssen das Schildelement erstellen, z. beim Sammeln eines Powerups. Ich verwende ein allgemeines Konzept namens Emitter, eine Art Entitätskomponente, die neue Entitäten erzeugt (normalerweise durch die Verwendung einer EntityFactory, auf die sie verweist). Wo Sie sich entscheiden, den Sender zu lokalisieren, liegt bei Ihnen - z. Schalten Sie das Gerät ein und lassen Sie es auslösen, sobald es eingesammelt ist.


Sollte der Schild eine eigene Entität sein, die den Standort des Spielers verfolgt? Dies könnte es schwierig machen, die Schadensfilterung zu implementieren. Außerdem verwischen die Linien zwischen angehängten Komponenten und Entitäten.

Zwischen logischen Unterkomponenten (Raum-, KI-, Waffensteckplätze, Eingabeverarbeitung usw. usw.) und physischen Unterkomponenten besteht eine feine Grenze. Sie müssen entscheiden, auf welcher Seite Sie stehen, da dies stark definiert, welche Art von Entitätssystem Sie haben. Für mich behandelt die Physik-Unterkomponente meiner Entität die physik-hierarchischen Beziehungen (wie Gliedmaßen in einem Körper - denken Sie an Szenegraphenknoten), während die oben genannten Logik-Controller in der Regel die von Ihren Entitätskomponenten repräsentierten sind - und nicht diese repräsentieren individuelle physische "Spielpaarungen".

Ingenieur
quelle
3

Sollte der Schild eine Komponente sein, die andere Komponenten beherbergt?

Enthält möglicherweise keine anderen Komponenten, steuert jedoch die Lebensdauer der Unterkomponenten. In einem groben Pseudocode würde Ihr Client-Code diese "Schild" -Komponente hinzufügen.

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}
Tetrade
quelle
Es ist nicht klar, was thisin Ihrer Antwort bedeutet. Bezieht thissich das auf die Schildkomponente oder meintest du die Entität, die den Schild verwendet, dessen Eltern? Die Verwirrung könnte meine Schuld sein. "Komponentenbasiert" ist ein bisschen vage. In meiner Version komponentenbasierter Entitäten ist eine Entität einfach ein Komponentencontainer mit einer eigenen minimalen Funktionalität (Objektname, Tags, Messaging usw.).
deft_code
Es wäre weniger verwirrend, wenn ich gameObjectetwas benutzte oder so. Es ist ein Verweis auf das aktuelle Spielobjekt / Objekt / was auch immer die Komponenten besitzt.
Tetrad
0

Wenn Ihr Komponentensystem Scripting zulässt, kann die Shield-Komponente fast eine Superkomponente sein, die nur ein Script für den Parameter "effect" aufruft. Auf diese Weise behalten Sie die Einfachheit einer einzelnen Komponente für Shields bei und verlagern die gesamte Logik dessen, was sie tatsächlich tut, in benutzerdefinierte Skriptdateien, die von Ihren Entitätsdefinitionen an Shields übergeben werden.

Ich mache etwas Ähnliches für meine Moveable-Komponente. Sie enthält ein Feld mit einem Schlüsselreaktionsskript (eine Unterklasse von Skripten in meiner Engine). Dieses Skript definiert eine Methode, die meiner Eingabenachricht folgt. Als solches kann ich einfach so etwas in meiner Tempalte-Definitionsdatei machen

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

dann registriere ich in meiner beweglichen Komponente während der Nachrichtenregistrierung die Do-Methode der Skripte (Code in C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

Dies hängt natürlich von meiner Do-Methode ab, die dem Funktionsmuster meines RegisterHandlers folgt. In diesem Fall sein (IComponent Absender, ref Typ Argument)

also mein "script" (in meinem fall auch nur c # laufzeit kompiliert) definiert

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

und meine Basisklasse KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

dann später, wenn eine Eingabekomponente eine Nachricht vom Typ MessageTypes.InputUpdate mit dem Typ als solchem ​​sendet

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

Die Methode im Skript, die an diese Nachricht und diesen Datentyp gebunden war, wird ausgelöst und verarbeitet die gesamte Logik.

Der Code ist ziemlich spezifisch für meine Engine, aber die Logik sollte auf jeden Fall funktionieren. Ich mache dies für viele Typen, um die Komponentenstruktur einfach und flexibel zu halten.

exnihilo1031
quelle