Dungeon Raiders - Дневник разработчика 1. Анимация или дьявол в мелочах
В данном посте я бы хотел осветить подход в проектировании системы анимации и их связи с игровыми событиями, которого придерживаюсь я и который может быть полезен вам.
Аннотация: Ваш покорный слуга исповедует принцип свободной информации. Я работаю на бесплатном движке, пишу на бесплатном языке в бесплатной среде программирования, рисую модельки в бесплатной среде моделирования и учился всему на бесплатных туториалах на бесплатном YouTube. Лучшее, что я могу сделать, чтобы отблагодарить сообщество - не быть снобом и делиться своим опытом. Все ассеты - код, модели и т. д. и т. п. - доступны в репозитории на GitHub. Пользуйтесь, заимствуйте, только не списывайте точь-в-точь, чтобы не спалили :)
Само собой речь пойдет НЕ о расширении движка - под системой анимации я подразумеваю исключительно архитектуру классов и логику их связей, которая кажется мне довольно универсальной, так как помогает при реализации разных проектов. Ведь иногда в погоне за красивой картинкой у вас может получиться такой вот Супер-Босс:
Так что давайте разберемся с ним по фазам...
Вариативность анимации
Я верю, что львиная доля положительных впечатлений от медиа-продукта (каковым является и видео-игра) зависит от деталей, на которые, на первый взгляд, никто не будет обращать внимание. Например, повтор анимации. Поэтому в каждом проекте я стараюсь добиваться разнообразия:
С точки зрения игровой логики происходит одно и то же событие, но для Игрока то, что Герой чередует ноги при прыжке - уже выглядит живее.
Техническая задача здесь заключается в том, чтобы правильно разделить ответственность: Герой должен прыгнуть и ему все равно, какая из двух (трех, четырех, пятидесяти (sic!)) вариаций анимации будет проиграна.
Я реализую такой подход в следующем:
Класс Unit - он хранит информацию о текущем запасе здоровья, маны, уроне и отвечает за сами игро-механические взаимодействия: атаку, прыжки, любые другие способности - но он мало что представляет о своем визуальном представлении.
Класс UnitAnimationHandler - напротив, мало что знает о самой боевой единице, но за то в курсе состояния Animator'а и умеет воспроизводить и обрабатывать события анимации.
AnimationClipGroup - это вспомогательный класс-контейнер, который хранит информацию о том, сколько вариаций анимации для данного действия существует и можно ли повторять эти вариации подряд?
Таким образом, мы можем определить сколь угодно много "групп" - помимо прыжков это может быть атака и бездействие (idle).
События анимации
Признаться, я был счастлив, когда узнал о существовании событий анимации в Unity. Этот инструмент, казалось, развязывает руки. Ни о какой интерактивности и отзывчивости не может идти и речи, если вы пытаетесь отмерить воспроизведение звука атаки и вызов метода нанесения урона по времени "на глаз" - то ли дело привязка этих событий к конкретному кадру!
Но очень скоро я столкнулся с архитектурной проблемой: с точки зрения игровой логики Атака - лишь одна из десятков (возможно, сотен) способностей - а значит, нужно завести классу AnimHandler столько же десятков (тысяч) методов-событий анимации? Конечно же нет.
Вместо этого я создал универсальные события, которые подходят для описания процесса применения любой способности:
Как видно на фрагменте кода - события анимации просто устанавливают некие логические переменные ("флаги", если пользоваться терминами классического программирования). Эти флаги "прослушивают" Способности:
Таким образом получается универсальная слабосвязанная система:
- Юнит (по команде Игрока или ИИ) приводит в действие Способность.
- Способность запрашивает проигрывание анимации по названию триггера.
- При проигрывании анимации устанавливаются универсальные флаги.
- Активная Способность мониторит наличие флагов и реагирует при их установке.
Вариации внешнего вида
Оказалось, что класс UnitAnimationHandler, отделенный от логики боевой единицы, годится для обработки любой информации, например, для генерации случайного внешнего вида.
Так модель зомби имеет 3 варианта внешнего вида головы, которые являются отдельными игровыми объектами:
Информация о вариациях внешнего вида хранится с помощью 2-ух вспомогательных классов:
Что выглядит в Инспекторе примерно так:
В итоге, при инициализации модели боевой единицы все объекты в группе мешей (кроме случайно выбранного), удаляются:
Такой подход позволяет генерировать случайные черты внешности персонажа независимо друг от друга, обходясь 1 префабом противника.
Когда-то и меня вела дорога приключений...
Игра ощущается живой, интерактивной и понятной, когда объекты ведут себя так же, как мы бы ожидали от них в реальном мире. Но при реализации игровых механик часто приходится идти на компромисс. Например, мы привыкли, что снаряды появляются в момент выстрела и исчезают при столкновении с целью.
"Но что если..." - подумал я - "...стрелы будут застревать в Герое, как в Скайриме?" Такие мелочи не свойственны казуальным играм, к каковым относится и данный проект, но я решил, что это будет красивой деталью.
Чтобы задать точки, к которым могут "прилепать" снаряды я создал класс-контейнер (т. е. класс, не содержащий логики):
И пару списков для хранения ссылок на эти точки:
Теперь можно сделать так, чтобы модель любого снаряда "прилипала" к юниту при нанесении урона:
Как видно в последней строчке - AnimationHandler сохраняет ссылки на Transform прилипших снарядов. Это нужно для того чтобы при исцелении персонажа зельем все эти стрелы отваливались: