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

По умолчанию этот узел выводит в лог "Hello, World" и возвращает Success, потому что условие истинно:
1 + 1 = 2
эквивалентно:
return 1 + 1 == 2;
что, в свою очередь, эквивалентно:
return true;
или:
return NodeStatus.Success;
NodeStatus
Каждый узел в дереве поведения может вернуть один из трёх статусов:
Success— задача успешно выполнена (например, объект найден на экране).Failure— задача не выполнена (например,Selectorне смог найти дочерний узел, который вернул бы успех).Running— задача всё ещё выполняется (например,WaitвозвращаетRunning, пока не истечёт время ожидания).
Если узел возвращает Running, дерево вызовет его снова на следующем тике. Есть исключения — например, узел Interrupter может прерывать узлы, находящиеся в состоянии Running.
IEnumerator и yield return
В 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) и начать диалог кликом по нему. Такое взаимодействие происходит не мгновенно: персонажу нужно подойти, а затем дождаться появления окна диалога.
Логика будет такой:
- Найти NPC (
MapDevice) в списке ближайших сущностей - Проверить, далеко ли игрок находится от NPC
- Если далеко — начать движение к нему
- Когда подойдёте достаточно близко — кликнуть по нему и дождаться открытия диалога
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.
В итоге деревья поведения становятся компактнее, проще в поддержке и лучше реагируют на происходящее, при этом сохраняя внутреннее состояние между тиками.