Форум программистов, компьютерный форум, киберфорум
GameUnited
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Разработка продвинутого ИИ в Unity с использованием Behavior Graph

Запись от GameUnited размещена 19.03.2025 в 09:12
Показов 4211 Комментарии 1
Метки .net, behavior graph, c#, unity

Нажмите на изображение для увеличения
Название: 623f9ebd-f396-4241-9434-ffce70964dc7.jpg
Просмотров: 316
Размер:	170.0 Кб
ID:	10456
В разработке игр искусственный интеллект персонажей часто становится тем элементом, который превращает хорошую игру в выдающуюся. До недавнего времени разработчикам под Unity приходилось либо писать собственные системы ИИ с нуля, либо покупать готовые решения в Asset Store. Ситуация кардинально изменилась с выходом пакета Unity Behavior – инструмента, который заполнил серьезный пробел в экосистеме Unity.

Выпущенный в версии 1.0.3, Unity Behavior представляет собой графовый инструмент для реализации логики искусственного интеллекта. И хотя он еще находится в ранней стадии развития (я лично натыкался на пару мелких багов), команда разработки оперативно исправляет недочеты. Могу с уверенностью сказать – сейчас идеальное время познакомиться с этой технологией, поскольку она имеет все шансы стать одним из важнейших инструментов Unity.

Что такое Behavior Graph?



Behavior Graph – это визуальный инструмент для создания графов поведения, где узлы содержат определенную логику. Такие графы можно прикреплять к игровым объектам, позволяя им динамически изменять свое поведение в зависимости от состояния игры. Хотя название может наводить на мысль, что это просто графическая версия деревьев поведения (behavior trees), функционал Unity Behavior выходит далеко за эти рамки. Он позволяет расширять древовидную структуру в полноценную иерархическую систему с подграфами, включающими пользовательское поведение. Эти компоненты, соединяясь между собой, формируют комплексные системы ИИ, способные решать самые разнообразные задачи – от управления NPC до контроля состояния меню или даже низкоуровневых структур данных.

Разьясните Behavior
Почитал https://msdn.microsoft.com/ru-ru/library/system.windows.dependencyproperty(v=vs.110).aspx http://habrahabr.ru/post/254887/...

