Я пытаюсь разобраться с деревьями поведения, поэтому выкручиваю тестовый код. Одна вещь, с которой я борюсь, это как выгрузить текущий работающий узел, когда появляется что-то с более высоким приоритетом.
Рассмотрим следующее простое, вымышленное дерево поведения для солдата:
Предположим, что какое-то количество тиков прошло, а поблизости нет врагов, солдат стоял на траве, поэтому для выполнения выбран узел Sit down :
Теперь для выполнения действия Sit down требуется время, поскольку анимация воспроизводится, поэтому она возвращается в Running
качестве своего состояния. Проходит один или два тика, анимация все еще работает, но Враг рядом? условие узла срабатывает. Теперь нам нужно выгрузить узел Sit down как можно скорее, чтобы мы могли выполнить узел Attack . В идеале солдат даже не успел бы сесть - вместо этого он мог бы изменить направление анимации, если бы только начал сидеть. Для дополнительного реализма, если он преодолел какой-то переломный момент в анимации, мы могли бы вместо этого выбрать, чтобы он закончил сесть, а затем снова встал, или, возможно, он споткнулся в своей спешке, чтобы отреагировать на угрозу.
Как ни старайся, мне не удалось найти руководство, как справиться с такой ситуацией. Вся литература и видео, которые я употреблял за последние несколько дней (и их было много), кажется, обошли вокруг этой проблемы. Самая близкая вещь, которую я смог найти, - это концепция сброса работающих узлов, но это не дает таким узлам, как Sit down, возможность сказать «эй, я еще не закончил!»
Я подумал о том, чтобы определить метод Preempt()
или Interrupt()
в моем базовом Node
классе. Разные узлы могут справиться с этим так, как считают нужным, но в этом случае мы попытаемся вернуть солдата на ноги как можно скорее, а затем вернуться Success
. Я думаю, что этот подход также потребовал бы, чтобы моя база Node
имела концепцию условий отдельно от других действий. Таким образом, механизм может проверять только условия и, если они пройдут, выгрузить любой выполняющийся в данный момент узел перед началом выполнения действий. Если это разграничение не было установлено, движок должен будет выполнять узлы без разбора и, следовательно, может инициировать новое действие, прежде чем прервать работающее.
Для справки ниже приведены мои текущие базовые классы. Опять же, это всплеск, поэтому я попытался сделать вещи максимально простыми и добавить сложность только тогда, когда мне это нужно, и когда я это понимаю, с чем я сейчас борюсь.
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>
{
}
Есть ли у кого-нибудь понимание, которое может направить меня в правильном направлении? Мое мышление в правильном направлении, или это так наивно, как я боюсь?
Stop()
обратного вызова перед выходом из активных узлов)