Verhaltensbäume verhindern

25

Ich versuche, mich in Verhaltensbäumen zurechtzufinden, also schreibe ich einen Testcode heraus. Eine Sache, mit der ich zu kämpfen habe, ist, wie man einen gerade laufenden Knoten verhindert, wenn etwas mit höherer Priorität auftaucht.

Betrachten Sie den folgenden einfachen, fiktiven Verhaltensbaum für einen Soldaten:

Bildbeschreibung hier eingeben

Angenommen, es sind einige Zecken vorbeigekommen und es war kein Feind in der Nähe, der Soldat stand auf Gras, und der Sitzknoten wurde zur Ausführung ausgewählt:

Bildbeschreibung hier eingeben

Jetzt dauert die Ausführung der Aktion " Hinsetzen" einige Zeit, da eine Animation abgespielt werden kann, die Runningals Status zurückgegeben wird. Ein oder zwei Häkchen vergehen, die Animation läuft noch, aber der Feind in der Nähe? Bedingungsknoten löst aus. Jetzt müssen wir die preempt Hinsetzen Knoten so schnell wie möglich , damit wir die ausführen kann Angriff Knoten. Im Idealfall würde der Soldat das Sitzen nicht beenden - er könnte stattdessen seine Animationsrichtung umkehren, wenn er gerade erst anfängt zu sitzen. Für zusätzlichen Realismus, wenn er einen Wendepunkt in der Animation überschritten hat, können wir stattdessen festlegen, dass er aufhört, sich zu setzen und wieder aufzustehen, oder dass er in seiner Hast stolpert, um auf die Bedrohung zu reagieren.

Wie auch immer, ich habe keine Anleitung gefunden, wie ich mit dieser Situation umgehen soll. All die Literatur und Videos, die ich in den letzten Tagen konsumiert habe (und es war eine Menge), scheinen dieses Problem zu umgehen. Das Nächste, was ich finden konnte, war das Zurücksetzen von aktiven Knoten, aber das gibt Knoten wie Sit down nicht die Möglichkeit zu sagen: "Hey, ich bin noch nicht fertig!"

Ich dachte daran, vielleicht eine Preempt()oder Interrupt()-Methode für meine Basisklasse zu definieren Node. Verschiedene Knoten können damit umgehen, wie sie es für richtig halten, aber in diesem Fall würden wir versuchen, den Soldaten so schnell wie möglich wieder auf die Füße zu bekommen und dann zurückzukehren Success. Ich denke, dieser Ansatz würde auch erfordern, dass meine Basis Nodedas Konzept der Bedingungen getrennt von anderen Aktionen hat. Auf diese Weise kann die Engine nur die Bedingungen überprüfen und, falls sie erfolgreich sind, einen aktuell ausgeführten Knoten vor der Ausführung der Aktionen deaktivieren. Wenn diese Unterscheidung nicht hergestellt würde, müsste die Engine Knoten wahllos ausführen und könnte daher eine neue Aktion auslösen, bevor die laufende Aktion verhindert wird.

Nachstehend finden Sie meine aktuellen Basisklassen. Auch dies ist eine Spitze, daher habe ich versucht, die Dinge so einfach wie möglich zu halten und die Komplexität nur dann zu erhöhen, wenn ich sie brauche und wenn ich sie verstehe, womit ich gerade zu kämpfen habe.

public enum ExecuteResult
{
    // node needs more time to run on next tick
    Running,

    // node completed successfully
    Succeeded,

    // node failed to complete
    Failed
}

public abstract class Node<TAgent>
{
    public abstract ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard);
}

public abstract class DecoratorNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent> child;

    protected DecoratorNode(Node<TAgent> child)
    {
        this.child = child;
    }

    protected Node<TAgent> Child
    {
        get { return this.child; }
    }
}

public abstract class CompositeNode<TAgent> : Node<TAgent>
{
    private readonly Node<TAgent>[] children;