Подскажите в чем ошибка ? (basic graph)
namespace temperatura { public partial class Form1 : Form { private double d; private void drawGraphic(object...

Ошибка: annot add object with apartment model behavior to the application intrinsic object.
При выполнении кода: IF VarType(Application('presents')) <> vbObject then Set Application('presents') =...

Как использовать компонент Microsoft Graph 2000 в ASP?
Привет всем. Кто может подсказат как исползоват Microsoft Graph 2000 . Или подсказат другои алтернативни рещени при работе с графиком. Спосибо.


Преимущества над классическими решениями



В отличие от других ИИ-решений в Unity, например ML Agents, которые нацелены на решение узкого спектра задач, Behavior Graph предлагает гораздо более гибкий подход. Графы поведения можно использовать для широчайшего спектра задач:
  • Создание NPC с продвинутой логикой принятия решений.
  • Управление состояниями интерфейса.
  • Организация игровых механик и систем.
  • Реализация сложных алгоритмов в визуальном формате.

Главное преимущество Behavior Graph в том, что он не ограничен работой в изоляции – графы могут взаимодействовать друг с другом и с любыми данными, отражающими состояние игры. Это могут быть как примитивные переменные, так и события или даже результаты выполнения других графов.

Сравнение с деревьями поведения



Традиционные деревья поведения представляют собой иерархические структуры узлов, которые при проходе сверху вниз формируют логику поведения агента или объекта. Реализация ИИ с помощью деревьев поведения – это итеративный процесс. Простой узел может позже превратиться в целое поддерево с более сложной логикой. Расширение дерева по вертикали добавляет детализацию логики, а по горизонтали – альтернативные варианты поведения. Важно понимать, что ни деревья поведения, ни Unity Behavior Graph не обрабатывают все свои узлы за один игровой тик. Они распределяют выполнение на несколько игровых циклов, что делает деревья поведения эффективными – их сложность не увеличивает вычислительную нагрузку.

Ключевое отличие между традиционными деревьями поведения и Unity Behavior Graph заключается в том, что деревья обычно обходят путь от корня до нужного узла в каждом игровом цикле. В противовес этому, Behavior Graph предлагает узлы, ограничивающие обход – например, узлы, повторяющие только определенные части графа, пока не будет выполнено условие.

В классическом дереве поведения узлы возвращают три состояния: Успех, Неудача или Выполнение. В Unity Behavior узлы могут возвращать пять состояний: Успех, Неудача, Выполнение, Ожидание и Инициализация. Статус "Выполнение" особенно важен – он сигнализирует, что узел продолжит выполняться в следующем игровом цикле. Например, узел, отвечающий за патрулирование, будет возвращать "Выполнение", пока агент патрулирует, и в конечном итоге вернет Успех или Неудачу в зависимости от того, достиг ли агент места назначения. Эти статусы передаются родительскому узлу, что позволяет выстраивать логику как по вертикали, так и по горизонтали. Успех или неудача одного узла влияет на выполнение его родительских и соседних узлов, создавая сложную, но гибкую систему принятия решений.

Если вас интересует реализация ИИ в играх – Unity Behavior Graph открывает новые горизонты, сочетая наглядность визуального программирования с мощью и гибкостью графовых структур.

Технические основы Behavior Graph



Прежде чем углубляться в практические аспекты, стоит разобраться в структуре Behavior Graph. В отличие от простого набора соединённых узлов, Behavior Graph представляет собой целостную систему, спроектированную для организации сложной логики ИИ в понятном визуальном формате. Поведенческие графы имеют три основные категории узлов: композитные, декораторы и листья. В Unity Behavior Graph эта структура несколько видоизменена, но базовые принципы сохраняются.

Типы узлов и их назначение



Узлы действий (Action nodes) формируют основу логики. В Behavior Graph они могут иметь дочерний элемент, что отличает их от классических деревьев поведения. Фактически, каждый листовой узел может состоять из нескольких узлов действий, выстроенных вертикально. Это значит, что действия комбинируются, хотя и остаются независимыми в плане выполнения. Если верхний узел возвращает статус "Выполнение", нижележащие узлы не начнут работу, пока вышестоящий узел не изменит свой статус. Существует множество типов узлов действий:
  • Узлы для получения и установки переменных.
  • Узлы управления агентами (перемещение, поворот).
  • Узлы для работы с Unity-специфичными механиками (навигация, физика, взаимодействие с MonoBehaviour, управление сценами).

Узлы-модификаторы (Modifier nodes) изменяют получаемый результат. Например, модификатор может инвертировать успех на неудачу или наоборот. Звучит странно? На самом деле это крайне полезно для повторного использования узлов без дублирования кода. Представьте композитный узел, ожидающий, пока все его дочерние элементы завершат выполнение, и возвращающий успех только если все дочерние узлы успешны. Такой узел можно использовать для проверки безопасности здания, возвращая успех, если все двери и окна закрыты. Если узел действия возвращает успех, когда дверь открыта, вместо переписывания логики для проверки закрытия двери, можно использовать узел-инвертор, который поменяет результат перед отправкой его родительскому композитному узлу.

Композитные узлы (Composite nodes) – это основа принятия решений в графе. Если узлы действий отвечают за "как", то композитные узлы определяют "если" и "когда". Они содержат несколько дочерних элементов и возвращают результат на основе их исходов. Некоторые композитные узлы возвращают успех, если хотя бы один дочерний элемент успешен, другие – если все дочерние элементы успешны, третьи позволяют дочерним элементам выполняться параллельно, а четвертые – последовательно, но только при успехе предыдущего дочернего элемента.

Условные узлы (Conditional nodes) включают, например, узлы "if", выполняющие разные ветви в зависимости от булева выражения, и узлы "switch", выполняющие разные ветви в зависимости от значения перечисления. Также есть узлы повторения, которые многократно выполняют свою дочернюю ветвь на основе условия (аналогично циклу while в программировании), узлы задержки, откладывающие выполнение, и многие другие.

Узлы событий (Event nodes) особенно полезны для передачи событий не только между узлами в пределах одного графа, но и между разными графами. Это создает мощный механизм обмена информацией и координации логики между различными компонентами игры.

Blackboard и Inspector – хранилища переменных и настроек



В Behavior Graph события, переменные и условия можно управлять в двух местах: на доске (blackboard) и в инспекторе.
Blackboard хранит все переменные, используемые в графе. Эти переменные применяются в разных местах графа и отвечают за логику и поток управления. Каждая переменная в blackboard, помимо типа и значения, имеет два дополнительных булевых свойства:
1. Exposed (Открытый) – когда установлено в true, делает переменную видимой в инспекторе Unity для назначения значения. По функциональности похоже на атрибут "SerializeField" в коде.
2. Shared (Общий) – когда включено, делает переменную доступной во всех экземплярах конкретного графа.

Эти переменные можно перетаскивать на узлы, которым они требуются, что значительно упрощает организацию логики. Каждый граф можно использовать на множестве игровых объектов в сцене. Это возможно потому, что граф назначается компоненту Behavior Agent, который принимает граф в качестве параметра и отображает его "открытые" переменные в инспекторе.

Inspector внутри редактора графов показывает подробности для каждого узла, позволяя изменять его поведение. Например, для узла "If" инспектор позволяет задавать условия. Это крайне полезно, поскольку устраняет необходимость создавать программно разные узлы "If" в зависимости от количества условий. Вместо этого один и тот же узел можно повторно использовать с различными условиями, определёнными непосредственно в инспекторе.

Условный узел "If" может иметь несколько условий, а инспектор позволяет указать, как оценивать эти условия – выполнять ветвь, если любое условие истинно (эквивалент "OR" в программировании) или если все условия истинны (эквивалент "AND"). Кнопка "Inspect Script" позволяет просмотреть код узла, разработанного командой Unity. Если же мы создали собственные узлы, эта кнопка меняется на "Edit Script", что даёт возможность редактировать реализацию узла прямо в IDE.

Наконец, окно Unity Graph Behavior позволяет использовать заметки-стикеры, содержащие напоминания и другую полезную информацию о нашем графе, что существенно улучшает документирование и организацию сложных поведенческих систем.

Дискретные состояния и переходы как основа Behavior Graph



Behavior Graph можно рассматривать как систему дискретных состояний с четко определенными переходами между ними. Этот принцип лежит в основе всей архитектуры графов поведения. Каждый узел представляет некое состояние, а результат его выполнения определяет, какой переход будет активирован. Такая модель особенно хорошо подходит для реализации игрового ИИ, где большинство решений принимается на основе конечного набора входных данных.

В классическом дереве поведения мы обычно строим вертикальную иерархию узлов, где решения принимаются последовательно сверху вниз. Unity Behavior Graph расширяет эту концепцию, добавляя возможность горизонтальных связей через специальные узлы объединения (join nodes), которые преобразуют дерево в полноценный граф. Это значительно увеличивает гибкость системы, позволяя реализовывать такие паттерны, как повторное использование подграфов или условные переходы между различными частями графа. Например, традиционная последовательность действий "проверить наличие патронов → прицелиться → выстрелить" в дереве поведения будет строго линейной. Если патроны закончились, вся ветка завершится с ошибкой. В Behavior Graph мы можем добавить альтернативный путь: "если патронов нет → перезарядиться → вернуться к прицеливанию", создавая таким образом более сложную, но и более реалистичную модель поведения.

Такая структура позволяет строить ИИ, который не просто следует заранее заданной последовательности команд, а способен адаптироваться к меняющимся условиям игры. Это особенно ценно в проектах с открытым миром или процедурно-генерируемым контентом, где заранее предсказать все возможные ситуации невозможно.

Реактивность и обработка событий в графе поведения



Одним из ключевых преимуществ Behavior Graph является его реактивная природа. Графы могут реагировать на внешние события, поступающие от игровых объектов, других графов или системных компонентов Unity. Это создает динамическую среду, где ИИ постоянно адаптируется к изменяющимся условиям игры. Механизм событий реализован через специальные узлы событий (Event nodes), которые могут как отправлять, так и принимать сигналы. Эти узлы формируют своеобразную нервную систему графа, обеспечивая коммуникацию между его частями и внешним миром.

Представьте сценарий с охранником в стелс-игре. Когда игрок входит в поле зрения NPC, генерируется событие "игрок замечен". Это событие может активировать соответствующий узел в графе поведения, который переключает охранника из режима патрулирования в режим преследования. При этом другие охранники могут получить то же событие и начать искать игрока, даже если сами его не видели. Такой подход позволяет создавать сложные поведенческие паттерны без необходимости писать тонны кода.

Ещё одна мощная функция – возможность прерывания текущего выполнения. Узлы в Behavior Graph могут быть настроены так, чтобы определенные события прерывали их работу независимо от текущего состояния. Например, если охранник патрулирует территорию и услышал подозрительный шум, логика патрулирования может быть немедленно прервана, и активирована ветка исследования источника шума. После завершения проверки выполнение может вернуться к исходной точке патрулирования.

Такая система прерываний делает поведение ИИ более естественным и реалистичным. NPC перестают быть механическими автоматами, выполняющими заранее заданные последовательности действий, и начинают реагировать на мир вокруг них почти как живые существа.

Механизмы параллельного выполнения задач



В отличие от традиционных деревьев поведения, где узлы выполняются строго последовательно, Behavior Graph предлагает мощные инструменты для параллельного выполнения задач. Это открывает новые возможности для создания сложных поведенческих паттернов, где персонаж может одновременно выполнять несколько действий. Параллельное выполнение реализуется через специальные композитные узлы, которые запускают все свои дочерние ветви одновременно. Каждая ветвь работает независимо, сохраняя собственное состояние и скорость выполнения. Финальный результат узла определяется политикой завершения – например, узел может вернуть успех, когда все дочерние ветви успешно завершены, или когда хотя бы одна из них успешна.

Приведу пример. Представьте NPC, который должен следить за определенной территорией, одновременно патрулируя её. В Behavior Graph мы можем создать параллельный узел с двумя дочерними ветвями:
1. Первая ветвь отвечает за перемещение персонажа по заданному маршруту патрулирования.
2. Вторая ветвь постоянно проверяет окружение на наличие подозрительных объектов или врагов.

Обе ветви выполняются одновременно, что создает естественное поведение: NPC идет по маршруту и в то же время оглядывается по сторонам. Если вторая ветвь обнаруживает что-то подозрительное, она может вернуть соответствующий статус, который будет обработан родительским узлом согласно его политике. Такой подход позволяет избежать "роботизированного" поведения, когда персонаж сначала идет, потом осматривается, потом снова идет. Вместо этого все действия выполняются естественно и плавно, что значительно повышает реалистичность ИИ.

Кроме того, параллельные узлы могут использоваться для создания сложных пространственных манипуляций. Например, NPC может одновременно держать в одной руке щит (для защиты), а другой рукой атаковать мечом. Каждое из этих действий управляется своей ветвью графа, но выполняются они синхронно, создавая комплексную анимацию.

Параллельное выполнение также помогает оптимизировать производительность. Некоторые операции, особенно связанные с физикой или навигацией, могут быть ресурсоемкими. Разделяя их на независимые ветви, которые выполняются с разной частотой, можно существенно снизить нагрузку на процессор. Например, проверка видимости цели может выполняться каждые 5 кадров, в то время как логика перемещения обновляется в каждом кадре.

Практическая реализация в Unity



Перейдём от теории к практике и разберём, как использовать Behavior Graph для создания интеллектуальных систем в Unity. Начнём с базовых шагов — установки пакета и настройки первого графа поведения.

Настройка и основы работы



Чтобы начать работу с Behavior Graph, необходимо установить пакет Unity Behavior. Это можно сделать через Package Manager (Window → Package Manager), выбрав опцию "Add package from git URL" и вставив ссылку на репозиторий com.unity.behavior. Альтернативно можно добавить зависимость напрямую в файл manifest.json вашего проекта:

JSON
1
2
3
4
5
6
{
  "dependencies": {
    "com.unity.behavior": "1.0.3",
    // другие зависимости
  }
}
После установки пакета в меню Unity появится новый раздел "Behaviors". Через него можно создать новый граф (Assets → Create → Behavior Graph). После создания файла графа, двойной клик по нему откроет редактор Behavior Graph.

Но сам по себе граф бесполезен, пока не подключен к игровому объекту. Для этого служит компонент Behavior Agent. Добавьте его на нужный объект через Inspector (Add Component → Behaviors → Behavior Agent) и укажите созданный вами граф в поле "Behavior". Теперь ваш объект будет выполнять логику, описанную в графе.

Создание первого графа поведения



Допустим, мы хотим создать простой ИИ для охранника, который патрулирует территорию и реагирует на появление игрока. Вот как будет выглядеть базовая структура такого графа:
1. Создайте новый граф и откройте редактор.
2. Добавьте корневой узел Sequence, который будет выполнять дочерние узлы последовательно.
3. Внутри Sequence добавьте узел Patrol, который будет отвечать за перемещение NPC по точкам патрулирования.
4. Добавьте условный узел If, который будет проверять, видит ли охранник игрока.
5. Если условие истинно, активируйте узел Chase, который заставит NPC преследовать игрока.
6. После завершения погони (игрок скрылся или был пойман) вернитесь к патрулированию.

На практике для реализации такой логики вам понадобится создать несколько переменных в Blackboard:
waypoints: массив Transform-компонентов, представляющих точки патрулирования.
player: ссылка на Transform игрока.
isPlayerVisible: булево значение, указывающее, видит ли NPC игрока.
visionRange: радиус, в котором NPC может обнаружить игрока.

Теперь рассмотрим код, необходимый для узла Patrol (который придётся создавать самостоятельно, так как это кастомный узел):

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Unity.Behaviors;
using UnityEngine;
using UnityEngine.AI;
 
[ActionCategory("Custom")]
public class PatrolAction : Action
{
    [SerializeField] private BlackboardPropertyName<Transform[]> _waypoints;
    [SerializeField] private BlackboardPropertyName<int> _currentWaypointIndex;
    
    private NavMeshAgent _agent;
    private int _targetWaypoint = 0;
    
    protected override void OnInitialize()
    {
        _agent = GetComponent<NavMeshAgent>();
        _targetWaypoint = GetBlackboardValue(_currentWaypointIndex, 0);
    }
    
    protected override Status OnUpdate()
    {
        var waypoints = GetBlackboardValue(_waypoints);
        if (waypoints == null || waypoints.Length == 0)
            return Status.Failure;
        
        if (!_agent.pathPending && _agent.remainingDistance < 0.5f)
        {
            _targetWaypoint = (_targetWaypoint + 1) % waypoints.Length;
            SetBlackboardValue(_currentWaypointIndex, _targetWaypoint);
            _agent.SetDestination(waypoints[_targetWaypoint].position);
        }
        
        return Status.Running;
    }
    
    protected override Status OnStop()
    {
        _agent.ResetPath();
        return Status.Success;
    }
}
Этот код создаёт кастомный узел действия, который управляет перемещением NPC между заданными точками патрулирования с использованием NavMeshAgent. Обратите внимание на методы жизненного цикла узла:
OnInitialize: вызывается при первой активации узла.
OnUpdate: вызывается каждый кадр, пока узел активен.
OnStop: вызывается при деактивации узла.

Хотя этот пример достаточно прост, он демонстрирует мощь Behavior Graph — возможность сочетания визуального программирования с кастомными узлами, написанными на C#.

Интеграция с существующими компонентами Unity



Одно из главных достоинств Unity Behavior — это лёгкость интеграции с существующей кодовой базой. Вот несколько способов взаимодействия Behavior Graph с другими компонентами Unity:

Доступ к компонентам GameObject



В кастомных узлах вы можете использовать метод GetComponent<T>() для получения доступа к любым компонентам на том же GameObject, что и Behavior Agent. Это позволяет управлять различными аспектами объекта: анимацией, аудио, физикой и т.д. Например, вы можете создать узел для проигрывания анимации:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[ActionCategory("Animation")]
public class PlayAnimationAction : Action
{
    [SerializeField] private string _animationName;
    
    private Animator _animator;
    
    protected override void OnInitialize()
    {
        _animator = GetComponent<Animator>();
    }
    
    protected override Status OnUpdate()
    {
        if (_animator == null)
            return Status.Failure;
        
        _animator.Play(_animationName);
        return Status.Success;
    }
}

Использование событий Unity



Behavior Graph может реагировать на стандартные события Unity через специальные узлы-слушатели. Например, можно создать узел, который будет активировать определённую ветвь графа при коллизии:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[ActionCategory("Physics")]
public class OnCollisionEnterAction : Action
{
    [SerializeField] private BlackboardPropertyName<GameObject> _collidedObject;
    
    private bool _collisionDetected = false;
    private GameObject _collider = null;
    
    protected override void OnInitialize()
    {
        var rigidBody = GetComponent<Rigidbody>();
        if (rigidBody != null)
        {
            // Подписываемся на события коллизии
            StartCoroutine(DetectCollisions());
        }
    }
    
    private System.Collections.IEnumerator DetectCollisions()
    {
        while (true)
        {
            if (_collisionDetected)
            {
                SetBlackboardValue(_collidedObject, _collider);
                yield break;
            }
            yield return null;
        }
    }
    
    public void OnCollisionEnter(Collision collision)
    {
        _collisionDetected = true;
        _collider = collision.gameObject;
    }
    
    protected override Status OnUpdate()
    {
        if (_collisionDetected)
            return Status.Success;
        return Status.Running;
    }
}
Этот узел ожидает коллизии, и когда она происходит, сохраняет ссылку на объект, с которым произошло столкновение, в Blackboard и возвращает Status.Success.

Оптимизация производительности



При работе с Behavior Graph важно помнить о производительности, особенно если у вас в сцене много объектов с активными графами поведения.
1. Используйте задержки там, где можно. Не все проверки нужно выполнять каждый кадр. Например, проверка видимости игрока может выполняться раз в 0.5 секунды.
2. Разделяйте логику на подграфы. Это не только улучшит организацию кода, но и позволит повторно использовать общие паттерны поведения.
3. Внимательно следите за условиями завершения узлов. Застрявший в состоянии Running узел будет продолжать выполняться, потребляя ресурсы.
4. Используйте профилировщик Unity. Он поможет выявить узкие места в производительности вашего графа.

Например, вместо постоянной проверки дистанции между NPC и игроком можно использовать события триггера:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[ActionCategory("Detection")]
public class DetectPlayer : Action
{
    [SerializeField] private BlackboardPropertyName<Transform> _player;
    [SerializeField] private BlackboardPropertyName<bool> _isPlayerVisible;
    [SerializeField] private BlackboardPropertyName<float> _detectionInterval = 0.5f;
    
    private float _lastCheckTime = 0;
    
    protected override Status OnUpdate()
    {
        if (Time.time - _lastCheckTime < GetBlackboardValue(_detectionInterval))
            return GetBlackboardValue(_isPlayerVisible) ? Status.Success : Status.Running;
        
        _lastCheckTime = Time.time;
        
        var player = GetBlackboardValue(_player);
        if (player == null)
            return Status.Failure;
        
        // Здесь логика проверки видимости
        bool isVisible = CanSeePlayer(player);
        SetBlackboardValue(_isPlayerVisible, isVisible);
        
        return isVisible ? Status.Success : Status.Running;
    }
    
    private bool CanSeePlayer(Transform player)
    {
        // Проверка видимости через рейкаст и угол обзора
        // ...
        return false;
    }
}
Этот узел будет проверять видимость игрока только через заданные интервалы времени, что существенно снижает нагрузку по сравнению с проверкой каждый кадр.

Сериализация графов поведения в Unity



Важнейшим аспектом практической работы с Behavior Graph является сохранение и загрузка графов, особенно при создании сложных поведенческих систем, которые могут использоваться в разных частях вашего проекта. Unity Behavior предоставляет мощный механизм сериализации, позволяющий не только сохранять графы как ассеты, но и клонировать их во время выполнения для создания уникальных экземпляров.

По умолчанию, все графы поведения сохраняются как отдельные ассеты в папке проекта. Это позволяет повторно использовать одну и ту же логику поведения для разных объектов. Когда граф назначается компоненту Behavior Agent, он создаёт локальную копию этого графа, которая затем может быть индивидуально настроена через переменные, отмеченные как Exposed.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Пример программного создания и назначения графа поведения
public class BehaviorGraphManager : MonoBehaviour
{
    [SerializeField] private BehaviorGraph _templateGraph;
    
    public void AssignBehaviorToObject(GameObject target)
    {
        // Создаем агента поведения, если его еще нет
        var agent = target.GetComponent<BehaviorAgent>() ?? 
                    target.AddComponent<BehaviorAgent>();
        
        // Клонируем шаблонный граф
        var instanceGraph = Instantiate(_templateGraph);
        
        // Назначаем клон агенту
        agent.behavior = instanceGraph;
        
        // Настраиваем переменные если нужно
        var blackboard = instanceGraph.blackboard;
        blackboard.SetValue("PatrolSpeed", 3.5f);
    }
}
Такой подход особенно полезен при создании систем со множеством однотипных, но индивидуально настроенных агентов – например, армии NPC с разными характеристиками, но схожей логикой поведения.

Однако, стоит быть внимательным с "общими" переменными (Shared). Изменение их значения в одном экземпляре графа повлияет на все экземпляры. Это может быть как преимуществом (для синхронизации поведения), так и источником трудно отлаживаемых ошибок, если использовать бездумно.

Пример реализации редактора Behavior Graph в Unity



Хотя Unity Behavior предоставляет встроенный редактор графов, иногда возникает необходимость расширить его функциональность для конкретного проекта. Это можно сделать, создав пользовательский редактор, который будет взаимодействовать с API Behavior Graph. Вот пример простого редактора, который позволяет настраивать переменные графа непосредственно из инспектора GameObject:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using Unity.Behaviors;
 
[CustomEditor(typeof(BehaviorAgent))]
public class BehaviorAgentEditor : Editor
{
    private bool _showBlackboard = true;
    
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        
        BehaviorAgent agent = (BehaviorAgent)target;
        if (agent.behavior == null)
            return;
            
        EditorGUILayout.Space();
        _showBlackboard = EditorGUILayout.Foldout(_showBlackboard, "Blackboard Variables");
        
        if (_showBlackboard)
        {
            EditorGUI.indentLevel++;
            
            var blackboard = agent.behavior.blackboard;
            var propertyNames = blackboard.GetPropertyNames();
            
            foreach (var propName in propertyNames)
            {
                // Показываем только exposed переменные
                if (!blackboard.IsPropertyExposed(propName))
                    continue;
                    
                var propType = blackboard.GetPropertyType(propName);
                
                // Для разных типов используем соответствующие элементы GUI
                if (propType == typeof(float))
                {
                    float value = blackboard.GetValue<float>(propName);
                    float newValue = EditorGUILayout.FloatField(propName, value);
                    if (value != newValue)
                        blackboard.SetValue(propName, newValue);
                }
                else if (propType == typeof(bool))
                {
                    bool value = blackboard.GetValue<bool>(propName);
                    bool newValue = EditorGUILayout.Toggle(propName, value);
                    if (value != newValue)
                        blackboard.SetValue(propName, newValue);
                }
                // Добавьте обработку других типов по необходимости
            }
            
            EditorGUI.indentLevel--;
        }
        
        // Кнопка для открытия графа в редакторе
        if (GUILayout.Button("Edit Behavior Graph"))
        {
            Selection.activeObject = agent.behavior;
        }
    }
}
#endif
Этот редактор предоставляет удобный интерфейс для настройки "открытых" переменных графа без необходимости открывать редактор графа, что может значительно ускорить рабочий процесс при настройке множества объектов.

Отладка и визуализация исполнения графов в реальном времени



Отладка сложных поведенческих систем может быть нетривиальной задачей. К счастью, Unity Behavior предлагает несколько инструментов, которые помогают понять, что происходит с вашим графом во время выполнения. Во время игрового режима вы можете выбрать объект с компонентом Behavior Agent и увидеть состояние его графа в реальном времени. Активные узлы подсвечиваются, что позволяет легко отслеживать поток выполнения. Более того, вы можете наблюдать за значениями переменных в Blackboard, что критически важно для понимания причин принятых графом решений. Для более сложных случаев может потребоваться собственная система логгирования. Вот пример узла, который записывает своё состояние в консоль:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[ActionCategory("Debug")]
public class LogStateAction : Action
{
    [SerializeField] private string _message = "Node execution";
    [SerializeField] private LogType _logType = LogType.Log;
    [SerializeField] private bool _logOnlyOnce = true;
    
