Деревья поведения в EyeAuras тесно интегрированы со встроенной системой скриптов. Каждый узел Execute Script — это, по сути, небольшая программа, в которой можно использовать любые команды. Вы можете отправлять ввод, рисовать на экране, показывать уведомления и в целом пользоваться всей мощью API EyeAuras.
Давайте добавим в дерево узел Execute Script:

По умолчанию этот узел выводит в лог "Hello, World" и возвращает Success, потому что условие истинно:
1 + 1 = 2
эквивалентно:
return 1 + 1 == 2;
что, в свою очередь, эквивалентно:
return true;
или:
return NodeStatus.Success;
Каждый узел в дереве поведения может вернуть один из трёх статусов:
Success — задача успешно выполнена (например, объект найден на экране).Failure — задача не выполнена (например, Selector не смог найти дочерний узел, который вернул бы успех).Running — задача всё ещё выполняется (например, Wait возвращает Running, пока не истечёт время ожидания).Если узел возвращает Running, дерево вызовет его снова на следующем тике. Есть исключения — например, узел Interrupter может прерывать узлы, находящиеся в состоянии Running.
В C# есть интерфейс IEnumerator, который представляет последовательность значений. В нашем случае особенно важен оператор yield return.
С помощью yield return можно создавать узлы, которые сохраняют внутреннее состояние между тиками:
Log.Info("Hello, world!");
return Run();
private IEnumerator<NodeStatus> Run()
{
var tickIndex = 0;
while (!cancellationToken.IsCancellationRequested)
{
tickIndex++;
Log.Info($"Tick #{tickIndex}");
if (tickIndex % 3 == 0)
{
yield return NodeStatus.Running;
}
else if (tickIndex % 5 == 0)
{
yield return NodeStatus.Success;
}
else
{
yield return NodeStatus.Failure;
}
}
}
Если вы вызовете этот скрипт несколько раз, увидите примерно такой результат:
Tick #1 → Failure
Tick #2 → Failure
Tick #3 → Running
Tick #4 → Failure
Tick #5 → Success
Раньше для такой логики с сохранением состояния приходилось использовать Variables или Blackboard. Теперь сам скрипт живёт между тиками.
Представим, что вам нужно подойти к NPC (MapDevice) и начать диалог кликом по нему. Такое взаимодействие происходит не мгновенно: персонажу нужно подойти, а затем дождаться появления окна диалога.
Логика будет такой:
MapDevice) в списке ближайших сущностейprivate IEnumerator<NodeStatus> Run()
{
var mapDevice = GameController.Entities
.Where(x => x.Type is EntityType.IngameIcon && x.IsTargetable && x.TryGetComponent<MinimapIcon>(out var minimapIcon) && minimapIcon.Name == "MapDevice")
.FirstOrDefault();
if (mapDevice == null)
{
Log.Warn("Map Device not found");
yield return NodeStatus.Failure;
yield break;
}
const float minDistance = 20;
if (mapDevice.DistancePlayer > minDistance)
{
PF.AddDestination(mapDevice.GridPos, PfDestinationType.Interact, minDistance);
while (mapDevice.DistancePlayer > minDistance)
{
yield return NodeStatus.Running;
}
}
SendInput.MouseClick(CameraManager.WorldToScreen(mapDevice.BoundsCenterPos).ToPoint());
while (!AtlasManager.IsActive)
{
yield return NodeStatus.Running;
}
yield return NodeStatus.Success;
}
Именно так появляются корутины внутри деревьев поведения — почти как Unity Coroutines.
В итоге деревья поведения становятся компактнее, проще в поддержке и лучше реагируют на происходящее, при этом сохраняя внутреннее состояние между тиками.