    protected CompositeNode(IEnumerable<Node<TAgent>> children)
    {
        this.children = children.ToArray();
    }

    protected Node<TAgent>[] Children
    {
        get { return this.children; }
    }
}

public abstract class ConditionNode<TAgent> : Node<TAgent>
{
    private readonly bool invert;

    protected ConditionNode()
        : this(false)
    {
    }

    protected ConditionNode(bool invert)
    {
        this.invert = invert;
    }

    public sealed override ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard)
    {
        var result = this.CheckCondition(agent, blackboard);

        if (this.invert)
        {
            result = !result;
        }

        return result ? ExecuteResult.Succeeded : ExecuteResult.Failed;
    }

    protected abstract bool CheckCondition(TAgent agent, Blackboard blackboard);
}

public abstract class ActionNode<TAgent> : Node<TAgent>
{
}

Hat jemand eine Einsicht, die mich in die richtige Richtung lenken könnte? Geht mein Denken in die richtige Richtung oder ist es so naiv, wie ich befürchte?

mich--
quelle
Sie müssen sich dieses Dokument ansehen : chrishecker.com/My_liner_notes_for_spore/… hier erklärt er, wie der Baum nicht wie eine Zustandsmaschine, sondern von der Wurzel bei jedem Tick aus begangen wird, was der wahre Trick zur Reaktivität ist. BT sollte keine Ausnahmen oder Ereignisse benötigen. Sie bündeln Systeme in sich und reagieren auf alle Situationen, indem sie immer von der Wurzel nach unten fließen. So funktioniert Präemptivität. Wenn eine externe Bedingung mit höherer Priorität überprüft wird, fließt sie dorthin. (Aufruf eines Stop()Rückrufs vor dem Verlassen der aktiven Knoten)
v.oddou
Dieses aigamedev.com/open/article/popular-behavior-tree-design ist auch sehr schön detailliert
v.oddou

Antworten:

6

Ich stellte die gleiche Frage wie Sie und führte ein kurzes Gespräch im Kommentarbereich dieser Blogseite, in dem mir eine andere Lösung des Problems angeboten wurde.

Als Erstes müssen Sie einen gleichzeitigen Knoten verwenden. Concurrent Node ist ein spezieller Typ von Composite Node. Es besteht aus einer Folge von Vorbedingungsprüfungen, gefolgt von einem einzelnen Aktionsknoten. Es werden alle untergeordneten Knoten aktualisiert, auch wenn sich der Aktionsknoten im aktiven Zustand befindet. (Im Gegensatz zum Sequenzknoten, dessen Aktualisierung vom aktuell ausgeführten untergeordneten Knoten gestartet werden muss.)

Die Hauptidee besteht darin, zwei weitere Rückgabestatus für Aktionsknoten zu erstellen: "Abbrechen" und "Abgebrochen".

Das Fehlschlagen der Voraussetzungsprüfung im gleichzeitigen Knoten ist ein Mechanismus, der das Abbrechen des ausgeführten Aktionsknotens auslöst. Wenn der Aktionsknoten keine langfristige Abbruchlogik erfordert, gibt er sofort "Abgebrochen" zurück. Andernfalls wird in den Status "Abbrechen" gewechselt, in dem Sie alle erforderlichen Logik für die korrekte Unterbrechung der Aktion einfügen können.

Rokannon
quelle
Hallo und herzlich willkommen auf der GDSE. Es wäre großartig, wenn Sie die Antwort von diesem Blog hier und am Ende den Link zu diesem Blog öffnen könnten. Links neigen dazu, nicht mehr zu funktionieren, wenn sie hier vollständig beantwortet werden, wird sie dauerhafter. Die Frage hat jetzt 8 Stimmen, also wäre eine gute Antwort fantastisch.
Katu
Ich halte nichts für eine gute Lösung, das Verhaltensbäume zurück in den Zustand einer endlichen Maschine bringt. Ihr Ansatz scheint mir, als müssten Sie sich alle Austrittsbedingungen eines jeden Staates vorstellen. Wenn dies tatsächlich der Nachteil von FSM ist! BT hat den Vorteil, dass der Start an der Wurzel erfolgt. Dadurch wird implizit ein vollständig verbundener FSM erstellt, sodass wir nicht explizit Exit-Bedingungen schreiben müssen.
v.oddou
5

