Полезности
November 15, 2023

Не наноси урон втупую

Во многих туториалах по Godot/Unity итд. можно увидеть, что нанесение урона реализуют самым банальным образом

То есть просто вбрасывают голую цифру урона и на этом всё

Однако, такой подход плох тем, что у тебя нет возможности в общем виде модифицировать урон и подвязываться на различные события - от кого пришёл урон, чем его нанесли, "нужно удвоить, если юнит застанен" и так далее. И сам процесс вычисления урона остаётся необобщённым

У себя в игре весь урон я реализовываю через вот такой класс. Назвал его DamageTransaction

А так же ряд методов, чтобы можно было "собирать" транзакцию

Тут большая простыня Ctrl+C Ctrl+V, но да ладно:

Выглядит это так

commit выглядит следующим образом:

Получаемый юнит же при вызове receive_damage_transaction смотрит, должен ли он получить демедж (ибо у него может быть неуязвимость, например), прогоняет модификации (а-ля порезать урон, если это крит), и затем уже получает урон (или не получает)

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

Дополнительно дёргается сигнал в общей шине, на который можно подписаться из любого места

Что это даёт

  • Наносимый урон собирается по кусочкам
    • Это даёт возможность модифицировать из любого источника в несколько этапов - будь то скилл; перк, его модифицирующий или же персонаж, его получающий
  • Транзакция содержит в себе всю необходимую информацию
    • Это даёт возможность делать тысячи модификаций в духе "если тычка была летальной", "если тычка ударила врага такого-то вида", "если на тебе висит какой-то статус-эффект" итд.
    • Также, засчёт того, что источник урона тоже лежит в транзакции, можно класть информацию прямо в ноды снарядов/аоешек/итд. К примеру, хочешь ты, чтобы стрела стакала урон за каждого прошитого моба. Это очень легко реализовать на этапе сбора транзакции - просто добавляешь счётчик поражённых врагов снаряду, а на этапе сборки транзакции добавляешь множитель от source.affected_units_count
    • Результат нанесения урона также можно узнать из транзакции
  • Глобальный сигнал
    • Даёт возможность реализовать "если кто-то получил урон"
      • Учитывая всё вышеназванное, это можно дополнить любыми необходимыми критериями

Напоследок - пару примеров утилизации

  • Перед получением урона юнит дёргает метод _can_hurt каждого статус-эффекта, на нём висящего. Вот, к примеру, как легко реализуется "юнит может получить урон только от себя"
  • У сущности DamageDealer, которая отвечает за нанесение урона (путём, как раз-таки, генерации транзакций), есть пайп, который вызывается с транзакцией перед её коммитом и считает суммарный множитель урона

Перки просто добавляют в этот пайп дополнительные шаги

Вот так, например, меняется урон тычки от количества врагов с меткой:

А вот так - если где-то прозвенел крит, можно обнулить кулдаун скиллов:

P.S. Очень жаль, что сигналы всё ещё не полноправные члены GDScript и ими нельзя оперировать, как переменными. Приходится создавать свой кастомный ивент и писать бойлерплейт код, чтобы докинуть сигнал куда надо

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