без сомнений один из самых нормальных каналов по юнити!!! было бы очень круто увидеть видео с DI и Zenject как его частный случай, а то инфы очень мало (ну или я тупой)
По итогам видоса возникли несколько вопросов: 1) Меня всегда учили, что если объект обладает неким поведением, то используем интерфейс. Если же объект в сущности своей является тем, от чего наследуется - используем абстрактный класс. Так вот не лучше ли вместо интерфейса IBuff использовать некий абстрактный класс Buff и от него наследовать? Так же туда можно завернуть свойства типа lifeTime, Type (Buff, Debuff) и т.д. 2) На сколько удобно/затратно постоянно пересчитывать каждый раз ВСЕ баффы? Не лучше ли считать один, тот который добавляем? 3) Как правильно связать бафы с интерфейсом? В большинстве игр при появлении бафа мы получаем об этом визуальное уведомление (ну например иконка бафа над персонажем). Как это лучше реализовать? P.s. я джун, на какое-то экспертное мнение не претендую
Отличные вопросы! 1. Сложный вопрос, потому что ответ зависит от требований. IBuff можно (и лучше) сделать генериком, чтобы совсем не зависеть от типа статов. Лайфтайм я бы вынес в родительский класс только если баффы живут между игровыми сессиями, иначе зачем там лайфтайм, ну и если необходимо свойство Buff/debuff - тоже в целом оправданно. Тут смотрите по требованиям, что лучше применить. Интерфейсы - не жестко педолируемая идея в этом видео) 2. Ни на сколько. Мы работаем с типом данных - значение, то есть мы даже не аллоцируем память (ну немножк через форыч, но там совсем крошки, см. далее). И второе, баф или дебаф - это не перманентновыполняемый код, он случается не каждый кадр (хотя даже в этом случае нагрузки бы не было). В общем за скорость работы не переживай. 3. Логика приложения делится на разные слои. И вот этот код, который я писал в CharacterView для тестов - он должен быть в бизнес логике (тут уже не смогу расписать подробно). Назовем его CharacterStatsService, и вот туда мы командуем CharacterStatsService.AddBuff(character, buff), а этот сервис имеет уведомлялку Action OnCharacterBuffed например, и сюда уже можно UI подвязывать. Это непростая тема, чтобы залететь с двух ног, но можно просто в Character, где у тебя AddBuff(IBuff) тоже ивент сделать и подписаться на персонажа.
хороший урок, в райдере когда создаете в конструкторе параметр можно нажать альт энтер и он сам предложит закэшировать его в классе, сам создаст поле и сам присвоит
Вроде смотрится здраво, но сильно не хватает, чтобы структура CharacterStats была у персонажа генериком. Из этого следует вопрос: Я бы при генерик реализации внутри баффов проверял *if (playerStats is CorrectPlayerStats)* и только при успешном касте продолжал работу. Есть ли более элегантная система или я +- правильно мыслю? Я неособо люблю касты, но иногда другие решения мне кажутся лишь более громоздкими.
Да, вопрос хороший, я в другом комменте уже ответил, что еще лучше сделать IBuff генериком, баффы все равно привязываются к конкретным статам, а вот интерфейс лучше сделать чем-то вроде IBuff where T : IStats, а конкретные баффы так: ImmortalityBuff : IBuff, ну и CharacterStats в свою очередь CharacterStats : IStats. Тогда везде можно использовать только IStats и только в реализациях манипулировать конкретикой, даже кастовать не надо
Отличное видео, спасибо! Хотелось бы узнать как реализовать правильно паузу, чтобы не использовать timescale, после которого и анимация перестает работать)
Спасибо за хорошее видео. Единственное не понял в конце почему это декоратор. Мне больше это напоминает прокси: TempBuff и CoreBuff(в нашем случае ImmortalityBuff) наследуются от IBuff, и TempBuff содержит ссылку на CoreBuff
@@gamedevlavka Ну там как сказать. Там лучше вовсе весь проект переделать, ибо я опять напоролся на проблему спагетти кода. Вот даже из-за этого решил попробовать использовать какой ни будь архитектурный паттерн в будущих проектах. Я правильно понял что в примере вы использовали MVC? И еще вопросик по системе баффов. Не будет проблем с временным бафом? Ведь там при удалении/добавлении текущие и новый баффы добавляются заново и следовательно снова запускается таймер и к эвенту окончания добавляется удаление временного баффа
Вот прям очень большой косяк есть в данной реализации, что на каждое изменение любого параметра -- придётся писать новый бафф. Плюс -- сами бафы будут заниматься валидацией. То есть, чтобы просто сделать одну и ту же логику изменения, и для хп, и для здоровья, например -- нужно реализовать два баффа и мне кажется это овер неудобным и странным. Наверняка сделано для новичков, но всё равно выскажу, как я бы сделал: сразу бы написал какой-нибудь ValueWithModifications (с названиями всегда туго, так что не важно + я немного опишу его загружено, но это не важно для понимания концепции), где внутри был бы реализован список модификаций (это например ограничения по Min/Max или как раз бафы + им сделать нужно приоритет по операциям, чтобы баундс всегда был в конце). Получится по итогу, что можно сделать и обычный бафф с изменениями, и сделать баундс изменения значения, и сделать временный бафф и т. д. И можно всё что угодно придумать и добавлять. Конечно в Юньке всё ещё нет IValue (или как его там? Ну который реализуют все значимые типы и это позволяет в дженерик классах производить примитивные операции над параметром), так что придётся писать там под int и float, но что есть).
Не совсем понял о чем ты, модно написать баф на каждую параметр статов вместе с валидацией и комбинировать как душе угодно. Параметры батареи задаются снаружи, то есть они конфигурируемые. Где тут на каждое изменение параметра ещё один баф?
@@gamedevlavka Сорри заранее, если моё сообщение показалось немного грубым или около того, хах). Просто я скорее говорил о тех реализациях, что есть в видео. Например DamageBuff -- там одновременно и валидация значения, так и одновременно и изменение значение на какое-то число. Но хотелось бы, например, сделать какую-то более глубокую систему, чтобы не писать под каждый стат -- новый бафф. К примеру если будет РПГ игра, где есть сила, ловкость, интеллект и т. д. (ну к примеру там 20 статов даже), то в таком ключе, который ты показал -- будет казаться, что надо делать один и тот же код под каждый такой стат, а это как-то... Ну не то же). И я просто предложил, что наверное стоит всё таки сам стат сделать хранилищем бафов/модификаций.
Возможно я не прав, потому что в видео предполагалась идея, что баффы будут иметь контекст именно через конкретные реализации, а я себе представляю баффы -- как контейнер модификаций. Например, как оружия в тех же РПГ -- это же по сути контейнер модификаций статов. Ты же не будешь под каждую оружку в игре -- делать наследника IWeapon какого-нибудь, например. Мы будем тогда не объектами, а классами орудовать). Конечно можно было для оружек сделать обёртку, со всеми 100 полями персонажи, где будут в большинстве нули или те же значения -- но это вообще не камельфо. Поэтому лучше будет, если ты сможешь выбирать что конкретно оружка изменяет и насколько. И мне кажется, что баффы и изменение статов -- это всё одна система).
@@Paulsams Да, этого можно добиться просто расширив предложенную в видео систему) Например: в ScriptableObject делаешь список бафов, какие угодно. Можно даже сделать генерируемые параметры в рамках каких-то ограничений. Пишется заранее класс декоратор (как в видео TemporaryBuff), который будет применять группу баффов и ему на вход список бафов кидаешь. Внутри он применяется каждый баф к статам. Вся прелесть в том, что получается невероятно гибкий конструктор, который легко конфигурировать. Такой скриптабл можно дать тому же оружию. Можно завернуть эту группу в TemporaryBuff и вуаля - все параметры что ты поменял, поменялись на время. Я понимаю, что в видео показан жёсткий пример, предполагалось, что остальное можно "додумать", но я могу и расширение снять рассказать)
@@gamedevlavka Ну это были названы преимущества именно декоратора). Я с этим паттерном как раз и не спорил). Просто меня беспокоило больше то, что по видео кажется, что на каждый стат, например: интеллект, броня, сила и т. д. -- надо будет делать отдельную реализацию интерфейса. Хотя я и понимаю, что ничто не запрещает сделать дженерик обобщённый бафф аля, который будет уметь от любого стата прибавлять n-ое число, например. Я опять же скорее просто говорил о том, что мне больше нравится подход, не когда статы валяются в контейнере/группе (как PlayerStats, например) и у баффов есть доступ ко всей группе статов, а когда сами статы являются контейнером баффов, которые влияют только на этот стат). Как-будто больше гибкости мне видется).
Здравствуйте. Хотелось бы спросить: не проще сделать коэффициент корректировки здоровья, брони, дамага? Таким образом не нужно будет менять основную переменную. И эффекты смогут легко смешиваться.
Так это тоже самое, коэффициент корректировки - это тоже стата, можно менять её. В целом в статах можно хоть что менять, как использовать- это другой вопрос. Есть разные механики и разные геймдизайнерские извращения. В формулах расчётов того же урона может такой ад твориться, даже не представляешь. Но ответ на вопрос таков: не не проще, но и не сложнее, т.к. это одно и то же)
кстати есть плагин Disable RUclips 60 FPS (Force 30 FPS) - а затем наслаждайтесь видео на RUclips без задержек, меньшим использованием процессора (в 2-4 раза), более длительным временем автономной работы и меньшим использованием полосы пропускания! :-)
Возможно, я тоже дурак, но мне кажется, что в коде есть ошибка, и я её поправил. В класс объекта игрока передаётся декоратор баффа (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-х раз в секунду, при том, они никак не должны взаимодействовать между собой - каждый таймер должен работать отдельно, и как только последний из них закончит свою работу - эффект должен сняться. Надеюсь, помог
всё же очень слабое развитие темы. Для системы бафов хочется иметь: проверку на уже наличие бафа этого типа, на наличие бафа генерик-типа, возможность стакать баффы или переписывать (заменять/обновлять) их новыми. Хочется возможность снятия бафа по типу, ведь не всегда известен конкретный референс на бафф. Собственно элементарная поддержка поведений бафов при apply/remove/ontick.
Подразумевается, что все перечисленное - уже понятно как делать, это лишь проверки, условия, применения - полная конкретика под конкретные нужды. Если очень надо, я могу дополнить, но не уверен, что это востребовано
без сомнений один из самых нормальных каналов по юнити!!! было бы очень круто увидеть видео с DI и Zenject как его частный случай, а то инфы очень мало (ну или я тупой)
Полностью солидарен!
Спасибо за видео!
Принцип добавления баффов через интерфейсы столь же просты, как и через компоненты в ECS - очень понравилось!
Только сегодня хотел сесть за написание системы баффов, и тут бац, видосик) Приятно
Месяц уже хотел реализовать, но не знал как создать ультимативную систему, откладывал - спасибо
По итогам видоса возникли несколько вопросов:
1) Меня всегда учили, что если объект обладает неким поведением, то используем интерфейс. Если же объект в сущности своей является тем, от чего наследуется - используем абстрактный класс. Так вот не лучше ли вместо интерфейса IBuff использовать некий абстрактный класс Buff и от него наследовать? Так же туда можно завернуть свойства типа lifeTime, Type (Buff, Debuff) и т.д.
2) На сколько удобно/затратно постоянно пересчитывать каждый раз ВСЕ баффы? Не лучше ли считать один, тот который добавляем?
3) Как правильно связать бафы с интерфейсом? В большинстве игр при появлении бафа мы получаем об этом визуальное уведомление (ну например иконка бафа над персонажем). Как это лучше реализовать?
P.s. я джун, на какое-то экспертное мнение не претендую
Отличные вопросы!
1. Сложный вопрос, потому что ответ зависит от требований. IBuff можно (и лучше) сделать генериком, чтобы совсем не зависеть от типа статов. Лайфтайм я бы вынес в родительский класс только если баффы живут между игровыми сессиями, иначе зачем там лайфтайм, ну и если необходимо свойство Buff/debuff - тоже в целом оправданно. Тут смотрите по требованиям, что лучше применить. Интерфейсы - не жестко педолируемая идея в этом видео)
2. Ни на сколько. Мы работаем с типом данных - значение, то есть мы даже не аллоцируем память (ну немножк через форыч, но там совсем крошки, см. далее). И второе, баф или дебаф - это не перманентновыполняемый код, он случается не каждый кадр (хотя даже в этом случае нагрузки бы не было). В общем за скорость работы не переживай.
3. Логика приложения делится на разные слои. И вот этот код, который я писал в CharacterView для тестов - он должен быть в бизнес логике (тут уже не смогу расписать подробно). Назовем его CharacterStatsService, и вот туда мы командуем CharacterStatsService.AddBuff(character, buff), а этот сервис имеет уведомлялку Action OnCharacterBuffed например, и сюда уже можно UI подвязывать. Это непростая тема, чтобы залететь с двух ног, но можно просто в Character, где у тебя AddBuff(IBuff) тоже ивент сделать и подписаться на персонажа.
Красавчик. так держать👍
Отличное видео! Хорошо и приятно объясняешь 👍
Решил зайти на ютубчик дабы поизучать нового, пока тасок нету, и в рекомендациях прилетело твое видео :)
Спасибо Андрей, то, что надо)
Спасибо за урок!
хороший урок, в райдере когда создаете в конструкторе параметр можно нажать альт энтер и он сам предложит закэшировать его в классе, сам создаст поле и сам присвоит
Отличный ролик, спасибо)
Вроде смотрится здраво, но сильно не хватает, чтобы структура CharacterStats была у персонажа генериком. Из этого следует вопрос:
Я бы при генерик реализации внутри баффов проверял *if (playerStats is CorrectPlayerStats)* и только при успешном касте продолжал работу. Есть ли более элегантная система или я +- правильно мыслю? Я неособо люблю касты, но иногда другие решения мне кажутся лишь более громоздкими.
Да, вопрос хороший, я в другом комменте уже ответил, что еще лучше сделать IBuff генериком, баффы все равно привязываются к конкретным статам, а вот интерфейс лучше сделать чем-то вроде IBuff where T : IStats, а конкретные баффы так: ImmortalityBuff : IBuff, ну и CharacterStats в свою очередь CharacterStats : IStats. Тогда везде можно использовать только IStats и только в реализациях манипулировать конкретикой, даже кастовать не надо
Отличное видео, спасибо! Хотелось бы узнать как реализовать правильно паузу, чтобы не использовать timescale, после которого и анимация перестает работать)
у Максима Крюкова на канале есть неплохой подход к решению этой проблемы
Актуальная тема :)
Спасибо за хорошее видео. Единственное не понял в конце почему это декоратор. Мне больше это напоминает прокси: TempBuff и CoreBuff(в нашем случае ImmortalityBuff) наследуются от IBuff, и TempBuff содержит ссылку на CoreBuff
Да прикольный способ сделать простую систему бафов для небольшой игры, но через декораторы будет конечно покруче, хотя не всегда и нужно
Где вы были несколько дней назад... Я уже свою систему модулей наделал
Рефакторинг ещё не поздно сделать) или уже поздно?
@@gamedevlavka Ну там как сказать. Там лучше вовсе весь проект переделать, ибо я опять напоролся на проблему спагетти кода. Вот даже из-за этого решил попробовать использовать какой ни будь архитектурный паттерн в будущих проектах. Я правильно понял что в примере вы использовали MVC? И еще вопросик по системе баффов. Не будет проблем с временным бафом? Ведь там при удалении/добавлении текущие и новый баффы добавляются заново и следовательно снова запускается таймер и к эвенту окончания добавляется удаление временного баффа
Лайк коммент не глядя
Вот прям очень большой косяк есть в данной реализации, что на каждое изменение любого параметра -- придётся писать новый бафф. Плюс -- сами бафы будут заниматься валидацией. То есть, чтобы просто сделать одну и ту же логику изменения, и для хп, и для здоровья, например -- нужно реализовать два баффа и мне кажется это овер неудобным и странным. Наверняка сделано для новичков, но всё равно выскажу, как я бы сделал: сразу бы написал какой-нибудь ValueWithModifications (с названиями всегда туго, так что не важно + я немного опишу его загружено, но это не важно для понимания концепции), где внутри был бы реализован список модификаций (это например ограничения по Min/Max или как раз бафы + им сделать нужно приоритет по операциям, чтобы баундс всегда был в конце). Получится по итогу, что можно сделать и обычный бафф с изменениями, и сделать баундс изменения значения, и сделать временный бафф и т. д. И можно всё что угодно придумать и добавлять. Конечно в Юньке всё ещё нет IValue (или как его там? Ну который реализуют все значимые типы и это позволяет в дженерик классах производить примитивные операции над параметром), так что придётся писать там под int и float, но что есть).
Не совсем понял о чем ты, модно написать баф на каждую параметр статов вместе с валидацией и комбинировать как душе угодно. Параметры батареи задаются снаружи, то есть они конфигурируемые. Где тут на каждое изменение параметра ещё один баф?
@@gamedevlavka Сорри заранее, если моё сообщение показалось немного грубым или около того, хах). Просто я скорее говорил о тех реализациях, что есть в видео. Например DamageBuff -- там одновременно и валидация значения, так и одновременно и изменение значение на какое-то число. Но хотелось бы, например, сделать какую-то более глубокую систему, чтобы не писать под каждый стат -- новый бафф. К примеру если будет РПГ игра, где есть сила, ловкость, интеллект и т. д. (ну к примеру там 20 статов даже), то в таком ключе, который ты показал -- будет казаться, что надо делать один и тот же код под каждый такой стат, а это как-то... Ну не то же). И я просто предложил, что наверное стоит всё таки сам стат сделать хранилищем бафов/модификаций.
Возможно я не прав, потому что в видео предполагалась идея, что баффы будут иметь контекст именно через конкретные реализации, а я себе представляю баффы -- как контейнер модификаций. Например, как оружия в тех же РПГ -- это же по сути контейнер модификаций статов. Ты же не будешь под каждую оружку в игре -- делать наследника IWeapon какого-нибудь, например. Мы будем тогда не объектами, а классами орудовать). Конечно можно было для оружек сделать обёртку, со всеми 100 полями персонажи, где будут в большинстве нули или те же значения -- но это вообще не камельфо. Поэтому лучше будет, если ты сможешь выбирать что конкретно оружка изменяет и насколько. И мне кажется, что баффы и изменение статов -- это всё одна система).
@@Paulsams
Да, этого можно добиться просто расширив предложенную в видео систему)
Например: в ScriptableObject делаешь список бафов, какие угодно. Можно даже сделать генерируемые параметры в рамках каких-то ограничений.
Пишется заранее класс декоратор (как в видео TemporaryBuff), который будет применять группу баффов и ему на вход список бафов кидаешь. Внутри он применяется каждый баф к статам. Вся прелесть в том, что получается невероятно гибкий конструктор, который легко конфигурировать. Такой скриптабл можно дать тому же оружию.
Можно завернуть эту группу в TemporaryBuff и вуаля - все параметры что ты поменял, поменялись на время.
Я понимаю, что в видео показан жёсткий пример, предполагалось, что остальное можно "додумать", но я могу и расширение снять рассказать)
@@gamedevlavka Ну это были названы преимущества именно декоратора). Я с этим паттерном как раз и не спорил). Просто меня беспокоило больше то, что по видео кажется, что на каждый стат, например: интеллект, броня, сила и т. д. -- надо будет делать отдельную реализацию интерфейса. Хотя я и понимаю, что ничто не запрещает сделать дженерик обобщённый бафф аля, который будет уметь от любого стата прибавлять n-ое число, например.
Я опять же скорее просто говорил о том, что мне больше нравится подход, не когда статы валяются в контейнере/группе (как PlayerStats, например) и у баффов есть доступ ко всей группе статов, а когда сами статы являются контейнером баффов, которые влияют только на этот стат). Как-будто больше гибкости мне видется).
Здравствуйте. Хотелось бы спросить: не проще сделать коэффициент корректировки здоровья, брони, дамага? Таким образом не нужно будет менять основную переменную. И эффекты смогут легко смешиваться.
Так это тоже самое, коэффициент корректировки - это тоже стата, можно менять её. В целом в статах можно хоть что менять, как использовать- это другой вопрос. Есть разные механики и разные геймдизайнерские извращения. В формулах расчётов того же урона может такой ад твориться, даже не представляешь. Но ответ на вопрос таков: не не проще, но и не сложнее, т.к. это одно и то же)
@@gamedevlavka Хорошо. Спасибо за видос!
кстати есть плагин Disable RUclips 60 FPS (Force 30 FPS) - а затем наслаждайтесь видео на RUclips без задержек, меньшим использованием процессора (в 2-4 раза), более длительным временем автономной работы и меньшим использованием полосы пропускания! :-)
Дяденька я тебя нашёл, твой канал записан у меня в блокноте как посмотреть весь полностью, канал просто кладесь нужной мне информации
При удалении единственного дебаффа на скорость, базовой она не становится и висит, как будто этот дебаф висит до сих пор
Возможно, я тоже дурак, но мне кажется, что в коде есть ошибка, и я её поправил. В класс объекта игрока передаётся декоратор баффа (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-х раз в секунду, при том, они никак не должны взаимодействовать между собой - каждый таймер должен работать отдельно, и как только последний из них закончит свою работу - эффект должен сняться. Надеюсь, помог
Все таки придётся думать как написать правильный таймер😢
😆
+
всё же очень слабое развитие темы. Для системы бафов хочется иметь: проверку на уже наличие бафа этого типа, на наличие бафа генерик-типа, возможность стакать баффы или переписывать (заменять/обновлять) их новыми. Хочется возможность снятия бафа по типу, ведь не всегда известен конкретный референс на бафф. Собственно элементарная поддержка поведений бафов при apply/remove/ontick.
Подразумевается, что все перечисленное - уже понятно как делать, это лишь проверки, условия, применения - полная конкретика под конкретные нужды. Если очень надо, я могу дополнить, но не уверен, что это востребовано