Как сделать бафы, дебафы, статусы или эффекты в Unity

2024 ж. 20 Мам.
6 649 Рет қаралды

Поддержи Лавку здесь:
www.donationalerts.com/r/game...
boosty.to/gamedevlavka
paypal.me/gamedevlavka
Истолкование простого способа сделать бафы, дебафы, статусы, эффекты (да, их по разному называют) в игре на Unity. Как бонус, в видео рассказывается о временных бафах, которые действуют на определенном промежутке времени, например бессмертие на несколько секунд. Со способом, описанном в видео можно делать абсолютно любые эффекты, сюда же можно приплести ответ на вопрос: как сделать шмотки с бонусными эффектами? Которые меняют статы. Ну вы поняли. Все подробненько рассказывается в новом видео из Лавки Разработчика!
Отсылки:
t.me/gamedevlavka - телеграм канал Лавки Разработчика
itch.io/jam/reactor-jam - ДЖЕМ РЕАКТОР
t.me/gamedevtavern - ламповый чат
/ discord - дискорд
vk.com/gamedevlavka - вконтактик
dzen.ru/gamedevlavka - Яндекс Дзен
__________
0:00 Вступление
0:35 Про Джем Реактор
1:10 Пишем фичу
10:54 Пишем пример использования
15:00 Делаем бафы временными