    private bool _hasLogged = false;
    
    protected override Status OnUpdate()
    {
        if (_logOnlyOnce && _hasLogged)
            return Status.Success;
            
        switch (_logType)
        {
            case LogType.Log:
                Debug.Log($"[{name}] {_message}");
                break;
            case LogType.Warning:
                Debug.LogWarning($"[{name}] {_message}");
                break;
            case LogType.Error:
                Debug.LogError($"[{name}] {_message}");
                break;
        }
        
        _hasLogged = true;
        return Status.Success;
    }
    
    protected override void OnReset()
    {
        _hasLogged = false;
    }
}
Вставляя такие узлы в ключевые места графа, вы сможете отслеживать последовательность выполнения и диагностировать проблемы.

Для визуализации состояния в игровом мире можно использовать Gizmos:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[ActionCategory("Debug")]
public class VisualizeStateAction : Action
{
    [SerializeField] private BlackboardPropertyName<Color> _debugColor = Color.red;
    [SerializeField] private BlackboardPropertyName<float> _radius = 1.0f;
    
    private GameObject _visualMarker;
    
    protected override void OnInitialize()
    {
        _visualMarker = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        _visualMarker.transform.parent = transform;
        _visualMarker.transform.localPosition = Vector3.zero;
        
        var renderer = _visualMarker.GetComponent<Renderer>();
        renderer.material.color = GetBlackboardValue(_debugColor);
        
        // Отключаем коллайдер, нам нужна только визуализация
        var collider = _visualMarker.GetComponent<Collider>();
        if (collider)
            Destroy(collider);
    }
    
    protected override Status OnUpdate()
    {
        float radius = GetBlackboardValue(_radius);
        _visualMarker.transform.localScale = new Vector3(radius, radius, radius);
        return Status.Running;
    }
    
    protected override void OnStop()
    {
        if (_visualMarker)
            Destroy(_visualMarker);
    }
}
Этот узел создаёт сферу, которая визуально отображает состояние агента в игровом мире – например, радиус обнаружения или область атаки. Такая визуализация незаменима при отладке пространственных аспектов поведения.

Комбинируя эти техники, вы сможете создавать сложные, но легко отлаживаемые поведенческие системы, которые придадут вашим NPC реалистичность и глубину, недостижимую с помощью более простых методов искусственного интеллекта.

Продвинутые примеры использования



Теперь, познакомившись с основами Behavior Graph, можно перейти к более сложным и интересным сценариям использования этой технологии. Рассмотрим несколько практических примеров, которые помогут вам оценить мощь и гибкость графов поведения в реальных игровых ситуациях.

Боевые ИИ противников



Одно из самых очевидных применений Behavior Graph — создание интеллектуальных противников с разнообразными боевыми тактиками. В отличие от простых скриптов с условиями, графы поведения позволяют моделировать сложные тактические решения и стили боя.

Вот пример структуры графа для тактически продуманного противника-мечника:
1. Корневой узел — параллельный композит, который одновременно обрабатывает:
- Ветвь оценки ситуации (постоянно анализирует расстояние до игрока, здоровье, выносливость).
- Ветвь принятия тактических решений (выбирает между атакой, защитой, отступлением).
- Ветвь реагирования на угрозы (прерывает текущие действия при необходимости).
2. Внутри ветви тактических решений используется узел выбора (Selector), который активирует одну из подветвей:
- Агрессивное поведение, если у противника много здоровья и преимущество.
- Оборонительное поведение, если здоровье низкое или игрок имеет численное превосходство.
- Тактическое отступление для восстановления и перегруппировки.

Каждая из этих ветвей может содержать собственные сложные последовательности действий. Например, агрессивное поведение может включать комбинации из нескольких быстрых атак, обманных движений и специальных приемов.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class CombatStyleSelector : Action
{
    [SerializeField] private BlackboardPropertyName<float> _healthPercentage;
    [SerializeField] private BlackboardPropertyName<float> _staminaPercentage;
    [SerializeField] private BlackboardPropertyName<float> _distanceToPlayer;
    [SerializeField] private BlackboardPropertyName<string> _selectedStyle;
    
    protected override Status OnUpdate()
    {
        float health = GetBlackboardValue(_healthPercentage);
        float stamina = GetBlackboardValue(_staminaPercentage);
        float distance = GetBlackboardValue(_distanceToPlayer);
        
        string newStyle;
        
        // Логика выбора стиля боя на основе текущего состояния
        if (health < 30f)
        {
            newStyle = "Defensive";
        }
        else if (distance > 5f)
        {
            newStyle = "Ranged";
        }
        else if (stamina > 70f)
        {
            newStyle = "Aggressive";
        }
        else
        {
            newStyle = "Balanced";
        }
        
        // Устанавливаем выбранный стиль в blackboard
        SetBlackboardValue(_selectedStyle, newStyle);
        
        return Status.Success;
    }
}
Особенно интересной становится реализация, когда противники могут координировать свои действия. Например, в группе из трех врагов один может отвлекать внимание игрока, второй заходить с фланга, а третий готовить мощную атаку. Такая командная работа реализуется через событийный механизм Behavior Graph, позволяя агентам обмениваться сигналами и синхронизировать свои действия.

Неигровые персонажи с комплексным поведением



NPC в открытом мире — еще одна область, где Behavior Graph может кардинально улучшить впечатления от игры. Вместо статичных персонажей, выполняющих одно и то же действие, можно создать живой мир, где каждый NPC следует своему расписанию и реагирует на окружающие события.

Представим городского жителя с таким графом поведения:
1. Верхний уровень — планировщик дневного расписания, который активирует разные подграфы в зависимости от игрового времени:
- Утренние активности (подъем, завтрак).
- Рабочее время (соответствующие профессии действия).
- Вечерний отдых (поход в таверну, общение).
- Ночной сон.
2. Параллельно работает система обработки событий, которая может прерывать текущее расписание:
- Опасность (атака монстров, пожар) — запускает поведение спасения.
- Социальные взаимодействия — разговоры с другими NPC или игроком.
- Погода — укрытие в случае дождя или бури.

Вот пример узла, управляющего переключением активностей по времени:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ScheduleManager : Action
{
    [SerializeField] private BlackboardPropertyName<float> _gameTimeHours;
    [SerializeField] private BlackboardPropertyName<string> _currentActivity;
    
    [Serializable]
    public class ScheduleEntry
    {
        public string ActivityName;
        public float StartTimeHours;
        public float EndTimeHours;
    }
    
    [SerializeField] private ScheduleEntry[] _schedule;
    
    protected override Status OnUpdate()
    {
        float currentTime = GetBlackboardValue(_gameTimeHours);
        
        // Находим подходящую активность по текущему времени
        string newActivity = "Idle";  // Действие по умолчанию
        
        foreach (var entry in _schedule)
        {
            if (currentTime >= entry.StartTimeHours && currentTime < entry.EndTimeHours)
            {
                newActivity = entry.ActivityName;
                break;
            }
        }
        
        // Обновляем активность только если она изменилась
        string currentActivity = GetBlackboardValue(_currentActivity);
        if (currentActivity != newActivity)
        {
            SetBlackboardValue(_currentActivity, newActivity);
        }
        
        return Status.Success;
    }
}
Такая система позволяет создавать NPC, которые ведут себя естественно и предсказуемо, но при этом могут гибко реагировать на действия игрока и изменения в игровом мире. Бармен из таверны будет не просто стоять за стойкой, а по утрам закупать продукты, днем обслуживать посетителей, а вечером подсчитывать выручку и убираться.

Системы принятия стратегических решений



На более высоком уровне Behavior Graph может использоваться для моделирования стратегического ИИ, управляющего целыми армиями или империями в стратегических играх.

Для RTS-игры можно создать многоуровневую систему ИИ:
1. Верхний уровень — стратегический ИИ, который оценивает глобальную ситуацию и выбирает общую стратегию:
- Агрессивное расширение.
- Технологическое развитие.
- Оборонительное укрепление.
- Экономический рост.
2. Средний уровень — тактический ИИ, распределяющий ресурсы и задачи:
- Планирование строительства.
- Формирование армий.
- Исследование карты.
- Реагирование на угрозы.
3. Нижний уровень — микроменеджмент отдельных юнитов и зданий:
- Позиционирование боевых отрядов.
- Сбор ресурсов рабочими.
- Оптимизация производственных цепочек.

Такая иерархическая структура естественно реализуется через вложенные графы в Unity Behavior. Графы верхнего уровня принимают глобальные решения и активируют соответствующие подграфы для их исполнения. При этом все уровни постоянно обмениваются информацией через общие переменные и события.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ResourceAllocationStrategy : Action
{
    [SerializeField] private BlackboardPropertyName<float> _militaryThreatLevel;
    [SerializeField] private BlackboardPropertyName<float> _economyStrength;
    [SerializeField] private BlackboardPropertyName<float> _militaryRatio;
    [SerializeField] private BlackboardPropertyName<float> _economyRatio;
    [SerializeField] private BlackboardPropertyName<float> _researchRatio;
    
    protected override Status OnUpdate()
    {
        float threat = GetBlackboardValue(_militaryThreatLevel);
        float economy = GetBlackboardValue(_economyStrength);
        
        // Распределение ресурсов в зависимости от текущей ситуации
        if (threat > 0.7f)  // Высокая угроза
        {
            // Приоритет военного производства
            SetBlackboardValue(_militaryRatio, 0.7f);
            SetBlackboardValue(_economyRatio, 0.2f);
            SetBlackboardValue(_researchRatio, 0.1f);
        }
        else if (economy < 0.4f)  // Слабая экономика
        {
            // Приоритет экономического развития
            SetBlackboardValue(_militaryRatio, 0.3f);
            SetBlackboardValue(_economyRatio, 0.6f);
            SetBlackboardValue(_researchRatio, 0.1f);
        }
        else  // Стабильная ситуация
        {
            // Сбалансированное развитие с уклоном в исследования
            SetBlackboardValue(_militaryRatio, 0.3f);
            SetBlackboardValue(_economyRatio, 0.3f);
            SetBlackboardValue(_researchRatio, 0.4f);
        }
        
        return Status.Success;
    }
}
Благодаря прозрачности и модульности графов поведения, такую систему легко настраивать и балансировать. Дизайнеры могут менять параметры решений или даже перестраивать отдельные части логики без необходимости погружаться в сложный код.

Симуляция эмоциональных состояний персонажей



Отдельного упоминания заслуживает возможность моделирования эмоциональных состояний NPC. Создание персонажей с правдоподобными эмоциями значительно повышает вовлеченность игроков и реалистичность игрового мира.
С помощью Behavior Graph можно реализовать систему эмоций для персонажей, основанную на нескольких измерениях: радость/грусть, страх/уверенность, любовь/ненависть и т.д. Каждая эмоция представляет собой числовое значение, которое меняется в зависимости от событий в игре.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class EmotionManager : Action
{
  [SerializeField] private BlackboardPropertyName<float> _happiness;
  [SerializeField] private BlackboardPropertyName<float> _fear;
  [SerializeField] private BlackboardPropertyName<float> _anger;
  [SerializeField] private BlackboardPropertyName<string> _dominantEmotion;
  
  private float _decayRate = 0.01f;  // Скорость затухания эмоций со временем
  
  protected override Status OnUpdate()
  {
      // Получаем текущие эмоциональные значения
      float happiness = GetBlackboardValue(_happiness);
      float fear = GetBlackboardValue(_fear);
      float anger = GetBlackboardValue(_anger);
      
      // Применяем затухание эмоций с течением времени
      happiness = Mathf.Max(0, happiness - _decayRate);
      fear = Mathf.Max(0, fear - _decayRate);
      anger = Mathf.Max(0, anger - _decayRate);
      
      // Обновляем значения
      SetBlackboardValue(_happiness, happiness);
      SetBlackboardValue(_fear, fear);
      SetBlackboardValue(_anger, anger);
      
      // Определяем доминирующую эмоцию
      string dominant = "neutral";
      float maxEmotion = 0.3f;  // Порог для проявления эмоции
      
      if (happiness > maxEmotion && happiness > fear && happiness > anger)
      {
          dominant = "happy";
          maxEmotion = happiness;
      }
      else if (fear > maxEmotion && fear > happiness && fear > anger)
      {
          dominant = "afraid";
          maxEmotion = fear;
      }
      else if (anger > maxEmotion && anger > happiness && anger > fear)
      {
          dominant = "angry";
          maxEmotion = anger;
      }
      
      SetBlackboardValue(_dominantEmotion, dominant);
      
      return Status.Running;  // Этот узел всегда активен
  }
}
Эмоциональное состояние влияет на все аспекты поведения NPC — от выбора фраз в диалогах до принятия тактических решений в бою. Испуганный персонаж будет избегать опасности, злой — искать конфликта, а счастливый — более охотно помогать игроку.

Такая система делает персонажей более живыми и непредсказуемыми, создавая уникальный игровой опыт при каждом прохождении. Игрок может намеренно вызывать определенные эмоции у NPC через свои действия, открывая новые возможности взаимодействия с миром игры.

Графы поведения для процедурной генерации контента



Еще одно интересное применение Behavior Graph — управление процедурной генерацией игрового контента. Вместо создания статичных уровней или предметов, можно использовать графы поведения для динамического формирования игрового мира с учетом множества факторов.

Например, система генерации подземелий может использовать следующий граф поведения:
1. Анализ параметров генерации (сложность, размер, тема подземелья).
2. Создание общей структуры (размещение входов, выходов, ключевых точек).
3. Наполнение комнат контентом в зависимости от их назначения.
4. Размещение противников с учетом желаемой сложности и темы.
5. Проверка проходимости и валидация созданного подземелья.
6. Итеративное улучшение при необходимости.

Каждый из этих шагов может быть реализован как отдельный подграф со своей сложной логикой. Преимущество такого подхода в том, что дизайнеры могут настраивать и модифицировать процесс генерации без необходимости писать или изменять код.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class RoomGenerator : Action
{
  [SerializeField] private BlackboardPropertyName<string> _roomType;
  [SerializeField] private BlackboardPropertyName<float> _difficulty;
  [SerializeField] private BlackboardPropertyName<Vector2Int> _roomSize;
  [SerializeField] private BlackboardPropertyName<GameObject> _generatedRoom;
  
  protected override Status OnUpdate()
  {
      string type = GetBlackboardValue(_roomType);
      float difficulty = GetBlackboardValue(_difficulty);
      Vector2Int size = GetBlackboardValue(_roomSize);
      
      GameObject room = new GameObject($"Room_{type}");
      
      // Генерируем базовую геометрию комнаты
      CreateRoomGeometry(room, size);
      
      // Наполняем комнату в зависимости от типа
      switch (type)
      {
          case "combat":
              PopulateCombatRoom(room, difficulty);
              break;
          case "treasure":
              PopulateTreasureRoom(room, difficulty);
              break;
          case "puzzle":
              PopulatePuzzleRoom(room, difficulty);
              break;
          default:
              PopulateBasicRoom(room);
              break;
      }
      
      SetBlackboardValue(_generatedRoom, room);
      return Status.Success;
  }
  
  private void CreateRoomGeometry(GameObject room, Vector2Int size)
  {
      // Логика создания стен, пола и потолка
      // ...
  }
  
  private void PopulateCombatRoom(GameObject room, float difficulty)
  {
      // Размещение врагов, препятствий и т.д.
      int enemyCount = Mathf.RoundToInt(difficulty * 2) + 1;
      
      for (int i = 0; i < enemyCount; i++)
      {
          // Создаем врага с соответствующей силой
          GameObject enemy = CreateEnemy(difficulty);
          enemy.transform.parent = room.transform;
          // Позиционируем врага
          // ...
      }
  }
  
  private void PopulateTreasureRoom(GameObject room, float difficulty)
  {
      // Размещение сокровищ, ловушек и т.д.
      // ...
  }
  
  private void PopulatePuzzleRoom(GameObject room, float difficulty)
  {
      // Размещение элементов головоломки
      // ...
  }
  
  private void PopulateBasicRoom(GameObject room)
  {
      // Базовое наполнение обычной комнаты
      // ...
  }
  
  private GameObject CreateEnemy(float difficulty)
  {
      // Создание врага соответствующей сложности
      // ...
      return new GameObject("Enemy");
  }
}
Процедурная генерация может применяться не только к локациям, но и к предметам, квестам, диалогам и даже сюжетным линиям. Behavior Graph позволяет моделировать сложные правила генерации с множеством взаимосвязанных условий и зависимостей, создавая уникальный и разнообразный игровой опыт при каждом прохождении.

Адаптивная сложность ИИ на основе поведенческих графов



Одной из наиболее впечатляющих возможностей Behavior Graph является создание адаптивных систем ИИ, которые подстраиваются под уровень игрока и обеспечивают оптимальный баланс сложности. Вместо статических настроек сложности (легкий, средний, тяжелый) можно реализовать динамическую систему, которая анализирует результаты игрока и корректирует поведение противников:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class AdaptiveDifficultyManager : Action
{
  [SerializeField] private BlackboardPropertyName<float> _playerWinRate;
  [SerializeField] private BlackboardPropertyName<float> _aiAggressiveness;
  [SerializeField] private BlackboardPropertyName<float> _aiAccuracy;
  [SerializeField] private BlackboardPropertyName<float> _aiReactionTime;
  
  [SerializeField] private float _targetWinRate = 0.6f;  // Целевой коэффициент побед игрока
  [SerializeField] private float _adjustmentRate = 0.05f;  // Скорость адаптации
  
  protected override Status OnUpdate()
  {
      float currentWinRate = GetBlackboardValue(_playerWinRate);
      
      // Если игрок выигрывает слишком часто, увеличиваем сложность
      if (currentWinRate > _targetWinRate)
      {
          AdjustDifficulty(true);
      }
      // Если игрок проигрывает слишком часто, снижаем сложность
      else if (currentWinRate < _targetWinRate - 0.1f)
      {
          AdjustDifficulty(false);
      }
      
      return Status.Success;
  }
  
  private void AdjustDifficulty(bool increase)
  {
      float adjustmentDirection = increase ? 1f : -1f;
      
      // Корректируем параметры ИИ
      float aggressiveness = GetBlackboardValue(_aiAggressiveness);
      float accuracy = GetBlackboardValue(_aiAccuracy);
      float reactionTime = GetBlackboardValue(_aiReactionTime);
      
      aggressiveness = Mathf.Clamp01(aggressiveness + adjustmentDirection * _adjustmentRate);
      accuracy = Mathf.Clamp01(accuracy + adjustmentDirection * _adjustmentRate);
      reactionTime = Mathf.Clamp(reactionTime - adjustmentDirection * _adjustmentRate * 0.1f, 0.1f, 1f);
      
      SetBlackboardValue(_aiAggressiveness, aggressiveness);
      SetBlackboardValue(_aiAccuracy, accuracy);
      SetBlackboardValue(_aiReactionTime, reactionTime);
  }
}
Эти параметры затем используются в других графах поведения для настройки конкретных аспектов ИИ:
  • Агрессивность влияет на частоту атак и выбор тактики.
  • Точность определяет вероятность попадания при стрельбе или прицеливании.
  • Время реакции задает скорость ответа на действия игрока.

Такая система создает глубокий, персонализированный игровой опыт, который постоянно балансируется для поддержания оптимального уровня сложности. Игроки не будут разочарованы слишком легкими заданиями или разочарованы чрезмерно сложными испытаниями. Что еще интереснее, этот подход можно расширить до полноценной системы обучения ИИ в реальном времени, которая не только настраивает параметры, но и выбирает более эффективные тактики на основе анализа прошлых взаимодействий с игроком.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class TacticEffectivenessTracker : Action
{
  [Serializable]
  public class TacticData
  {
      public string TacticName;
      public float SuccessRate;
      public int UsageCount;
  }
  
  [SerializeField] private BlackboardPropertyName<string> _currentTactic;
  [SerializeField] private BlackboardPropertyName<bool> _tacticSuccess;
  [SerializeField] private BlackboardPropertyName<string> _recommendedTactic;
  
  // Список тактик и их эффективность
  private List<TacticData> _tactics = new List<TacticData>
  {
      new TacticData { TacticName = "Aggressive", SuccessRate = 0.5f, UsageCount = 1 },
      new TacticData { TacticName = "Defensive", SuccessRate = 0.5f, UsageCount = 1 },
      new TacticData { TacticName = "Flanking", SuccessRate = 0.5f, UsageCount = 1 },
      new TacticData { TacticName = "Ranged", SuccessRate = 0.5f, UsageCount = 1 }
  };
  
  protected override void OnInitialize()
  {
      // Выбираем начальную тактику случайным образом
      int randomIndex = UnityEngine.Random.Range(0, _tactics.Count);
      SetBlackboardValue(_recommendedTactic, _tactics[randomIndex].TacticName);
  }
  
  protected override Status OnUpdate()
  {
      // Проверяем, изменилась ли текущая тактика
      string currentTactic = GetBlackboardValue(_currentTactic);
      string prevTactic = null;
      
      // Если текущая тактика была оценена (успех/неудача)
      if (GetBlackboardValueChanged(_tacticSuccess, out bool success))
      {
          // Обновляем статистику для текущей тактики
          foreach (var tactic in _tactics)
          {
              if (tactic.TacticName == currentTactic)
              {
                  // Обновляем успешность тактики
                  tactic.SuccessRate = (tactic.SuccessRate * tactic.UsageCount + (success ? 1 : 0)) / (tactic.UsageCount + 1);
                  tactic.UsageCount++;
                  break;
              }
          }
          
          // Выбираем новую рекомендуемую тактику на основе их эффективности
          // С вероятностью 80% выбираем лучшую тактику, 20% - случайную (для исследования)
          if (UnityEngine.Random.value < 0.8f)
          {
              // Сортируем тактики по успешности и выбираем лучшую
              var sortedTactics = _tactics.OrderByDescending(t => t.SuccessRate).ToList();
              SetBlackboardValue(_recommendedTactic, sortedTactics[0].TacticName);
          }
          else
          {
              // Случайная тактика для исследования
              int randomIndex = UnityEngine.Random.Range(0, _tactics.Count);
              SetBlackboardValue(_recommendedTactic, _tactics[randomIndex].TacticName);
          }
      }
      
      return Status.Running;  // Этот узел всегда активен
  }
}
Эта система демонстрирует, как Behavior Graph может использоваться для реализации элементов машинного обучения без необходимости интегрировать сложные ML-библиотеки. Противники в игре буквально учатся на своих ошибках и адаптируют свою стратегию для более эффективного противостояния конкретному игроку.

Возможности адаптивного ИИ на базе Behavior Graph практически безграничны — от простых подстроек параметров до сложных систем, способных анализировать паттерны поведения игрока и разрабатывать контрстратегии. Такие системы могут значительно увеличить реиграбельность игры, так как ИИ продолжает развиваться и адаптироваться с каждым прохождением.

Ограничения и проблемы



Несмотря на все преимущества Behavior Graph, у этого инструмента есть свои ограничения и подводные камни. Столкнувшись с ними в процессе разработки, вы можете потратить много времени на отладку, поэтому лучше знать о них заранее.

Типичные ошибки при реализации



Одна из самых распространённых ошибок — создание слишком сложных графов без промежуточной декомпозиции. Когда граф разрастается до сотен узлов, он становится трудным для понимания и отладки. Решение простое — использовать подграфы для инкапсуляции логики и повторного использования компонентов.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Плохой подход — всё в одном графе
public class GiantMonolithicAI : Action
{
    // Десятки переменных и сотни строк кода
}
 
// Хороший подход — декомпозиция на компоненты
public class PatrolSubgraphController : Action
{
    // Только логика патрулирования
}
 
public class CombatSubgraphController : Action
{
    // Только боевая логика
}
Вторая распространённая ошибка — некорректная обработка событий и состояний "Running". Если узел возвращает "Running", но никогда не переходит в другое состояние, граф застрянет в бесконечном цикле. Всегда проверяйте, что каждый путь в вашем графе может завершиться состоянием Success или Failure.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Потенциально проблемный код
protected override Status OnUpdate()
{
    if (SomeCondition())
        return Status.Running;  // Граф может застрять, если SomeCondition всегда true
    
    return Status.Success;
}
 
// Безопасный вариант с таймаутом
protected override Status OnUpdate()
{
    if (SomeCondition() && Time.time - _startTime < _maxRunningTime)
        return Status.Running;
    
    return SomeCondition() ? Status.Failure : Status.Success;
}
Ещё одна ошибка — чрезмерное использование Blackboard без организации переменных. Когда доска переполнена десятками переменных без ясной структуры, код становится трудночитаемым. Полезно группировать связанные переменные с помощью префиксов или даже создавать специальные структуры.

Вопросы производительности при глубоких вложенных графах



Производительность — ахиллесова пята любой сложной ИИ-системы. Хотя Behavior Graph оптимизирован для работы со множеством узлов, слишком глубокие вложенные структуры могут создать проблемы. В частности, каждый переход между узлами имеет накладные расходы на стеке вызовов. При глубине графа в десятки уровней эти расходы начинают ощутимо влиять на производительность, особенно на мобильных платформах. Существует несколько стратегий оптимизации:
1. Горизонтальное расширение вместо вертикального — старайтесь организовать граф "вширь", а не "вглубь".
2. Использование кэширования — вычисляйте сложные результаты реже и сохраняйте их в Blackboard.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CachedPathfinding : Action
{
    [SerializeField] private BlackboardPropertyName<Vector3> _targetPosition;
    [SerializeField] private BlackboardPropertyName<List<Vector3>> _cachedPath;
    [SerializeField] private float _recalculationDistance = 3f;
    
    private Vector3 _lastCalculatedPosition;
    
    protected override Status OnUpdate()
    {
        Vector3 currentTarget = GetBlackboardValue(_targetPosition);
        
        // Пересчитываем путь только если цель существенно переместилась
        if (Vector3.Distance(currentTarget, _lastCalculatedPosition) > _recalculationDistance)
        {
            List<Vector3> newPath = CalculatePath(transform.position, currentTarget);
            SetBlackboardValue(_cachedPath, newPath);
            _lastCalculatedPosition = currentTarget;
        }
        
        return Status.Success;
    }
    
    private List<Vector3> CalculatePath(Vector3 start, Vector3 end)
    {
        // Дорогостоящие расчёты пути
        // ...
        return new List<Vector3>();
    }
}
3. Асинхронные операции — вынесите тяжёлые вычисления в отдельные потоки.
4. Различная частота обновления — не все узлы требуют обновления каждый кадр.

Сравнительный анализ производительности графов и деревьев поведения



Возникает вопрос: как Behavior Graph соотносится с традиционными деревьями поведения по производительности? Мой опыт показывает, что при правильной реализации разница минимальна.

В сценарии с 100 NPC, каждый из которых использует граф из примерно 50 узлов, затраты ЦП на обработку логики поведения составляют около 5-15% в зависимости от сложности вычислений внутри узлов. Для сравнения, аналогичная реализация с использованием традиционных деревьев поведения потребляет примерно столько же ресурсов.

Главное преимущество Behavior Graph заключается в удобстве разработки и отладки, а не в радикальном повышении производительности. Визуальное представление логики значительно упрощает понимание и модификацию сложных поведенческих паттернов.

Альтернативы в сложных случаях



Иногда Behavior Graph может оказаться не лучшим решением для конкретной задачи. В таких ситуациях стоит рассмотреть альтернативы:
1. Конечные автоматы (FSM) — идеальны для систем с чётко определёнными состояниями и переходами. Проще и эффективнее графов для таких задач.
2. GOAP (Goal-Oriented Action Planning) — лучше подходит для ИИ, который должен динамически планировать последовательность действий для достижения цели.
3. Утилитарный ИИ — когда решения должны приниматься на основе взвешивания множества факторов.
4. Нейронные сети — для задач, требующих обучения и адаптации на основе больших объёмов данных.

Особенно сложная ситуация возникает при разработке ИИ для многопользовательских игр, где требуется детерминированное поведение и устойчивость к сетевым задержкам. В таких случаях может потребоваться гибридный подход, комбинирующий Behavior Graph для высокоуровневого принятия решений с более простыми и предсказуемыми системами для критичных компонентов.

Control Flow Graph генератор (MSIL)
Доброго времени суток, выручайте) ищу инструмент для создания графа потока управления по MSIL коду. Т.е. последовательность действий будет такая: ...

Граф в WPF. Graph#
Все привет, подскажите, а у меня есть граф, который я строю на WPF, не могу очистить строку грида, когда очищаю граф. Подскажите, как быть?...

Отчет pdf и graph.drawstring
Вечер добрый) У меня к Вам вопрос как к знатокам C#)) Вопрос в следующем: Формируется отчет в pdf формат (т.к. заполняется grid таблицами и в...