Ich denke, dein Soldat könnte in Körper und Geist zerlegt werden (und was auch immer). Anschließend kann der Körper in Beine und Hände zerlegt werden. Dann benötigt jeder Teil seinen eigenen Verhaltensbaum und auch eine öffentliche Schnittstelle - für Anforderungen von Teilen höherer oder niedrigerer Ebene.

Anstatt jede einzelne Aktion im Mikromodus zu verwalten, senden Sie einfach Sofortnachrichten wie "Körper, setzen Sie sich für einige Zeit" oder "Körper, rennen Sie dorthin", und der Körper verwaltet Animationen, Zustandsübergänge, Verzögerungen und andere Dinge für Sie.

Alternativ kann der Körper solche Verhaltensweisen auch selbst steuern. Wenn es keine Befehle hat, kann es den Verstand fragen, "können wir hier sitzen?". Interessanter ist, dass Sie aufgrund der Verkapselung leicht Merkmale wie Müdigkeit oder Betäubung modellieren können.

Sie können sogar Teile austauschen - Elefanten mit dem Verstand eines Zombies herstellen, dem Menschen Flügel verleihen (er merkt es nicht einmal) oder was auch immer.

Ohne eine solche Zersetzung laufen Sie wahrscheinlich früher oder später Gefahr, einer kombinatorischen Explosion zu begegnen.

Auch: http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf

Schatten im Regen
quelle
Vielen Dank. Nachdem ich Ihre Antwort dreimal gelesen habe, denke ich, dass ich sie verstehe. Ich werde dieses Wochenende dieses PDF lesen.
Ich--
1
Nachdem ich in der letzten Stunde darüber nachgedacht habe, bin ich mir nicht sicher, ob ich den Unterschied zwischen vollständig getrennten BTs für Körper und Geist und einem einzelnen BT, der in Teilbäume zerlegt ist (auf die durch einen speziellen Decorator mit Build-Time-Skripten verwiesen wird), verstehe alles zu einem großen BT zusammenbinden). Mir scheint, dies würde ähnliche Vorteile für die Abstraktion bieten und es könnte tatsächlich einfacher sein zu verstehen, wie sich eine bestimmte Entität verhält, da Sie nicht mehrere separate BTs betrachten müssen. Allerdings bin ich wahrscheinlich naiv.
Ich
@ user13414 Der Unterschied besteht darin, dass Sie spezielle Skripte benötigen, um einen Baum zu erstellen, wenn nur der indirekte Zugriff (dh wenn der Körperknoten seinen Baum fragen muss, welches Objekt Beine darstellt) ausreicht und auch kein zusätzliches Brainfuck erforderlich ist. Weniger Code, weniger Fehler. Außerdem verlieren Sie die Fähigkeit, den Teilbaum zur Laufzeit (leicht) zu wechseln. Auch wenn Sie keine solche Flexibilität benötigen, verlieren Sie nichts (einschließlich der Ausführungsgeschwindigkeit).
Schatten im Regen
3

Gestern Abend im Bett gelegen, hatte ich eine Art Offenbarung darüber, wie ich vorgehen könnte, ohne die Komplexität einzuführen, zu der ich mich in meiner Frage neigte. Es handelt sich um die Verwendung des (nach IMHO ungenannten) "parallelen" Verbundwerkstoffs. Hier ist was ich denke:

Bildbeschreibung hier eingeben

