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

Поделиться
HTML-код
  • Опубликовано: 29 дек 2024

Комментарии • 43

  • @neosinpocket4184
    @neosinpocket4184 Год назад +7

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

    • @Veles017
      @Veles017 Год назад

      Полностью солидарен!

  • @zloybivshiy7236
    @zloybivshiy7236 Год назад +2

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

  • @caveman7246
    @caveman7246 Год назад +3

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

  •  Год назад +2

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

  • @goshacar2273
    @goshacar2273 Год назад +9

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

    • @gamedevlavka
      @gamedevlavka  Год назад +12

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

  • @Rom3lk
    @Rom3lk Год назад +1

    Красавчик. так держать👍

  • @SergeyBobrov240
    @SergeyBobrov240 Год назад +1

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

  • @nepochat
    @nepochat Год назад +1

    Спасибо Андрей, то, что надо)

  • @skadexgd5057
    @skadexgd5057 Год назад +1

    Спасибо за урок!

  • @people_fly13
    @people_fly13 Год назад +1

    хороший урок, в райдере когда создаете в конструкторе параметр можно нажать альт энтер и он сам предложит закэшировать его в классе, сам создаст поле и сам присвоит

  • @ОлегНахаев-у7с
    @ОлегНахаев-у7с Год назад

    Отличный ролик, спасибо)

  • @devilbob
    @devilbob Год назад +4

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

    • @gamedevlavka
      @gamedevlavka  Год назад +1

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

  • @devnem0y
    @devnem0y Год назад +1

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

    • @neosinpocket4184
      @neosinpocket4184 Год назад

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

  • @vitaliySobakinson
    @vitaliySobakinson Год назад +1

    Актуальная тема :)

  • @akella0073
    @akella0073 Год назад

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

  • @finimensniper3322
    @finimensniper3322 Год назад +1

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

  • @moranyt8299
    @moranyt8299 Год назад +1

    Где вы были несколько дней назад... Я уже свою систему модулей наделал

    • @gamedevlavka
      @gamedevlavka  Год назад +1

      Рефакторинг ещё не поздно сделать) или уже поздно?

    • @moranyt8299
      @moranyt8299 Год назад

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

  • @maximtronin4510
    @maximtronin4510 Год назад +2

    Лайк коммент не глядя

  • @Paulsams
    @Paulsams Год назад

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

    • @gamedevlavka
      @gamedevlavka  Год назад

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

    • @Paulsams
      @Paulsams Год назад

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

    • @Paulsams
      @Paulsams Год назад

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

    • @gamedevlavka
      @gamedevlavka  Год назад +1

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

    • @Paulsams
      @Paulsams Год назад

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

  • @-._63
    @-._63 Год назад +1

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

    • @gamedevlavka
      @gamedevlavka  Год назад +1

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

    • @-._63
      @-._63 Год назад +1

      @@gamedevlavka Хорошо. Спасибо за видос!

  • @SlothHuntOnYou
    @SlothHuntOnYou Год назад

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

  • @РщивтИвоил
    @РщивтИвоил Год назад +2

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

  • @ivanshamanaev1112
    @ivanshamanaev1112 Год назад

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

    • @buran_m3248
      @buran_m3248 Год назад +2

      Возможно, я тоже дурак, но мне кажется, что в коде есть ошибка, и я её поправил. В класс объекта игрока передаётся декоратор баффа (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-х раз в секунду, при том, они никак не должны взаимодействовать между собой - каждый таймер должен работать отдельно, и как только последний из них закончит свою работу - эффект должен сняться. Надеюсь, помог

  • @StratoCatster
    @StratoCatster Год назад +1

    Все таки придётся думать как написать правильный таймер😢

  • @krivodeling7925
    @krivodeling7925 Год назад +1

    +

  • @nightyonetwothree
    @nightyonetwothree Год назад

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

    • @gamedevlavka
      @gamedevlavka  Год назад

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