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:
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:
Jetzt dauert die Ausführung der Aktion " Hinsetzen" einige Zeit, da eine Animation abgespielt werden kann, die Running
als 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 Node
das 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?
Stop()
Rückrufs vor dem Verlassen der aktiven Knoten)