Behavior скрытие курсора мыши
есть необходимость, когда окно находится в развернутом состоянии, при &quot;простое&quot; крусора мыши скрывать его наваял первое, что в голову пришло ...

Как нарисовать точки в zed graph левой кнопкой мыши?
Как нарисовать точки в zed graph левой кнопкой мыши?

Как в Zed Graph нарисовать еще одну ось, параллелную Y, смещенную по оси X
Как в Zed Graph нарисовать еще одну ось,параллелную Y, смещенную по оси X? нужно для наглядности рисунка.

Behavior не компилится, не хватает конструктора. Объявляю, не компилится. Не пойму
В общем, такой вот код, но не компилируется. Что не так? Чёт не пойму вообще. /// &lt;summary&gt; /// Поведение, которое устанавливает...

Есть событие которо реагирует на пересечение любого из объектов Graph, таких как polygon или line?
Надо для курсовой работы

Как запустить компонент Graph Excel?
Как запустить компонент Graph Excel в C#????

Как запустить Graph Excel?
Незнаете как запустить Graph Excel в C#???

Использование microsoft graph для чтения данных файлов Excel с Sharepoint online
Привет! Два дня поиска в интернете не помогли. Поиск по сайту фразы microsoft graph и sharepoint - не находит ничего. Тему создаю в этом...