Hoffentlich ist das noch ziemlich lesbar. Die wichtigen Punkte sind:

  • Die Sit-down / Delay / Stand-up- Sequenz ist eine Sequenz innerhalb einer parallelen Sequenz ( A ). Bei jedem Tick überprüft die parallele Sequenz auch die feindliche Nahbedingung (invertiert). Wenn sich ein Feind in der Nähe befindet, schlägt die Bedingung fehl, und dies gilt auch für die gesamte parallele Sequenz (sofort, auch wenn sich die untergeordnete Sequenz in der Mitte von Sitzen , Verzögern oder Aufstehen befindet ).
  • Bei einem Fehler springt der Selektor B über der parallelen Sequenz nach unten in den Selektor C , um die Unterbrechung zu handhaben. Wichtig ist, dass Selektor C nicht ausgeführt wird, wenn die parallele Sequenz A erfolgreich abgeschlossen wurde
  • Selektor C versucht dann normal aufzustehen, kann aber auch eine Stolperanimation auslösen, wenn sich der Soldat gerade in einer zu ungünstigen Position befindet, um einfach aufzustehen

Ich denke, dass dies funktionieren wird (ich werde es bald in meinem Spike versuchen), obwohl es etwas chaotischer ist, als ich es mir vorgestellt hatte. Das Gute ist, dass ich in der Lage sein würde, Teilbäume als wiederverwendbare Teile der Logik zu kapseln und von mehreren Punkten aus auf sie zu verweisen. Das wird die meisten meiner Sorgen dort lindern, daher halte ich dies für eine praktikable Lösung.

Natürlich würde ich immer noch gerne hören, ob jemand darüber nachdenkt.

UPDATE : obwohl dieser Ansatz technisch funktioniert, habe ich es sux entschieden. Dies liegt daran, dass nicht verwandte Teilbäume die in anderen Teilen des Baums definierten Bedingungen "kennen" müssen, damit sie ihren eigenen Untergang auslösen können. Das Teilen von Teilbaumreferenzen würde zwar dazu beitragen, diesen Schmerz zu lindern, aber es widerspricht immer noch den Erwartungen beim Betrachten des Verhaltensbaums. In der Tat habe ich denselben Fehler zweimal an einem sehr einfachen Spike gemacht.

Daher gehe ich den anderen Weg: explizite Unterstützung für das Preempting innerhalb des Objektmodells und ein spezielles Composite, das die Ausführung einer anderen Gruppe von Aktionen ermöglicht, wenn das Preempting auftritt. Ich werde eine separate Antwort posten, wenn etwas funktioniert.

mich--
quelle
1
Wenn Sie Teilbäume wirklich wiederverwenden möchten, sollte die Logik für den Zeitpunkt der Unterbrechung ("Feind in der Nähe" hier) vermutlich nicht Teil des Teilbaums sein. Stattdessen kann das System möglicherweise einen Teilbaum (z. B. B hier) auffordern, sich aufgrund eines Stimulus mit höherer Priorität zu unterbrechen, und dann zu einem speziell markierten Unterbrechungsknoten (C hier) springen, der es handhaben würde, den Charakter in einen Standardzustand zurückzubringen zB stehend. Ein bisschen wie das Verhaltensbaumäquivalent der Ausnahmebehandlung.
Nathan Reed
1
Sie können sogar mehrere Interrupt-Handler einbinden, je nachdem, welcher Stimulus unterbrochen wird. Wenn der NPC zum Beispiel sitzt und anfängt zu feuern, möchten Sie vielleicht nicht, dass er aufsteht (und ein größeres Ziel präsentiert), sondern dass er niedrig bleibt und in Deckung geht.
Nathan Reed
@ Nathan: witzig du erwähnst "ausnahmebehandlung". Der erste mögliche Ansatz, den ich mir letzte Nacht ausgedacht habe, war die Idee eines Preempt-Composites, das zwei Kinder haben würde: eines für die normale Ausführung und eines für die vorzeitige Ausführung. Wenn das normale Kind besteht oder scheitert, wird dieses Ergebnis weitergegeben. Das vorgezogen Kind würde immer nur rennen, wenn es vorgezogen würde. Alle Knoten hätten eine Preempt()Methode, die durch den Baum sickern würde. Das einzige, was dies wirklich "handhaben" würde, wäre das Preempt-Composite, das sofort zu seinem Preempt-Child-Knoten wechseln würde.
Ich--
Dann dachte ich an den parallelen Ansatz, den ich oben skizziert habe, und das schien eleganter zu sein, da für die API kein zusätzliches Crufting erforderlich ist. Was die Verkapselung von Teilbäumen angeht, denke ich, dass dies ein möglicher Substitutionspunkt ist, wo immer Komplexität auftritt. Dies kann sogar der Fall sein, wenn Sie mehrere Bedingungen haben, die häufig zusammen geprüft werden. In diesem Fall wäre die Wurzel der Substitution eine zusammengesetzte Sequenz mit mehreren Bedingungen als untergeordneten Bedingungen.
Ich--
Ich denke, dass Teilbäume, die die Bedingungen kennen, die sie "treffen" müssen, bevor sie ausgeführt werden, vollkommen angemessen sind, da sie dadurch eigenständig und sehr explizit gegenüber implizit sind. Wenn das ein größeres Problem ist, lassen Sie die Bedingungen nicht im Teilbaum, sondern an der "Aufrufstelle" des Teilbaums.
Seivan
2

