Найти тему

Гибкая система заклинаний в Unity

Сегодня речь пойдёт о такой интересной теме как проектирование гибких систем в Unity. В качестве примера я выбрал систему заклинаний так, как обычно вокруг неё обычно идут самые жаркие споры.

Эту систему я разрабатывал со своими учениками в рамках недельного интенсива «проектирование в Unity». Также подобный подход я использую на своей работе и он показал себя лучшим образом.

В корне он состоит в использование полиморфных Scriptable Object. Готовый код вы можете найти в репозитории потока занятий. Ссылка на папку — https://github.com/HolyMonkey/UnityDesign-1/tree/master/Assets/Scripts/Skills

Перед тем как мы занялись разработкой кода, я выставил нам требования которые позволят понять границы. Проектировать без требований — есть занятие бессмысленное, а иногда и откровенно вредящее. Хорошо спроектированная система — это система которая удовлетворяет текущим требованиям и легка в понимание, поддержки, конфигурации и тестирование. Такая система и становится «расширяемой».

Основные требования:

1) У нас есть некое заклинание которые игрок может разместить в два действий. Первое — выбрать активное заклинание в UI. Второе кликнуть по экрану.

2) Каждое заклинание имеет некую логику размещения. Так некоторые заклинания размещаются на целей (Клик должен происходить на конкретной цели), они так же разделяются на вражеские с союзные цели (условный FireBall и HealthTouch). А некоторые групповое размещение, тогда клик может происходить в свободной зону.

3) Заклинания должны состоять из наборы действий которые применяться к выбранным целям. Для начала условились на уроне и лечение.

-2

Пример кода на самом верхнем уровне которые выполняет заклинание. Для тестирование активное заклинание назначается через инспектор (следующий модуль занятий предполагал иньекцию через Zenject).

Как вы можете заметить заклинание состоит из двух основных точек входа (публичных методов): SelectTargets и ApplyActions. Первый выбирает цели для заклинания с позиции на экране, а второй применяет сокрытые действия.

-3

Самое заклинание это всего лишь Scriptable Object-контейнер который содержит в себе ссылку на нужную логику размещения и список действий.

Настраивается в редакторе всё вот так

-4

Абстракция под которой скрывается алгоритм выбора целей.

-5

Пример реализация на одну цель.

-6

Реализация этого всего через полиморфные Scriptable Object'ы позволяет нам гибко настраивать базу заклинаний без перебегания к сторонним плагинам (например Odin Inspector, который позволяет выбирать реализацию интерфейса).

Абстракция скрывающая действие

-7

Пример реализация с уроном

-8

Заданный курс ещё больше раскрывается при покрытие этой систему тестам и использование DI. Например в случае с классом AbillityPlaceLogicSingleTarget разумней было бы производить иньекцию камеры через которую планируется рейкастинг.

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

Например стандартный путь реализации подобного стоял бы в определение перечисления: TargetType { Single, AOE } и перечисления Action { Heal, Damage }. Что свелось бы в лучшем случае к паттерну Visitor (на самом деле почти никогда до этого в итоге не доходят) а в худшем (и чаще всего так и есть) к повсеместному использованию операторов ветвления для ручного определения что же делать с определённым заклинанием