Я намагаюся обвести голову навколо дерев поведінки, тому я вискакую деякий тестовий код. Одне з чим я борюся - це як викупити поточний вузол, що працює, коли відбувається щось більш високий пріоритет.
Розглянемо наступне просте, вигадане дерево поведінки для солдата:
Припустимо, що якась кількість кліщів пройшла повз, а поруч не було ворога, солдат стояв на траві, тому вузол Сидіти вибрано для виконання:
Тепер для дії « Сідайте» потрібен час для виконання, оскільки для цього є анімація, тому вона повертається Running
як її статус. Тик чи два проходить, анімація все ще працює, але Ворог поруч? спрацьовує вузол стану. Тепер нам потрібно стягнути якнайшвидший вузол Сидіти, щоб ми могли виконати вузол Attack . В ідеалі солдат навіть не закінчить сідати - він може замість цього змінити напрямок анімації, якби він тільки почав сидіти. Для додаткового реалізму, якщо він минув якийсь переломний момент в анімації, ми можемо замість цього вибрати, щоб дозволити йому закінчити сісти, а потім знову встати, або, можливо, змусити його спіткнутися в поспіху реагувати на загрозу.
Постарайтеся, я не зміг знайти вказівки щодо вирішення подібної ситуації. Вся література та відеозаписи, які я споживав за останні кілька днів (а їх було багато), схоже, вирішують цю проблему. Найближче, що мені вдалося знайти, - це концепція скидання запущених вузлів, але це не дає вузлам на зразок Сидіти шансу сказати "ей, я ще не закінчив!"
Я думав, можливо, визначити a 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()
зворотного дзвінка перед виходом із активних вузлів)