Hier ist die Lösung, für die ich mich jetzt entschieden habe ...

  • Meine Basisklasse Nodehat eine InterruptMethode, die standardmäßig nichts tut
  • Bedingungen sind "erstklassige" Konstrukte, da sie zurückgegeben werden müssen bool(was bedeutet, dass sie schnell ausgeführt werden können und niemals mehr als ein Update benötigen).
  • Node macht eine Auflistung von Bedingungen separat zu ihrer Auflistung von untergeordneten Knoten verfügbar
  • Node.ExecuteFührt zuerst alle Bedingungen aus und schlägt sofort fehl, wenn eine Bedingung fehlschlägt. Wenn die Bedingungen erfolgreich sind (oder es keine gibt), ruft es auf, ExecuteCoredamit die Unterklasse ihre eigentliche Arbeit erledigen kann. Es gibt einen Parameter, der das Überspringen von Bedingungen aus den nachfolgend aufgeführten Gründen ermöglicht
  • NodeErmöglicht auch die isolierte Ausführung von Bedingungen über eine CheckConditionsMethode. Natürlich Node.Executeruft eigentlich nur an, CheckConditionswenn es um die Validierung von Bedingungen geht
  • Mein SelectorComposite ruft jetzt CheckConditionsjedes Kind auf, das es für die Ausführung in Betracht zieht. Wenn die Bedingungen nicht erfüllt sind, bewegt es sich direkt zum nächsten Kind. Wenn sie bestehen, wird geprüft, ob bereits ein ausführendes Kind vorhanden ist. Wenn ja, ruft es auf Interruptund schlägt dann fehl. Das ist alles, was es an diesem Punkt tun kann, in der Hoffnung, dass der aktuell laufende Knoten auf die Interrupt-Anfrage reagiert, was es tun kann, indem es ...
  • Ich habe einen InterruptibleKnoten hinzugefügt , der eine Art Spezialdekorateur ist, weil er den regulären Logikfluss als dekoriertes Kind hat und dann einen separaten Knoten für Unterbrechungen. Es führt sein reguläres Kind bis zur Vollendung oder zum Scheitern aus, solange es nicht unterbrochen wird. Bei einer Unterbrechung wird sofort auf die Ausführung des untergeordneten Knotens für die Unterbrechungsbehandlung umgeschaltet, der ein beliebig komplexer Teilbaum sein kann

Das Endergebnis ist ungefähr so, entnommen aus meinem Dorn:

Bildbeschreibung hier eingeben

Das Obige ist der Verhaltensbaum für eine Biene, die Nektar sammelt und in ihren Bienenstock zurückbringt. Wenn es keinen Nektar hat und nicht in der Nähe einer Blume ist, die einige hat, wandert es:

Bildbeschreibung hier eingeben

