Укрощение Unity, часть следующая: От настройки "режима Бога" до дилеммы модульного мозга (#AI-Evolution #5)

Последние дни выдались довольно продуктивными. Изучать C# и Unity после связки React + Python — одно удовольствие. Сложно, местами больно, но приятно. Спешу поделиться тем, что удалось сделать за эти дни.

В результате одного бага породил хаос на карте) Хорошая новость, симуляция в текущем виде неплохо держит такое количество объектов на поле.
В результате одного бага породил хаос на карте) Хорошая новость, симуляция в текущем виде неплохо держит такое количество объектов на поле.

Режим Бога: пишем RTS-камеру с приключениями

Первым делом я убрал с карты тестовую модельку болванчика и настроил полноценную камеру для «режима Бога». Проще говоря — классическое управление в стиле стратегий. Казалось бы, банальная задача, но условий пришлось прописать немало:

1. Позиционирование: чтобы камера летала над полем как положено, нужно было подобрать правильное расстояние до земли, жестко заблокировать изменения по вертикальной оси (чтобы она не проваливалась под текстуры) и наклонить её под стандартные для жанра 45 градусов.

2. Война с кнопками (WASD): сначала игра вообще отказывалась реагировать на клавиатуру. Как оказалось, дело в конфликте старой и новой систем ввода Unity. По умолчанию движок врубил новую, «консольную» модель. Пришлось залезть в настройки проекта и разрешить использовать обе системы одновременно. Заработало.

3. Движение у краев экрана: тут обошлось без костылей — прописываешь несколько условий о нахождении курсора у края экрана, и мышка двигает карту как надо.

4. Вращение карты: при зажатой правой кнопке мыши камера сначала поворачивалась ровно на один кадр и застывала. Выяснилось, что при клике нужно принудительно «блокировать» и скрывать курсор (Cursor.lockState), чтобы движок корректно считывал перемещение мыши.

5. Плавный зум: тут тоже довольно интересно оказалось. Сначала я прописал изменение просто по вертикальной оси, но выглядело это так себе. Поэтому в финальном варианте при прокрутке колесиком менялись значения по осям Y и Z и стало выглядеть красиво.

Суровый симулятор эволюции, который мы заслужили: гладкий как стекло луг и целеустремленные агент-шарики.
Суровый симулятор эволюции, который мы заслужили: гладкий как стекло луг и целеустремленные агент-шарики.

GUI и первая кровь в дебаге

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

Укрощение Unity, часть следующая: От настройки "режима Бога" до дилеммы модульного мозга (#AI-Evolution #5)

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

Укрощение Unity, часть следующая: От настройки "режима Бога" до дилеммы модульного мозга (#AI-Evolution #5)

Бизнес-логика и «неуравновешенные» агенты

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

Куст и его зона "столкновения" или коллизии.
Куст и его зона "столкновения" или коллизии.

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

Новая модель с добавленным выходом.
Новая модель с добавленным выходом.

Я добавил в сеть третий выход, отвечающий за «намерение/действие». Он нормализуется в диапазоне от -1 до 1:

· от -1 до -0.3: враждебные или агрессивные действия (атака).

· от 0.3 до 1: дружелюбные и мирные действия (социализация, торговля, ну и еще может что-то придумаю).

· от -0.3 до 0.3: «шум» или зона неопределенности

Почему я не сделал жесткую двухточечную систему (-1 — враг, 1 — друг)? Представьте, что значение этого выхода колеблется в районе нуля. Без буферной зоны мы бы получили классического психопата: агент подходит к товарищу, признается в любви, в следующую секунду бьет кулаком в челюсть, а потом снова обнимает. Мертвая зона в центре должна защитить симуляцию от таких метаний.

Тригонометрия против слоев Unity

Чтобы обучить нейросеть этой схеме, нужно было вернуть агентам трехсекторный конус обзора, который был у меня в веб-версии. В Unity это прописывается через Physics.OverlapSphere и расчет углов (Vector3.Angle / Vector3.Cross). Я думал, что при переходе в 3D расчет зоны обзора окажется гораздо сложнее чем было в прошлом 2D-варианте. Но на удивление, в C# это вышло даже проще, чем кодить тригонометрию на чистом Python вручную.

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

Для наглядности я сразу визуализировал конус зрения линиями в окне игры. И тут мой свеженаписанный дебаг-интерфейс окупился в первую же минуту. Смотрю на панель параметров: агент стоит в упор перед кустом еды, а датчики зрения показывают «0» — пустота. Сущность буквально слепа.

Укрощение Unity, часть следующая: От настройки "режима Бога" до дилеммы модульного мозга (#AI-Evolution #5)

Оказалось, я не учел ключевую особенность Unity — слои (Layers). По умолчанию все объекты падают на слой Default. А когда я прописывал функцию фильтрации объектов в конусе зрения, я не указал маску слоев, по которым нужно искать кусты. Движок честно игнорировал еду под ногами агента. Исправил за пару строк, теперь они зрячие.

Дилемма на будущее: Монолит против Модулей

Сейчас я стою на распутье в плане архитектуры мозга сущностей. Есть два пути:

1. Монолит. Одна огромная нейросеть. На вход подаем вообще всё: 3 входа зрения на кусты, 3 входа зрения на сородичей, уровень голода, здоровья и т.д. На выходе получаем наши три значения (скорость, поворот, намерение). Плюс — сеть сама внутри себя строит неочевидные корреляции. Минус — обучать такого монстра придется очень долго.

2. Модули. Разбиваем мозг на изолированные подсети. Условно: подсеть поиска еды, подсеть боевки, подсеть социализации. И переключаем их жесткими условиями извне. Например: если уровень голода > 50, управление перехватывает модуль еды. Если агент сыт — включается модуль «строителя» или «социализатора».

У обоих методов свои плюсы и минусы. Пока мои шарики учатся просто стабильно находить и есть кусты, время подумать у меня еще есть.

Спасибо за внимание! Обязательно делитесь в комментариях любыми мыслями, критикой или идеями по проекту. Я все внимательно читаю, анализирую и наматываю на ус. Кто знает — возможно, именно ваше предложение или каверзный вопрос внезапно подтолкнет меня к какому-то крутому архитектурному решению, которое полностью изменит этот цифровой мир.

33
11