Давайте продолжим наш Hack’n’slash и дадим нашему герою особые навыки!

⬅️ Урок № 22. Улучшение статистики игроков | ТОЦ | Урок №24: Добавляем некоторые навыки и способности! 2/2 ➡️

В предыдущем эпизоде ​​мы работали над базовой статистикой нашего героя и над тем, как вывести из нее несколько интересных переменных. Это хороший способ увеличить силу нашего персонажа на протяжении всего приключения, особенно с добычей, но обычно это не единственный инструмент, который есть в распоряжении игроков hack’n’slash…

… еще один важный элемент — это особые навыки и сверхспособности!

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

Тем не менее, мы все еще можем обсудить, как реализовать такую ​​​​систему, и разобрать простой пример, чтобы попрактиковаться :)

Итак, в этой паре эпизодов давайте погрузимся в специальные заклинания и навыки!

Определение данных для наших навыков

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

Первые три параметра можно легко упаковать в Scriptable Object, как обычно:

Однако для функции вычисления значения все немного сложнее. Поскольку мы не можем редактировать C# в инспекторе, мы должны записать его куда-нибудь и связать с остальными нашими данными. Но было бы лучше избегать большой проверки if-else if в нашем классе SkillData, так как же мы можем «извлечь» эту информацию из другого места?

Крутой трюк здесь — использовать делегатов. По сути, делегаты C# — это способ объявления прототипа функции как типа, чтобы вы могли затем передавать функции в качестве входных данных для другой функции.

Например, предположим, что мы создали еще один скрипт с именем SkillEffects, который содержит статический класс. Это то, где я хочу меньше всего использовать все мои фактические функции вычисления навыков. Что ж, я могу объявить какой-нибудь глобальный делегат в моем пространстве имен Skills, который не имеет входных параметров и не возвращает значения:

А затем использовать его в моем классе, чтобы создать словарь, сопоставляющий определенный код навыка с его вычислительной функцией:

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

Теперь мне просто нужно добавить некоторый сквозной метод к объекту SkillData, чтобы он автоматически выбирал правильную логику:

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

Затем в нашем SkillData мы изменим тип поля code на этот тип перечисления:

Наконец, нам просто нужно заменить тип в нашем классе SkillEffects, и все готово!

Это означает, что теперь в инспекторе у нас есть выпадающий список с возможными значениями, что облегчает правильное определение;)

Хорошо, с этой быстрой настройкой, давайте перейдем к самой интересной части и начнем использовать этот навык!

Используя наши навыки, чтобы убить врага с одного выстрела!

Запуск определенной анимации

Здесь мы не будем усложнять и скажем, что наш Power Strike мгновенно наносит удар перед нашим персонажем и наносит безумное количество урона. Таким образом, если мы столкнемся с Грубым врагом из предыдущих эпизодов, мы сможем убить его одним ударом.

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

На самом деле, для простоты я буду повторно использовать свою первую анимацию атаки, но скопирую ее в другое состояние в моем аниматоре под названием «PowerStrike». Таким образом, я могу связать его с инструментом «Любое состояние»: это означает, что независимо от того, в каком состоянии сейчас находится аниматор, мы сможем напрямую перейти в состояние «PowerStrike».

Чтобы сделать этот переход, я буду использовать новую переменную триггера под названием «PowerStrike»; затем для окончательного перехода обратно в состояние «Idle» я оставлю его пустым и просто дождусь окончания анимации.

Вот мой обновленный аниматор со всеми этими дополнениями:

Чтобы позволить навыку активировать этот триггер, давайте добавим к нашему PlayerController новую функцию под названием TriggerState():

Обратите внимание, что эта логика предполагает, что ваше состояние навыка и триггер для перехода к нему имеют одинаковые имена.

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

Вызванный здесь ResetAttackCombo() легко закодировать в нашем PlayerController:

Тестирование!

Теперь предположим, что мы добавили тестовую кнопку графического интерфейса в наш скрипт PlayerManager, чтобы опробовать этот рабочий процесс. Я буду использовать IMGUI от Unity, чтобы сделать это быстро — дело в том, что на экране должна быть кнопка, по которой я могу нажать, чтобы вызвать свой навык Power Strike, например:

Где мой массив _skills заполняется вручную в инспекторе (в конечном итоге должна быть конечно логика разблокировки/изучения новых умений, которая бы заполняла этот массив постепенно):

Если я нажму кнопку в своей игре, я увижу, что персонаж действительно бьет, как и ожидалось:

Нанесение дополнительного урона!

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

Нам нужно сказать игроку, что будущий удар особенный, с определенным количеством урона. Давайте сделаем это в нашей функции PowerStrike(): мы установим некоторую статическую переменную для нашего PlayerController (нет риска загрязнить экосистему, так как у нас есть только одна копия этого скрипта во всей игре) и определим некоторый «override damage»:

Затем в нашем PlayerAttackManager мы проверим, установлено ли это значение. Если это так, мы используем его; в противном случае мы вычисляем ущерб, как и раньше:

Если вы снова запустите игру, о чудо! Вы увидите, что ваш Power Strike действительно мощный :)

Сброс переопределения урона

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

Если вы попытаетесь отбросить Power Strike от врага, а затем нанести обычный удар по Brute, вы увидите, что он действительно вернулся к своему нормальному количеству урона ;)

Заключение

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

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

Чтобы читать больше моего контента и статей многих других замечательных писателей из Medium, рассмотрите возможность стать участником! Ваш членский взнос напрямую поддерживает писателей, которых вы читаете.



⬅️ Урок № 22. Улучшение статистики игроков| ТОС