Wenn dieser Knoten nicht unterbrechbar wäre, würde er niemals ausfallen, sodass die Biene immer weiter wandern würde. Da der übergeordnete Knoten jedoch ein Selektor ist und untergeordnete Knoten mit höherer Priorität hat, wird die Berechtigung zur Ausführung ständig überprüft. Wenn ihre Bedingungen erfüllt sind, löst der Selektor eine Unterbrechung aus, und der darüber liegende Unterbaum wechselt sofort zum Pfad "Unterbrochen", der bei einem Fehler so schnell wie möglich beendet wird. Es könnte natürlich zuerst einige andere Aktionen ausführen, aber mein Spike hat eigentlich nichts anderes zu tun als Kaution.

Um dies mit meiner Frage in Verbindung zu bringen, könnten Sie sich vorstellen, dass der "Unterbrochene" Pfad versuchen könnte, die Sitzanimation umzukehren, und andernfalls den Soldaten stolpern lassen. All dies würde den Übergang in den Zustand höherer Priorität aufhalten, und genau das war das Ziel.

Ich denke, ich bin mit diesem Ansatz zufrieden - insbesondere mit den Kernelementen, die ich oben skizziere -, aber um ehrlich zu sein, wirft er weitere Fragen zur Verbreitung spezifischer Implementierungen von Bedingungen und Aktionen und zur Einbindung des Verhaltensbaums in das Animationssystem auf. Ich bin mir nicht mal sicher, ob ich diese Fragen noch artikulieren kann, also werde ich weiter nachdenken.

mich--
quelle
1

Ich habe das gleiche Problem behoben, indem ich den "Wann" -Dekorateur erfunden habe. Es hat eine Bedingung und zwei kindliche Verhaltensweisen ("dann" und "anders"). Wenn "When" ausgeführt wird, prüft es den Zustand und läuft je nach Ergebnis dann / sonst child. Wenn sich das Ergebnis der Bedingung ändert, wird das laufende untergeordnete Element zurückgesetzt und das dem anderen Zweig entsprechende untergeordnete Element gestartet. Wenn das Kind die Ausführung beendet, beendet das ganze "Wann" die Ausführung.

Der entscheidende Punkt ist, dass im Gegensatz zum anfänglichen BT in dieser Frage, bei dem der Zustand nur beim Start der Sequenz überprüft wird, mein "Wann" den Zustand weiterhin überprüft, während er ausgeführt wird. Daher wird der obere Rand des Verhaltensbaums durch Folgendes ersetzt:

When[EnemyNear]
  Then
    AttackSequence
  Otherwise
    When[StandingOnGrass]
      Then
        IdleSequence
      Otherwise
        Hum a tune

Für fortgeschrittenere "Wann" -Verwendung würde man auch eine "Warte" -Aktion einführen wollen, die einfach für einen bestimmten Zeitraum oder auf unbestimmte Zeit nichts tut (bis sie durch das übergeordnete Verhalten zurückgesetzt wird). Wenn Sie nur einen Zweig von "Wann" benötigen, kann der andere entweder "Erfolg" - oder "Fehlgeschlagen" -Aktionen enthalten, die jeweils erfolgreich sind und sofort fehlschlagen.

Slonopotamus
quelle
Ich denke, dieser Ansatz ist dem, was die ursprünglichen Erfinder von BT im Sinn hatten, näher. Es wird ein dynamischerer Fluss verwendet, weshalb der Status "Laufen" in BT ein sehr gefährlicher Status ist, der sehr selten verwendet werden sollte. Wir sollten BTs immer unter Berücksichtigung der Möglichkeit entwerfen, jederzeit wieder an die Wurzel zu kommen.
v.oddou
0

Ich bin zwar spät dran, hoffe aber das kann helfen. Vor allem, weil ich sicherstellen möchte, dass ich persönlich nichts verpasst habe, da ich auch versucht habe, dies herauszufinden. Ich habe diese Idee größtenteils von ausgeliehen Unreal, aber ohne sie zu einer DecoratorEigenschaft auf einer Basis zu machen Nodeoder stark mit der verbunden zu sein Blackboard, ist sie allgemeiner.