Пікірлер
  • без сомнений один из самых нормальных каналов по юнити!!! было бы очень круто увидеть видео с DI и Zenject как его частный случай, а то инфы очень мало (ну или я тупой)

    @neosinpocket4184@neosinpocket41848 ай бұрын
    • Полностью солидарен!

      @Veles017@Veles0177 ай бұрын
  • Только сегодня хотел сесть за написание системы баффов, и тут бац, видосик) Приятно

    @caveman7246@caveman72468 ай бұрын
  • Спасибо за видео! Принцип добавления баффов через интерфейсы столь же просты, как и через компоненты в ECS - очень понравилось!

    @zloybivshiy7236@zloybivshiy72368 ай бұрын
  • По итогам видоса возникли несколько вопросов: 1) Меня всегда учили, что если объект обладает неким поведением, то используем интерфейс. Если же объект в сущности своей является тем, от чего наследуется - используем абстрактный класс. Так вот не лучше ли вместо интерфейса IBuff использовать некий абстрактный класс Buff и от него наследовать? Так же туда можно завернуть свойства типа lifeTime, Type (Buff, Debuff) и т.д. 2) На сколько удобно/затратно постоянно пересчитывать каждый раз ВСЕ баффы? Не лучше ли считать один, тот который добавляем? 3) Как правильно связать бафы с интерфейсом? В большинстве игр при появлении бафа мы получаем об этом визуальное уведомление (ну например иконка бафа над персонажем). Как это лучше реализовать? P.s. я джун, на какое-то экспертное мнение не претендую

    @goshacar2273@goshacar22738 ай бұрын
    • Отличные вопросы! 1. Сложный вопрос, потому что ответ зависит от требований. IBuff можно (и лучше) сделать генериком, чтобы совсем не зависеть от типа статов. Лайфтайм я бы вынес в родительский класс только если баффы живут между игровыми сессиями, иначе зачем там лайфтайм, ну и если необходимо свойство Buff/debuff - тоже в целом оправданно. Тут смотрите по требованиям, что лучше применить. Интерфейсы - не жестко педолируемая идея в этом видео) 2. Ни на сколько. Мы работаем с типом данных - значение, то есть мы даже не аллоцируем память (ну немножк через форыч, но там совсем крошки, см. далее). И второе, баф или дебаф - это не перманентновыполняемый код, он случается не каждый кадр (хотя даже в этом случае нагрузки бы не было). В общем за скорость работы не переживай. 3. Логика приложения делится на разные слои. И вот этот код, который я писал в CharacterView для тестов - он должен быть в бизнес логике (тут уже не смогу расписать подробно). Назовем его CharacterStatsService, и вот туда мы командуем CharacterStatsService.AddBuff(character, buff), а этот сервис имеет уведомлялку Action OnCharacterBuffed например, и сюда уже можно UI подвязывать. Это непростая тема, чтобы залететь с двух ног, но можно просто в Character, где у тебя AddBuff(IBuff) тоже ивент сделать и подписаться на персонажа.

      @gamedevlavka@gamedevlavka8 ай бұрын
  • Месяц уже хотел реализовать, но не знал как создать ультимативную систему, откладывал - спасибо

    8 ай бұрын
  • Спасибо за урок!

    @skadexgd5057@skadexgd50578 ай бұрын
  • Спасибо Андрей, то, что надо)

    @nepochat@nepochat8 ай бұрын
  • Красавчик. так держать👍

    @Rom3lk@Rom3lk8 ай бұрын
  • Отличный ролик, спасибо)

    @user-mo4fb1tr6c@user-mo4fb1tr6c8 ай бұрын
  • Отличное видео! Хорошо и приятно объясняешь 👍 Решил зайти на ютубчик дабы поизучать нового, пока тасок нету, и в рекомендациях прилетело твое видео :)

    @SergeyBobrov240@SergeyBobrov2408 ай бұрын
  • Актуальная тема :)

    @vitaliySobakinson@vitaliySobakinson8 ай бұрын
  • хороший урок, в райдере когда создаете в конструкторе параметр можно нажать альт энтер и он сам предложит закэшировать его в классе, сам создаст поле и сам присвоит

    @people_fly13@people_fly137 ай бұрын
  • Вроде смотрится здраво, но сильно не хватает, чтобы структура CharacterStats была у персонажа генериком. Из этого следует вопрос: Я бы при генерик реализации внутри баффов проверял *if (playerStats is CorrectPlayerStats)* и только при успешном касте продолжал работу. Есть ли более элегантная система или я +- правильно мыслю? Я неособо люблю касты, но иногда другие решения мне кажутся лишь более громоздкими.

    @devilbob@devilbob8 ай бұрын
    • Да, вопрос хороший, я в другом комменте уже ответил, что еще лучше сделать IBuff генериком, баффы все равно привязываются к конкретным статам, а вот интерфейс лучше сделать чем-то вроде IBuff where T : IStats, а конкретные баффы так: ImmortalityBuff : IBuff, ну и CharacterStats в свою очередь CharacterStats : IStats. Тогда везде можно использовать только IStats и только в реализациях манипулировать конкретикой, даже кастовать не надо

      @gamedevlavka@gamedevlavka8 ай бұрын
  • Лайк коммент не глядя

    @maximtronin4510@maximtronin45108 ай бұрын
  • Спасибо за хорошее видео. Единственное не понял в конце почему это декоратор. Мне больше это напоминает прокси: TempBuff и CoreBuff(в нашем случае ImmortalityBuff) наследуются от IBuff, и TempBuff содержит ссылку на CoreBuff

    @akella0073@akella00738 ай бұрын
  • Дяденька я тебя нашёл, твой канал записан у меня в блокноте как посмотреть весь полностью, канал просто кладесь нужной мне информации

    @user-dm3ej8gn7g@user-dm3ej8gn7g8 ай бұрын
  • Да прикольный способ сделать простую систему бафов для небольшой игры, но через декораторы будет конечно покруче, хотя не всегда и нужно

    @finimensniper3322@finimensniper33228 ай бұрын
  • Отличное видео, спасибо! Хотелось бы узнать как реализовать правильно паузу, чтобы не использовать timescale, после которого и анимация перестает работать)

    @devnem0y@devnem0y8 ай бұрын
    • у Максима Крюкова на канале есть неплохой подход к решению этой проблемы

      @neosinpocket4184@neosinpocket41848 ай бұрын
  • кстати есть плагин Disable KZhead 60 FPS (Force 30 FPS) - а затем наслаждайтесь видео на KZhead без задержек, меньшим использованием процессора (в 2-4 раза), более длительным временем автономной работы и меньшим использованием полосы пропускания! :-)

    @ElmoLovesYou2@ElmoLovesYou28 ай бұрын
  • Здравствуйте. Хотелось бы спросить: не проще сделать коэффициент корректировки здоровья, брони, дамага? Таким образом не нужно будет менять основную переменную. И эффекты смогут легко смешиваться.

    @-._63@-._638 ай бұрын
    • Так это тоже самое, коэффициент корректировки - это тоже стата, можно менять её. В целом в статах можно хоть что менять, как использовать- это другой вопрос. Есть разные механики и разные геймдизайнерские извращения. В формулах расчётов того же урона может такой ад твориться, даже не представляешь. Но ответ на вопрос таков: не не проще, но и не сложнее, т.к. это одно и то же)

      @gamedevlavka@gamedevlavka8 ай бұрын
    • @@gamedevlavka Хорошо. Спасибо за видос!

      @-._63@-._638 ай бұрын
  • Где вы были несколько дней назад... Я уже свою систему модулей наделал

    @moranyt8299@moranyt82998 ай бұрын
    • Рефакторинг ещё не поздно сделать) или уже поздно?

      @gamedevlavka@gamedevlavka8 ай бұрын
    • @@gamedevlavka Ну там как сказать. Там лучше вовсе весь проект переделать, ибо я опять напоролся на проблему спагетти кода. Вот даже из-за этого решил попробовать использовать какой ни будь архитектурный паттерн в будущих проектах. Я правильно понял что в примере вы использовали MVC? И еще вопросик по системе баффов. Не будет проблем с временным бафом? Ведь там при удалении/добавлении текущие и новый баффы добавляются заново и следовательно снова запускается таймер и к эвенту окончания добавляется удаление временного баффа

      @moranyt8299@moranyt82998 ай бұрын
  • Вот прям очень большой косяк есть в данной реализации, что на каждое изменение любого параметра -- придётся писать новый бафф. Плюс -- сами бафы будут заниматься валидацией. То есть, чтобы просто сделать одну и ту же логику изменения, и для хп, и для здоровья, например -- нужно реализовать два баффа и мне кажется это овер неудобным и странным. Наверняка сделано для новичков, но всё равно выскажу, как я бы сделал: сразу бы написал какой-нибудь ValueWithModifications (с названиями всегда туго, так что не важно + я немного опишу его загружено, но это не важно для понимания концепции), где внутри был бы реализован список модификаций (это например ограничения по Min/Max или как раз бафы + им сделать нужно приоритет по операциям, чтобы баундс всегда был в конце). Получится по итогу, что можно сделать и обычный бафф с изменениями, и сделать баундс изменения значения, и сделать временный бафф и т. д. И можно всё что угодно придумать и добавлять. Конечно в Юньке всё ещё нет IValue (или как его там? Ну который реализуют все значимые типы и это позволяет в дженерик классах производить примитивные операции над параметром), так что придётся писать там под int и float, но что есть).

    @Paulsams@Paulsams8 ай бұрын
    • Не совсем понял о чем ты, модно написать баф на каждую параметр статов вместе с валидацией и комбинировать как душе угодно. Параметры батареи задаются снаружи, то есть они конфигурируемые. Где тут на каждое изменение параметра ещё один баф?

      @gamedevlavka@gamedevlavka8 ай бұрын
    • ​@@gamedevlavka Сорри заранее, если моё сообщение показалось немного грубым или около того, хах). Просто я скорее говорил о тех реализациях, что есть в видео. Например DamageBuff -- там одновременно и валидация значения, так и одновременно и изменение значение на какое-то число. Но хотелось бы, например, сделать какую-то более глубокую систему, чтобы не писать под каждый стат -- новый бафф. К примеру если будет РПГ игра, где есть сила, ловкость, интеллект и т. д. (ну к примеру там 20 статов даже), то в таком ключе, который ты показал -- будет казаться, что надо делать один и тот же код под каждый такой стат, а это как-то... Ну не то же). И я просто предложил, что наверное стоит всё таки сам стат сделать хранилищем бафов/модификаций.

      @Paulsams@Paulsams8 ай бұрын
    • Возможно я не прав, потому что в видео предполагалась идея, что баффы будут иметь контекст именно через конкретные реализации, а я себе представляю баффы -- как контейнер модификаций. Например, как оружия в тех же РПГ -- это же по сути контейнер модификаций статов. Ты же не будешь под каждую оружку в игре -- делать наследника IWeapon какого-нибудь, например. Мы будем тогда не объектами, а классами орудовать). Конечно можно было для оружек сделать обёртку, со всеми 100 полями персонажи, где будут в большинстве нули или те же значения -- но это вообще не камельфо. Поэтому лучше будет, если ты сможешь выбирать что конкретно оружка изменяет и насколько. И мне кажется, что баффы и изменение статов -- это всё одна система).

      @Paulsams@Paulsams8 ай бұрын
    • ​@@Paulsams Да, этого можно добиться просто расширив предложенную в видео систему) Например: в ScriptableObject делаешь список бафов, какие угодно. Можно даже сделать генерируемые параметры в рамках каких-то ограничений. Пишется заранее класс декоратор (как в видео TemporaryBuff), который будет применять группу баффов и ему на вход список бафов кидаешь. Внутри он применяется каждый баф к статам. Вся прелесть в том, что получается невероятно гибкий конструктор, который легко конфигурировать. Такой скриптабл можно дать тому же оружию. Можно завернуть эту группу в TemporaryBuff и вуаля - все параметры что ты поменял, поменялись на время. Я понимаю, что в видео показан жёсткий пример, предполагалось, что остальное можно "додумать", но я могу и расширение снять рассказать)

      @gamedevlavka@gamedevlavka8 ай бұрын
    • @@gamedevlavka Ну это были названы преимущества именно декоратора). Я с этим паттерном как раз и не спорил). Просто меня беспокоило больше то, что по видео кажется, что на каждый стат, например: интеллект, броня, сила и т. д. -- надо будет делать отдельную реализацию интерфейса. Хотя я и понимаю, что ничто не запрещает сделать дженерик обобщённый бафф аля, который будет уметь от любого стата прибавлять n-ое число, например. Я опять же скорее просто говорил о том, что мне больше нравится подход, не когда статы валяются в контейнере/группе (как PlayerStats, например) и у баффов есть доступ ко всей группе статов, а когда сами статы являются контейнером баффов, которые влияют только на этот стат). Как-будто больше гибкости мне видется).

      @Paulsams@Paulsams8 ай бұрын
  • Все таки придётся думать как написать правильный таймер😢

    @StratoCatster@StratoCatster8 ай бұрын
    • 😆

      @gamedevlavka@gamedevlavka8 ай бұрын
  • +

    @krivodeling7925@krivodeling79254 ай бұрын
  • При удалении единственного дебаффа на скорость, базовой она не становится и висит, как будто этот дебаф висит до сих пор

    @ivanshamanaev1112@ivanshamanaev11125 ай бұрын
    • Возможно, я тоже дурак, но мне кажется, что в коде есть ошибка, и я её поправил. В класс объекта игрока передаётся декоратор баффа (TemporaryBuff) и лист _buffs заполняется экземплярами декоратора. Сам же класс TemporaryBuff по окончанию своей работы пытается вызвать owner.RemoveBuff(coreBuff), и это ошибка - owner не содержит экземпляры core баффа, он содержит декораторы. Нужно передать owner.RemoveBuff(this). Далее, сам описанный баг вызван тем, что функция RemoveBuff в классе персонажа не выполняет ApplyBuffs(), применяя отмену баффа, а просто удаляет его из списка и всё. Однако, если просто дописать такую строку, вы получите бесконечный луп (почти, там сложный эффект, но вам не понравится) - ведь вызывая... foreach (var buff in _buffs) { CurrentState = buff.ApllyBuff(CurrentState); } ... вы каждый раз перезапускаете таймер, так как вы обращаетесь не к баффу напрямую, а к декоратору, запускающему таймер. Я решил эту проблему, введя банальную булку, которая говорит, был ли уже запущен таймер. Если да - не трогать его, а просто пропустить через себя вызов и вернуть статы после применения core баффа. public EnemyStats ApllyBuff(EnemyStats stats) { var newStats = coreBuff.ApllyBuff(stats); if (!timerOn) { timerOn = true; monoTimer.StartTimer(lifeTime); monoTimer.Completed += () => owner.RemoveBuff(this); } return newStats; } В моей ситуации, некоторые баффы могут накладываться до 4-х раз в секунду, при том, они никак не должны взаимодействовать между собой - каждый таймер должен работать отдельно, и как только последний из них закончит свою работу - эффект должен сняться. Надеюсь, помог

      @buran_m3248@buran_m32485 ай бұрын
  • всё же очень слабое развитие темы. Для системы бафов хочется иметь: проверку на уже наличие бафа этого типа, на наличие бафа генерик-типа, возможность стакать баффы или переписывать (заменять/обновлять) их новыми. Хочется возможность снятия бафа по типу, ведь не всегда известен конкретный референс на бафф. Собственно элементарная поддержка поведений бафов при apply/remove/ontick.

    @nightyonetwothree@nightyonetwothree8 ай бұрын
    • Подразумевается, что все перечисленное - уже понятно как делать, это лишь проверки, условия, применения - полная конкретика под конкретные нужды. Если очень надо, я могу дополнить, но не уверен, что это востребовано

      @gamedevlavka@gamedevlavka8 ай бұрын
KZhead