Скромный ИИ Elden Ring: как FROMSOFT программирует поведение врагов
FROMSOFT славится сложным и разнообразным ИИ врагов. Но когда посмотреть на код Elden Ring (написанный на Havok Script, интерпретируемом Lua), видно что это не deep learning, а elegantly простая система.
Основный инструмент, Goal (цель/состояние), не просто конечный автомат (FSM), а Pushdown Automaton (стек состояний). Каждый фрейм враг обновляет Goal на верхушке стека. Goal может push-ить sub-goals на стек, те выполняются следующие фреймы.
Пример: CoolBossBattle (top-level) push-ит Attack goals разных типов:
[CoolBossBattle] ← Attack(R2) ← Attack(R2, Combo) ← Attack(R2, Finisher)
Когда Attack успешно выполнится, Goal pop-ится со стека. Если fail, раскручивается стек обратно к parent-Goal и тот push-ит новый action.
Когда Goal активируется первый раз (activate-функция), он выбирает следующее действие через взвешенный рандом по дистанции до врага, HP, и других параметров. Веса динамично меняются через таймауты действий (cooldown).
Второй инструмент, Interrupt: события (урон, spell-casting врага, fire debuff) могут прерывать текущий Goal stack и forseполучить новый action. Это делает боссов интерактивными: если врага поджечь, он бросает текущее и атакует; если ты кастанул spell, он может interrupt и тебя атаковать.
Третий, Actor Data: простой массив floats, которые Goals читают и пишут. Blackboard-паттерн, но минималистичный.
Всё это написано на Lua, компилируется in-engine, производительность хорошая потому что load-bearing функции (Attack, MoveTo) в C++.
Таким образом: цель достигается не через нейросети и попытки обучать врагов, а через ручной design каждого boss-типа, но design достаточно гибкий, чтобы враги выглядели живыми и непредсказуемыми.
Ключевые факты
- Pushdown Automaton, а не Finite State Machine: стек Goals позволяет иерархичное поведение и smooth восстановление после fail
- Взвешенный рандом по дистанции и HP: враг выбирает action вероятностно, не детерминировано, что создает иллюзию интеллекта
- Interrupt-система: события (damage, spell, fire) прерывают текущий stack и форсят новые actions в реальном времени
- Всё на Lua (Havok Script), за исключением Load-bearing функций в C++: баланс между гибкостью и производительностью
- Бросок на дизайнера, а не на ML: каждый boss type ручной, но система достаточно flexible чтобы выглядеть естественным
Почему это важно
Показывает, что в game AI не всегда нужны сложные техники. Простая, хорошо-спроектированная система (Pushdown Automaton + weighted random) + ручной design часто лучше чем попытка обучить нейросеть. Урок для разработчиков: иногда elegance и clarity важнее передовых методов.
Кому это важно
Game developers, которые думают про ИИ врагов. Исследователи в game AI. Люди, интересующиеся архитектурой игровых систем. Фанаты Souls games, которые хотят понять как враги работают.
Как это применить
Если ты делаешь boss-fight, заимствуй идею Goal stack: это позволяет ветвящемуся поведению быть понятным и поддерживаемым. Interrupt-система хороша для реактивности врага на player actions. Взвешенный рандом делает врага менее предсказуемым.
Можно ли доверять
Автор (nega.tv) не original research, а читает декомпилированный код. Код был reverse-engineered другими, и автор просто интерпретирует. Примеры на Rust pseudocode для ясности. Объяснения внутренней архитектуры звучат правдоподобно для FROMSOFT, у них репутация за такой дизайн.
Риски и подводные камни
Reverse engineering может содержать ошибки в интерпретации. Код Elden Ring специфичен для Havok и может не быть портируемым в другой engine без переписи. Real-time performance зависит от профессиональной оптимизации, которую любитель может не достичь.
«Each frame Actors will update the Goal on top of their stack of Goals. When the Goal updates, it can then push more Goals as Sub-Goals onto the stack, the topmost of which will execute next frame.»
— nega.tv об архитектуре Elden Ring AI