Dies wird einen neuen Knotentyp genannt einzuführen , Guarddie wie eine Kombination aus a ist Decorator, und ist Compositeund eine condition() -> ResultUnterschrift neben einemupdate() -> Result

Es gibt drei Modi, die angeben, wie die Stornierung bei der GuardRückgabe erfolgen soll, Successoder Faileddie tatsächliche Stornierung hängt vom Anrufer ab. Also für einen SelectorAnruf ein Guard:

  1. Abbrechen .self -> Bricht den Vorgang Guard(und sein laufendes untergeordnetes Element) nur ab, wenn er ausgeführt wird und die Bedingung erfüllt istFailed
  2. Abbrechen .lower-> Brechen Sie die Knoten mit niedrigerer Priorität nur ab, wenn sie ausgeführt werden und die Bedingung Successoder warRunning
  3. Abbrechen .both -> Beides .selfund .lowerabhängig von den Bedingungen und laufenden Knoten. Sie möchten self stornieren, wenn es ausgeführt wird, und würden falseden ausgeführten Knoten bedingen oder stornieren, wenn sie aufgrund der CompositeRegel als niedriger eingestuft werden ( Selectorin unserem Fall), wenn die Bedingung erfüllt ist Success. Mit anderen Worten, es sind im Grunde beide Konzepte kombiniert.

So Decoratoroder Compositeso braucht es nur ein einziges Kind.

Obwohl Guardnehmen nur ein einziges Kind, können Sie nisten so viele Sequences, Selectorsoder andere Arten , Nodeswie Sie wollen, einschließlich anderer Guardsoder Decorators.

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Sequence2 StandingOnGrass? Idle HumATune

Im obigen Szenario werden bei Selector1Aktualisierungen immer Zustandsüberprüfungen für die den untergeordneten Elementen zugeordneten Wachen ausgeführt. Im obigen Fall Sequence1ist es bewacht und muss überprüft werden, bevor Selector1mit den runningAufgaben fortgefahren wird.

Jedes Mal , wenn Selector2oder Sequence1läuft, sobald EnemyNear?kehrt successwährend einer Guards condition()Überprüfung dann Selector1eine Unterbrechung Ausgabe / cancel auf die running nodeund dann wie gewohnt weiter.

Mit anderen Worten, wir können auf einen "Leerlauf" - oder einen "Angriff" -Zweig reagieren, basierend auf ein paar Bedingungen, die das Verhalten weitaus reaktiver machen, als wenn wir uns entschieden hätten Parallel

Auf diese Weise können Sie auch Einzelpersonen Nodemit höherer Priorität davor schützen , Nodesin derselben zu laufenComposite

Selector1 Guard.both[Sequence[EnemyNear?]] Sequence1 MoveToEnemy Attack Selector2 Guard.both[StandingOnGrass?] Idle HumATune

Wenn HumATunees ein langer Lauf ist Node, Selector2wird immer zuerst geprüft, ob es nicht für die Guard. Wenn der npc also beim nächsten Mal auf eine Rasenfläche teleportiert Selector2wird, überprüft er das Guardund bricht HumATuneab, um zu laufenIdle

Wenn es aus dem Grasfeld teleportiert wird, bricht es den laufenden Knoten ( Idle) ab und bewegt sich zuHumATune

Wie Sie hier sehen, hängt die Entscheidungsfindung vom Anrufer Guardund nicht von ihm Guardselbst ab. Die Regeln, wer als wer gilt, lower priorityverbleiben beim Anrufer. In beiden Beispielen ist es derjenige, Selectorder definiert, was a ausmacht lower priority.

Wenn Sie einen CompositeAufruf hätten Random Selector, würden Sie die Regeln innerhalb der Implementierung dieses spezifischen definieren Composite.

Seivan
quelle