Не удалось найти тип или имя пространства имен "graph"
Полностью код. Выдает ошибку &quot;не удалось найти тип или имя пространства имен &quot;graph&quot; &quot;. Никак не воткну в чем дело. Я новичок, не ругайте меня...

Метки .net, behavior graph, c#, unity
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Чрезвычайно...чайный...и очень информативный материал...иногда голову ломаешь над какой нибудь
    вроде бы простой вещью...и тут попадается текст...со 100% попаданием в тему...и может всяческих
    Невтонов и быстрых разумом Геймдевов IT-шная среда рождать!!!
    Запись от avedeo размещена 20.03.2025 в 13:52 avedeo вне форума
 
Новые блоги и статьи
[golang] Алгоритм «Хак Госпера»
alhaos 17.05.2026
Алгоритм «Хак Госпера» Хак Госпера (Gosper's Hack) — алгоритм нахождения следующего по величине числа с тем же количеством установленных бит. Придуман Биллом Госпером в 1970-х, опубликован в. . .
Рисование бинарного древа до 6-го колена на js, svg.
russiannick 17.05.2026
<svg width="335" height="240" viewBox="0 0 335 240" fill="#e5e1bb"> <style> <!]> </ style> <g id="bush"> </ g> </ svg> function fn(){ let rost;/ / высота древа let xx=165,yy=210,w=256;
FSharp: interface of module
DevAlt 16.05.2026
Интерфейс модуля F# позволяет управлять доступностью членов, содержащихся в реализации модуля. По-умолчанию все члены модуля доступны: module Foo let x = 10 let boo () = printfn "boo" . . .
Хитросплетение родственных связей пантеона греческих богов.
russiannick 14.05.2026
Однооконник, позволяющий узреть и изучить отдельных героев древней Греции. <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible". . .
[golang] Угол между стрелками часов
alhaos 12.05.2026
По заданным значениям часа и минуты необходимо определить значение меньшего угла между стрелками аналогового циферблата часов. import "math" func angleClock(hour int, minutes int) float64 { . . .
Debian 13: Установка Lazarus QT5
ВитГо 09.05.2026
Эта инструкция моя компиляция инструкций volvo https:/ / www. cyberforum. ru/ blogs/ 203668/ 10753. html и его же старой инструкции по установке Lazarus с gtk2. . .
Нейросеть на алгоритме "эстафета хвоста" как перспектива.
Hrethgir 06.05.2026
На десерт, когда запущу сервер. Статья тут https:/ / habr. com/ ru/ articles/ 1030914/ . Автор я сам, нейросеть только помогает в вопросах которые мне не известны - не знаю людей которые знали-бы. . .
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru