#вопрос_ответ
Приветствую👋
Сегодня разберу один из недавно заданных вопросов. Ответ получился объемным, на публикацию 📚
Вопрос от @PavelAltynnikov
В игре есть инвентарь, в котором лежат такие типы предметов:
- потребляемые, используемые и неиспользуемые.
И все эти типы предметов могут воздействовать на характеристики игрока.
Например: потребляемый предмет "эликсир маны" при употреблении увеличивает текущее количество маны,
рукавицы из шкуры дракона при одевании увеличивает ловкость и уменьшает стойкость к яду,
не используемый тотем в инвентаре увеличивает мудрость.
Как бы вы решили проблему связи предметов инвентаря с характеристиками игрока?
Приступим
В ответе обращаюсь к слоям абстракции, также использую термин “провода” и реактивные объекты. Рассказывал в “Слои абстракции” и “Связи в проекте”.
Также к “проводам” отношу все динамические поля, которые меняются в процессе игры. Ответ упрощен.
Считаю, что вы знакомы (хотя бы обзорно) с теми архитектурными подходами, которые я рассматривал в цикле статей.
В проекте необходимо сделать деление на слои абстракции.
Предметы с исходными характеристиками - слой контента.
Условно иерархия контентных классов ⬇️ :
- Content, внутри класса есть поле id
- Item - наследник Content
- ConsumableItem - наследник Item
- UsableItem - наследник Item
- …
‼️Для упрощения считаю, что классы выше уже созданы
- GameEffect - наследник Content
- GameEffectInstant - наследник GameEffect
- GameEffectMana - наследник GameEffectInstant. Содержит поле mana, значение увеличения/уменьшения маны
- GameEffectPersistent - наследник GameEffect
- GameEffectDexterityModifier - наследник GameEffectPersistent. Содержит поле dexterityIncrement, значение увеличения/уменьшения ловкости
- GameEffectPoisonResistanceModifier - наследник GameEffectPersistent. Содержит поле poisonResistanceIncrement, значение увеличения/уменьшения устойчивости к яду
- GameEffectWisdomModifier - наследник GameEffectPersistent. Содержит поле wisdomIncrement, значение увеличения/уменьшения мудрости
- GameEffectApplicator - наследник Content. Содержит поля:
- trigger: onUseInventoryItem (при использовании), onWearInventoryItem (при одевании), onItemAddToInventory (при добавлении в инвентарь)
- item (инстанс контентного класса Item)
- gameEffect (инстанс контентного класса GameEffect, какой эффект применить).
‼️Инстанс класса является связующим звеном между событием в игре и эффектом, который нужно применить.
"эликсир маны" - инстанс UsableItem.
"рукавицы из шкуры дракона" - инстанс ConsumableItem.
"тотем" - инстанс Item.
Перейдем к иерархии классов состояний, слою состояний/State
- State содержит поля: id, contentId
- ItemState - наследник State
- ConsumableItemState - наследник ItemState
- UsableItemState - наследник ItemState
- …
‼️ Для упрощения считаю, что классы выше уже созданы
- GameEffectState - наследник ItemState
- GameEffectPersistentState - наследник GameEffectState
- GameEffectDexterityModifierState - наследник GameEffectPersistentState
- GameEffectPoisonResistanceModifierState - наследник GameEffectPersistentState
- GameEffectWisdomModifierState - наследник GameEffectPersistentState.
Профиль - состояние игрока, набор динамических параметров:
- mana - текущее значение маны
- dexterity - текущее значение ловкости
- poisonResistence - текущее значение устойчивости к яду
- wisdom - текущее значение мудрости
- inventory - инвентарь
- gameEffectStates - состояния игровых эффектов.
В профиле могут присутствовать и другие состояния.
Инвентарь - это часть состояния игрока, которое находится в профиле.
Продолжение в комментариях ⬇️
Приветствую👋
Сегодня разберу один из недавно заданных вопросов. Ответ получился объемным, на публикацию 📚
Вопрос от @PavelAltynnikov
В игре есть инвентарь, в котором лежат такие типы предметов:
- потребляемые, используемые и неиспользуемые.
И все эти типы предметов могут воздействовать на характеристики игрока.
Например: потребляемый предмет "эликсир маны" при употреблении увеличивает текущее количество маны,
рукавицы из шкуры дракона при одевании увеличивает ловкость и уменьшает стойкость к яду,
не используемый тотем в инвентаре увеличивает мудрость.
Как бы вы решили проблему связи предметов инвентаря с характеристиками игрока?
Приступим
В ответе обращаюсь к слоям абстракции, также использую термин “провода” и реактивные объекты. Рассказывал в “Слои абстракции” и “Связи в проекте”.
Также к “проводам” отношу все динамические поля, которые меняются в процессе игры. Ответ упрощен.
Считаю, что вы знакомы (хотя бы обзорно) с теми архитектурными подходами, которые я рассматривал в цикле статей.
В проекте необходимо сделать деление на слои абстракции.
Предметы с исходными характеристиками - слой контента.
Условно иерархия контентных классов ⬇️ :
- Content, внутри класса есть поле id
- Item - наследник Content
- ConsumableItem - наследник Item
- UsableItem - наследник Item
- …
‼️Для упрощения считаю, что классы выше уже созданы
- GameEffect - наследник Content
- GameEffectInstant - наследник GameEffect
- GameEffectMana - наследник GameEffectInstant. Содержит поле mana, значение увеличения/уменьшения маны
- GameEffectPersistent - наследник GameEffect
- GameEffectDexterityModifier - наследник GameEffectPersistent. Содержит поле dexterityIncrement, значение увеличения/уменьшения ловкости
- GameEffectPoisonResistanceModifier - наследник GameEffectPersistent. Содержит поле poisonResistanceIncrement, значение увеличения/уменьшения устойчивости к яду
- GameEffectWisdomModifier - наследник GameEffectPersistent. Содержит поле wisdomIncrement, значение увеличения/уменьшения мудрости
- GameEffectApplicator - наследник Content. Содержит поля:
- trigger: onUseInventoryItem (при использовании), onWearInventoryItem (при одевании), onItemAddToInventory (при добавлении в инвентарь)
- item (инстанс контентного класса Item)
- gameEffect (инстанс контентного класса GameEffect, какой эффект применить).
‼️Инстанс класса является связующим звеном между событием в игре и эффектом, который нужно применить.
"эликсир маны" - инстанс UsableItem.
"рукавицы из шкуры дракона" - инстанс ConsumableItem.
"тотем" - инстанс Item.
Перейдем к иерархии классов состояний, слою состояний/State
- State содержит поля: id, contentId
- ItemState - наследник State
- ConsumableItemState - наследник ItemState
- UsableItemState - наследник ItemState
- …
‼️ Для упрощения считаю, что классы выше уже созданы
- GameEffectState - наследник ItemState
- GameEffectPersistentState - наследник GameEffectState
- GameEffectDexterityModifierState - наследник GameEffectPersistentState
- GameEffectPoisonResistanceModifierState - наследник GameEffectPersistentState
- GameEffectWisdomModifierState - наследник GameEffectPersistentState.
Профиль - состояние игрока, набор динамических параметров:
- mana - текущее значение маны
- dexterity - текущее значение ловкости
- poisonResistence - текущее значение устойчивости к яду
- wisdom - текущее значение мудрости
- inventory - инвентарь
- gameEffectStates - состояния игровых эффектов.
В профиле могут присутствовать и другие состояния.
Инвентарь - это часть состояния игрока, которое находится в профиле.
Продолжение в комментариях ⬇️
#вопрос_ответ
Приветствую 👋
Из серии ваших вопросов, хочу выделить еще один, с которым часто сталкиваются разработчики. Поэтому выношу его из комментариев, чтобы он не остался без внимания!
Вопрос звучит так 👇
Хотелось бы узнать,кто как бы решил такую ситуацию:
Есть персонаж, на нем около 30-40 коллайдеров (руки,плечи,ноги и т.д.)
и нужно ловить на нём коллизии и вызывать класс, который лежит на самом верхнем слое персонажа. Кто как решал бы данную проблему?
а)Использовали б поиск скрипта с поиском по родителям?(если часто вызываться будет и много вложений, проблемы с производительностью возможны).
б)Использовать какие-нибудь collision handlers,которые будут вызывать события,а в верхнем персонажи будет какой-то скрипт,который будет на них подписываться? (не много ли подписок будет?)
Ответ 👇
Приведу пример нанесения урона персонажу, который может содержать несколько зон получения урона.
Я бы делал так:
- в ветке composition tree, которая относится к уровню (пусть это будет LevelEntity) создаем рычаги:
- onDealDamage (содержит информацию о коллайдере и характеристиках атаки)
- receiveDamage (содержит информацию о том куда именно было попадание State того, кому наносим урон, характеристики атаки и зоне попадания)
- в Pm, которая отвечает за физику нанесения урона, например EnemyAttackPm мы получаем набор коллайдеров, с которыми было пересечение, вызываем onDealDamage (столько раз, сколько было задето коллайдеров), который прокидывается вниз по иерархии от LevelEntity
- создаем View DamageReceiverView (содержит поле collider и какой-то идентификатор зоны: enum), которая принимает в контексте State персонажа, onDealDamage и receiveDamage.
В методе установки контекста подписываемся на onDealDamage с фильтрацией коллайдера.
В методе подписки вызываем receiveDamage с нужными параметрами: State кого атакуют, характеристиках атаки и зоне.
- в иерархии персонажа кидаем в нужные GameObject DamageReceiverView (таких мест будет столько, сколько зон попадания у вас есть), ставим в них нужные коллайдеры. Ставим идентификатор.
Во View персонажа (содержит поля с DamageReceiverView, которые именуются по зонам попадания) перетаскиваем нужные DamageReceiverView.
В контексте View персонажа заполняем контексты DamageReceiverView.
- также в иерархии composition tree должен быть DamageSystemPm, в который приходит рычаг receiveDamage, с информацией о State кого атакуют и информацией об атаке и зоне. Эта Pm'ка принимает решения и отвечает за изменения State того, кого атакуют.
По поводу подписок, если речь про UniRx, то с ними всё будет хорошо даже, если их тысячи в проекте. Главное не забывать делать Dispose подписке, когда она больше не нужна.
❗Нужно помнить, что явное всегда лучше неявного.
Когда вы используете GetComponentInParent (или что-то аналогичное), то это означает что у вас в Design time еще не выстроена связь и непонятно, выстроится ли в Runtime (разработчик надеятся, что скрипт будет найден, но по факту может и не быть найден).
❗Если вы делаете прототип и быстро нужно получить MVP, то нужно выбирать самый быстрый вариант.
Хорошего дня!
Приветствую 👋
Из серии ваших вопросов, хочу выделить еще один, с которым часто сталкиваются разработчики. Поэтому выношу его из комментариев, чтобы он не остался без внимания!
Вопрос звучит так 👇
Хотелось бы узнать,кто как бы решил такую ситуацию:
Есть персонаж, на нем около 30-40 коллайдеров (руки,плечи,ноги и т.д.)
и нужно ловить на нём коллизии и вызывать класс, который лежит на самом верхнем слое персонажа. Кто как решал бы данную проблему?
а)Использовали б поиск скрипта с поиском по родителям?(если часто вызываться будет и много вложений, проблемы с производительностью возможны).
б)Использовать какие-нибудь collision handlers,которые будут вызывать события,а в верхнем персонажи будет какой-то скрипт,который будет на них подписываться? (не много ли подписок будет?)
Ответ 👇
Приведу пример нанесения урона персонажу, который может содержать несколько зон получения урона.
Я бы делал так:
- в ветке composition tree, которая относится к уровню (пусть это будет LevelEntity) создаем рычаги:
- onDealDamage (содержит информацию о коллайдере и характеристиках атаки)
- receiveDamage (содержит информацию о том куда именно было попадание State того, кому наносим урон, характеристики атаки и зоне попадания)
- в Pm, которая отвечает за физику нанесения урона, например EnemyAttackPm мы получаем набор коллайдеров, с которыми было пересечение, вызываем onDealDamage (столько раз, сколько было задето коллайдеров), который прокидывается вниз по иерархии от LevelEntity
- создаем View DamageReceiverView (содержит поле collider и какой-то идентификатор зоны: enum), которая принимает в контексте State персонажа, onDealDamage и receiveDamage.
В методе установки контекста подписываемся на onDealDamage с фильтрацией коллайдера.
В методе подписки вызываем receiveDamage с нужными параметрами: State кого атакуют, характеристиках атаки и зоне.
- в иерархии персонажа кидаем в нужные GameObject DamageReceiverView (таких мест будет столько, сколько зон попадания у вас есть), ставим в них нужные коллайдеры. Ставим идентификатор.
Во View персонажа (содержит поля с DamageReceiverView, которые именуются по зонам попадания) перетаскиваем нужные DamageReceiverView.
В контексте View персонажа заполняем контексты DamageReceiverView.
- также в иерархии composition tree должен быть DamageSystemPm, в который приходит рычаг receiveDamage, с информацией о State кого атакуют и информацией об атаке и зоне. Эта Pm'ка принимает решения и отвечает за изменения State того, кого атакуют.
По поводу подписок, если речь про UniRx, то с ними всё будет хорошо даже, если их тысячи в проекте. Главное не забывать делать Dispose подписке, когда она больше не нужна.
❗Нужно помнить, что явное всегда лучше неявного.
Когда вы используете GetComponentInParent (или что-то аналогичное), то это означает что у вас в Design time еще не выстроена связь и непонятно, выстроится ли в Runtime (разработчик надеятся, что скрипт будет найден, но по факту может и не быть найден).
❗Если вы делаете прототип и быстро нужно получить MVP, то нужно выбирать самый быстрый вариант.
Хорошего дня!
Приветствую 👋 Мне понравилось, как прошел цикл вопрос-ответ 🤝. Если данный формат полезен, предлагаю продолжить. Оставляйте ваши вопросы и предложения по темам в комментариях. Начнем?
Anonymous Poll
97%
Да
3%
Нет
#вопрос_ответ
Приветствую 👋
Сегодня в рамках рубрики #вопрос_ответ разберем вопрос про систему апгрейда игральных костей. Вопрос ниже, синтаксис и пунктуация автора сохранены.
Суть игры:
Игрок бросает от 1 до "условно бесконечного числа" игральных костей, получая очки от выпавших значений. Дефолтно 1 кубик.
За накопленные очки можно апгрейдить грани кубика (каждую отдельно) либо покупать дополнительные кубики.
При апгрейде выбирается грань кубика и увеличивается количество точек на ней (до 10 - выражены точками, дальше - числами).
Вопрос: как лучше организовать систему апгрейда кубиков, чтобы учесть что может качаться как сами грани кубика, так и покупаются новые кубики, которые прокачиваются отдельно. И плюс к этому в будущем добавится система бафов.
❗В ответе подсвечены важные с точки зрения реализации моменты, на которых я считаю нужным сделать акцент. Опущены моменты взаимодействия с UI, они остаются на усмотрение исполнителя.
Также ответ не покрывает систему бафов, но предлагаемый подход позволит легко ее интегрировать.
Поехали.
Считаю, что любой куб можно апгрейдить.
Разделим проект на слои: Content, State, Entity, Pm, View.
Иерархия контента:
- Content, содержит поле id.
- Cube, наследник Content. Поля: side1, side2, side3, side4, side5, side6.
- CubeLevel1, наследник Cube. Cодержит начальные характеристики куба уровня 1.
- ...
- CubeLevel[n], наследник Cube. Cодержит начальные характеристики куба уровня [n].
- CubeUpgrade, наследник Content.
- CubeUpgradeLevel2, наследник CubeUpgrade. Поля: from (куб, с которого апгрейд им), to (куб, на который апгрейдим).
- CubeUpgradeLevel[n], -//-.
- CubeSide, наследник Content
- CubeSideLevel1, наследник CubeSide, условно - грань уровня 1, содержит начальные характеристики грани уровня 1.
- ...
- CubeSideLevel[n], ..., наследник CubeSide, грань уровня [n], содержит начальные характеристики грани уровня [n].
- CubeSideUpgrade, наследник Content.
- CubeSideUpgradeLevel2, наследник CubeSideUpgrade. Поля: from (грань, с какой апгрейдим), to (грань, на которую апгрейдим).
- ...
- CubeSideUpgradeLevel[n], -//-.
Иерархия стейтов:
- State. Поля: id, contentId.
- CubeState, наследник State. Содержит изменяемые характеристики куба: уровень апгрейда, rotate, position, набор стейтов граней, кулдаун на следующий апгрейд, наложенные бафы, и т.д.
- CubeSideState, наследник State. Содержит изменяемые характеристики грани: индекс грани, уровень апгрейда, кол-во точек, кулдаун на следующий апгрейд, наложенные бафы, и т.д.
PlayerState - игровой профиль. Считаю, что уже существует. Его поля содержат:
- ...
- cubeStates, коллекция со стейтами кубов.
Продолжение в комментариях ⬇️
Приветствую 👋
Сегодня в рамках рубрики #вопрос_ответ разберем вопрос про систему апгрейда игральных костей. Вопрос ниже, синтаксис и пунктуация автора сохранены.
Суть игры:
Игрок бросает от 1 до "условно бесконечного числа" игральных костей, получая очки от выпавших значений. Дефолтно 1 кубик.
За накопленные очки можно апгрейдить грани кубика (каждую отдельно) либо покупать дополнительные кубики.
При апгрейде выбирается грань кубика и увеличивается количество точек на ней (до 10 - выражены точками, дальше - числами).
Вопрос: как лучше организовать систему апгрейда кубиков, чтобы учесть что может качаться как сами грани кубика, так и покупаются новые кубики, которые прокачиваются отдельно. И плюс к этому в будущем добавится система бафов.
❗В ответе подсвечены важные с точки зрения реализации моменты, на которых я считаю нужным сделать акцент. Опущены моменты взаимодействия с UI, они остаются на усмотрение исполнителя.
Также ответ не покрывает систему бафов, но предлагаемый подход позволит легко ее интегрировать.
Поехали.
Считаю, что любой куб можно апгрейдить.
Разделим проект на слои: Content, State, Entity, Pm, View.
Иерархия контента:
- Content, содержит поле id.
- Cube, наследник Content. Поля: side1, side2, side3, side4, side5, side6.
- CubeLevel1, наследник Cube. Cодержит начальные характеристики куба уровня 1.
- ...
- CubeLevel[n], наследник Cube. Cодержит начальные характеристики куба уровня [n].
- CubeUpgrade, наследник Content.
- CubeUpgradeLevel2, наследник CubeUpgrade. Поля: from (куб, с которого апгрейд им), to (куб, на который апгрейдим).
- CubeUpgradeLevel[n], -//-.
- CubeSide, наследник Content
- CubeSideLevel1, наследник CubeSide, условно - грань уровня 1, содержит начальные характеристики грани уровня 1.
- ...
- CubeSideLevel[n], ..., наследник CubeSide, грань уровня [n], содержит начальные характеристики грани уровня [n].
- CubeSideUpgrade, наследник Content.
- CubeSideUpgradeLevel2, наследник CubeSideUpgrade. Поля: from (грань, с какой апгрейдим), to (грань, на которую апгрейдим).
- ...
- CubeSideUpgradeLevel[n], -//-.
Иерархия стейтов:
- State. Поля: id, contentId.
- CubeState, наследник State. Содержит изменяемые характеристики куба: уровень апгрейда, rotate, position, набор стейтов граней, кулдаун на следующий апгрейд, наложенные бафы, и т.д.
- CubeSideState, наследник State. Содержит изменяемые характеристики грани: индекс грани, уровень апгрейда, кол-во точек, кулдаун на следующий апгрейд, наложенные бафы, и т.д.
PlayerState - игровой профиль. Считаю, что уже существует. Его поля содержат:
- ...
- cubeStates, коллекция со стейтами кубов.
Продолжение в комментариях ⬇️
#код #unirx
Друзья, приветствую 👋
Иногда в реактивном программировании необходимо не просто выполнить какую-либо функцию, а дождаться её выполнения и получить результат.
В текущем году данный вопрос был наиболее частым среди моих коллег.
Приведу пример:
В UniRx, к сожалению, такое поведение отсутствует.
❗Честно скажу, что в своих проектах, я обходил стороной такой подход, и до сих пор считаю, что если такое назревает, то с архитектурой проекта МОЖЕТ быть что-то не так.
Тем не менее, если вы всё спроектировали хорошо, но такая потребность присутствует, то это как раз тот вариант, когда вам может это пригодиться.
Тк запрос был частым, я решил немного доработать код ReactiveCommand из UniRx и выложить сюда.
Чтобы не ломать codestyle, который используется в UniRx, писал в их стиле.
Как пользоваться и осноные нюансы:
❗Если вызвать Dispose для команды, то при попытке сделать Execute будет выбрасываться исключение: ObjectDisposedException: Cannot access a disposed object.
❗Если вызвать Dispose для команды, то при попытке сделать Subscribe будет выбрасываться исключение: ObjectDisposedException: Cannot access a disposed object.
❗Если вызвать Dispose для подписки, то обработчик больше не будет срабатывать
❗Если несколько раз подписаться на команду, то будет срабатывать последняя подписка, поэтому лучше избегать ситуацию с несколькими подписками.
Файл можно скачать ниже👇
Всем хороших выходных!
Друзья, приветствую 👋
Иногда в реактивном программировании необходимо не просто выполнить какую-либо функцию, а дождаться её выполнения и получить результат.
В текущем году данный вопрос был наиболее частым среди моих коллег.
Приведу пример:
ReactiveCommand<string, string> testStringResultCommand = new ReactiveCommand<string, string>();
testStringResultCommand.Subscribe(inputValue =>
{
return $"{inputValue}def";
});
string resultNormal = testStringResultCommand.Execute("abc");
Debug.Log(resultNormal); <- abcdef
testStringResultCommand.Dispose();
В UniRx, к сожалению, такое поведение отсутствует.
❗Честно скажу, что в своих проектах, я обходил стороной такой подход, и до сих пор считаю, что если такое назревает, то с архитектурой проекта МОЖЕТ быть что-то не так.
Тем не менее, если вы всё спроектировали хорошо, но такая потребность присутствует, то это как раз тот вариант, когда вам может это пригодиться.
Тк запрос был частым, я решил немного доработать код ReactiveCommand из UniRx и выложить сюда.
Чтобы не ломать codestyle, который используется в UniRx, писал в их стиле.
Как пользоваться и осноные нюансы:
ReactiveCommand<string, string> testStringResultCommand = new ReactiveCommand<string, string>();
testStringResultCommand.Subscribe(inputValue =>
{
return $"{inputValue}def";
});
string resultNormal = testStringResultCommand.Execute("abc");
Debug.Log(resultNormal); <- abcdef
testStringResultCommand.Dispose();
string resultDisposed = testStringResultCommand.Execute("abc"); <- ObjectDisposedException: Cannot access a disposed object.
❗Если вызвать Dispose для команды, то при попытке сделать Execute будет выбрасываться исключение: ObjectDisposedException: Cannot access a disposed object.
❗Если вызвать Dispose для команды, то при попытке сделать Subscribe будет выбрасываться исключение: ObjectDisposedException: Cannot access a disposed object.
❗Если вызвать Dispose для подписки, то обработчик больше не будет срабатывать
❗Если несколько раз подписаться на команду, то будет срабатывать последняя подписка, поэтому лучше избегать ситуацию с несколькими подписками.
Файл можно скачать ниже👇
Всем хороших выходных!
Приветствую 👋
Хочу поделиться ближайшими планами.
По мотивам вопроса с кубиком я решил написать упрощенный код и сделать его разбор.
Далее при наличии достаточного количества желающих можем запланировать и провести видеострим.
Для понимания запроса просьба поставить реакцию всем, кто хотел бы присутствовать.
Хорошего вечера.
Хочу поделиться ближайшими планами.
По мотивам вопроса с кубиком я решил написать упрощенный код и сделать его разбор.
Далее при наличии достаточного количества желающих можем запланировать и провести видеострим.
Для понимания запроса просьба поставить реакцию всем, кто хотел бы присутствовать.
Хорошего вечера.
Приветствую, друзья 👋
Код для игрового кубика написан 💪
Разбор в процессе!
Позже будет полезный пост по Rider, как настроить автоматическое удаление ненужных "using".
Код для игрового кубика написан 💪
Разбор в процессе!
Позже будет полезный пост по Rider, как настроить автоматическое удаление ненужных "using".
Media is too big
VIEW IN TELEGRAM
#разбор_кода #unity #архитектура
Приветствую 👋
Я завершил разбор кода для игры с кубиками 🥳.
Видео доступно по ссылке ➡️
https://www.youtube.com/watch?v=xikR6XkeuKE
Приятного просмотра 😉
Приветствую 👋
Я завершил разбор кода для игры с кубиками 🥳.
Видео доступно по ссылке ➡️
https://www.youtube.com/watch?v=xikR6XkeuKE
Приятного просмотра 😉
YouTube
Геймдев. Разбор архитектурного подхода на примере игры для канала https://t.me/gamedev_unity3d
Разбор подхода к проектированию архитектуры игрового проекта в рамках образовательного канала по разработке игр на Unity 3D.
Разбираем:
- точка входа в код
- слои абстракции
- префабы и их организация
- жизненный цикл объектов.
Больше полезной информации…
Разбираем:
- точка входа в код
- слои абстракции
- префабы и их организация
- жизненный цикл объектов.
Больше полезной информации…
Приветствую, друзья👋 Как вы? Удалось посмотреть разбор и подготовить список вопросов?
Отдайте свой голос, какой из вариантов по стриму для вас более привлекателен 👇
Отдайте свой голос, какой из вариантов по стриму для вас более привлекателен 👇
Anonymous Poll
19%
Будни после 19:00 Мск
22%
Выходной
22%
НГ каникулы 🎄🥂🍾
36%
Любой из вариантов
Друзья, с наступающим Новым Годом 🥂🍾
Пусть он принесет вам рост и новые возможности 🙌
С 2024 годом 🎄
Пусть он принесет вам рост и новые возможности 🙌
С 2024 годом 🎄
Друзья, приветствую 👋
К сожалению, в связи с высокой загруженностью материал на канале появляется не с той частотой, как мне хотелось 😔
Из приятного: на текущий момент готовлю для вас статью, посвященную различию между реактивными объектами и делегатами ✍️.
Обещанный стрим постараюсь поставить на 17 - 18 февраля.
Хорошего дня!
К сожалению, в связи с высокой загруженностью материал на канале появляется не с той частотой, как мне хотелось 😔
Из приятного: на текущий момент готовлю для вас статью, посвященную различию между реактивными объектами и делегатами ✍️.
Обещанный стрим постараюсь поставить на 17 - 18 февраля.
Хорошего дня!
Друзья, приветствую 👋 Я часто слышал аргумент: "Зачем реактивщина, ведь тоже самое можно сделать на делегатах". Пока я готовлю статью на эту тему, предлагаю рассмотреть пример (код в комментарии) и выбрать ответ.
Final Results
71%
Все ок, сработают две подписки, т.к. делегат - это ref тип
21%
Не ок, делегат - это value тип, вторая подписка не сработает
8%
Свой вариант ответа
Приветствую 👋
Стрим планируем на это воскресенье (18.02) 16:00 Мск
Буду раз всех вас видеть
Стрим планируем на это воскресенье (18.02) 16:00 Мск
Буду раз всех вас видеть
Всем привет, напоминаю, что через 45 мин начинаем стрим, стрим будет проходить в этом канале.
#архитектура #делегаты #event #action #Unity3D
Реактивное программирование vs делегаты в C#, Unity 3D
Приветствую 👋
Сегодня говорим про реактивное программирование vs делегаты (delegate) в C#, Unity 3D.
Для начала поясню, почему в статье я рассматриваю именно delegate, а не event или Action.
В C# Action, event и delegate являются ключевыми элементами, используемыми для работы с методами как с объектами. Это позволяет реализовывать такие концепции, как обратные вызовы (callbacks) и событийно-ориентированное программирование.
Delegate является основой для Action и event, где
- Action предоставляет удобный способ использования делегатов без возвращаемого значения
- event использует делегаты, обеспечивая контролируемый способ подписки на уведомления и обработки событий.
Поэтому в статье я буду рассматривать именно delegate.
Но для начала приведу основные отличия delegate, Action, event:
- Delegate - это тип, который представляет ссылки на методы с определённой сигнатурой и возвращаемым типом.
Это означает, что мы можем использовать переменные delegate для хранения ссылок на методы.
Delegate обеспечивает возможность передачи методов как аргументов методам или в качестве типов возвращаемых значений.
Delegate поддерживает многоадресные вызовы, позволяя вызывать несколько методов подписанных на делегат (multicast делегаты).
- Action - это обобщённый делегат, который не возвращает значение (возвращаемый тип void) и может принимать от 0 до 16 параметров.
Action является удобным способом использования делегатов, не требуя объявлять новый тип делегата для каждой сигнатуры метода.
По сути, Action предназначен для ситуаций, когда нужно передать методы, не возвращающие значение.
- Event - это способ, с помощью которого класс или объект может предоставлять уведомления.
Итак event (события):
-используют делегаты для обработки уведомлений.
- реализует паттерн Observer, где издатель уведомляет подписчиков о том, что произошло определённое событие.
- ограничивают способность внешнего кода вызывать делегат. Внешний код может подписаться на событие или отписаться от него, но не может напрямую вызвать делегат события.
- добавляют уровень инкапсуляции к делегатам, обеспечивая более безопасную и управляемую модель подписки на уведомления.
❗Реактивное программирование и использование делегатов в C# представляют собой два различных, но похожих подхода к обработке и передаче данных.
Оба механизма имеют свои преимущества и недостатки, и выбор между ними зависит от конкретных требований к проекту.
продолжение 🔽
Реактивное программирование vs делегаты в C#, Unity 3D
Приветствую 👋
Сегодня говорим про реактивное программирование vs делегаты (delegate) в C#, Unity 3D.
Для начала поясню, почему в статье я рассматриваю именно delegate, а не event или Action.
В C# Action, event и delegate являются ключевыми элементами, используемыми для работы с методами как с объектами. Это позволяет реализовывать такие концепции, как обратные вызовы (callbacks) и событийно-ориентированное программирование.
Delegate является основой для Action и event, где
- Action предоставляет удобный способ использования делегатов без возвращаемого значения
- event использует делегаты, обеспечивая контролируемый способ подписки на уведомления и обработки событий.
Поэтому в статье я буду рассматривать именно delegate.
Но для начала приведу основные отличия delegate, Action, event:
- Delegate - это тип, который представляет ссылки на методы с определённой сигнатурой и возвращаемым типом.
Это означает, что мы можем использовать переменные delegate для хранения ссылок на методы.
Delegate обеспечивает возможность передачи методов как аргументов методам или в качестве типов возвращаемых значений.
Delegate поддерживает многоадресные вызовы, позволяя вызывать несколько методов подписанных на делегат (multicast делегаты).
- Action - это обобщённый делегат, который не возвращает значение (возвращаемый тип void) и может принимать от 0 до 16 параметров.
Action является удобным способом использования делегатов, не требуя объявлять новый тип делегата для каждой сигнатуры метода.
По сути, Action предназначен для ситуаций, когда нужно передать методы, не возвращающие значение.
- Event - это способ, с помощью которого класс или объект может предоставлять уведомления.
Итак event (события):
-используют делегаты для обработки уведомлений.
- реализует паттерн Observer, где издатель уведомляет подписчиков о том, что произошло определённое событие.
- ограничивают способность внешнего кода вызывать делегат. Внешний код может подписаться на событие или отписаться от него, но не может напрямую вызвать делегат события.
- добавляют уровень инкапсуляции к делегатам, обеспечивая более безопасную и управляемую модель подписки на уведомления.
❗Реактивное программирование и использование делегатов в C# представляют собой два различных, но похожих подхода к обработке и передаче данных.
Оба механизма имеют свои преимущества и недостатки, и выбор между ними зависит от конкретных требований к проекту.
продолжение 🔽
#архитектура #делегаты #event #action #Unity3D
Реактивное программирование vs делегаты в C#, Unity 3D продолжение 🔽
Рассмотрим ключевые моменты каждого подхода и выделим тонкости, связанные с делегатами.
Реактивное программирование - это парадигма, ориентированная на потоки данных и распространение изменений.
Это значит, что можно легко выразить статические или динамические потоки данных в программе, а также реагировать на их изменения.
Например, при изменении реактивных характеристик игрока в профиле, мы всегда можем подписаться на изменение нужных характеристик и сделать обработку детерминированной.
Также можно получить и использовать текущие значение реактивных характеристик, если этого требует наша логика.
В реактивном программировании необходимо помнить, как работает ReactiveProperty и ReactiveCommand. Краткий обзор.
При вдумчивом подходе в коде не будет никаких сюрпризов.
Вы сможете неразрывно писать код, даже когда логика требует выполнить реактивную команду и получить и обработать результат.
Подробнее тут.
Плюсы:
- Упрощение работы с потоками данных.
Реактивное программирование позволяет обрабатывать запросы, упрощая работу с потоками данных и их синхронизацию.
- Лучшая масштабируемость и отзывчивость.
Игры, написанные с использованием реактивного подхода, часто более отзывчивы и легче масштабируются благодаря эффективному управлению потоками данных.
Делегаты работают медленнее, когда выполняется множество подписок.
- Выразительность и лаконичность кода.
Реактивное программирование позволяет выражать сложные потоки данных и их взаимодействия более лаконично и наглядно.
Минусы:
- Для разработчиков может быть сложно освоить парадигму реактивного программирования и научиться думать в её категориях.
- Отладка реактивного подхода может быть более сложной, если мы будем задействовать асинхронщину.
Продолжение 🔽
Реактивное программирование vs делегаты в C#, Unity 3D продолжение 🔽
Рассмотрим ключевые моменты каждого подхода и выделим тонкости, связанные с делегатами.
Реактивное программирование - это парадигма, ориентированная на потоки данных и распространение изменений.
Это значит, что можно легко выразить статические или динамические потоки данных в программе, а также реагировать на их изменения.
Например, при изменении реактивных характеристик игрока в профиле, мы всегда можем подписаться на изменение нужных характеристик и сделать обработку детерминированной.
Также можно получить и использовать текущие значение реактивных характеристик, если этого требует наша логика.
В реактивном программировании необходимо помнить, как работает ReactiveProperty и ReactiveCommand. Краткий обзор.
При вдумчивом подходе в коде не будет никаких сюрпризов.
Вы сможете неразрывно писать код, даже когда логика требует выполнить реактивную команду и получить и обработать результат.
Подробнее тут.
Плюсы:
- Упрощение работы с потоками данных.
Реактивное программирование позволяет обрабатывать запросы, упрощая работу с потоками данных и их синхронизацию.
- Лучшая масштабируемость и отзывчивость.
Игры, написанные с использованием реактивного подхода, часто более отзывчивы и легче масштабируются благодаря эффективному управлению потоками данных.
Делегаты работают медленнее, когда выполняется множество подписок.
- Выразительность и лаконичность кода.
Реактивное программирование позволяет выражать сложные потоки данных и их взаимодействия более лаконично и наглядно.
Минусы:
- Для разработчиков может быть сложно освоить парадигму реактивного программирования и научиться думать в её категориях.
- Отладка реактивного подхода может быть более сложной, если мы будем задействовать асинхронщину.
Продолжение 🔽
#архитектура #делегаты #event #action #Unity3D
Реактивное программирование vs делегаты в C#, Unity 3D продолжение 🔽
Делегаты в C# - это типы, которые представляют собой ссылки на методы.
С их помощью можно передавать методы в качестве аргументов другим методам, что делает делегаты удобным средством для реализации обратных вызовов и событий.
Плюсы:
- Делегаты позволяют создавать гибкие и масштабируемые приложения благодаря возможности использования методов в качестве параметров.
- Multicast делегаты могут ссылаться сразу на несколько методов, что позволяет легко реализовывать паттерн Observer.
Минусы:
- Сложности с multicast делегатами.
Хотя multicast делегаты предоставляют мощные возможности, управление порядком вызова и обработка возвращаемых значений могут быть сложными.
- Ограниченность в сравнении с реактивным программированием.
Делегаты могут быть менее выразительными при работе с сложными потоками данных по сравнению с реактивным программированием.
- Неявные тонкости, которые могут запутать начинающих разработчиков.
Продолжение 🔽
Реактивное программирование vs делегаты в C#, Unity 3D продолжение 🔽
Делегаты в C# - это типы, которые представляют собой ссылки на методы.
С их помощью можно передавать методы в качестве аргументов другим методам, что делает делегаты удобным средством для реализации обратных вызовов и событий.
Плюсы:
- Делегаты позволяют создавать гибкие и масштабируемые приложения благодаря возможности использования методов в качестве параметров.
- Multicast делегаты могут ссылаться сразу на несколько методов, что позволяет легко реализовывать паттерн Observer.
Минусы:
- Сложности с multicast делегатами.
Хотя multicast делегаты предоставляют мощные возможности, управление порядком вызова и обработка возвращаемых значений могут быть сложными.
- Ограниченность в сравнении с реактивным программированием.
Делегаты могут быть менее выразительными при работе с сложными потоками данных по сравнению с реактивным программированием.
- Неявные тонкости, которые могут запутать начинающих разработчиков.
Продолжение 🔽
Теперь вернёмся к опросу
К сожалению, корректный корректно ответила меньшая часть опрашиваемых.
❗Поскольку делегат - это ref тип, большинство посчитало, что в вышеприведенном коде вторая подписка сработает после вызова dealDamage в методе PassDealDamageToAnotherLayer, но это не так.
Для понимания происходящего в коде, опишу принцип работы:
Когда мы передаём делегат dealDamage в метод PassDealDamageToAnotherLayer, происходит передача по значению.
Это означает, что внутри PassDealDamageToAnotherLayer используется копия ссылки на тот же список вызовов делегата, что и в оригинальном делегате dealDamage на момент передачи.
Все изменения, которые происходят с делегатом dealDamage после его передачи в PassDealDamageToAnotherLayer (например, добавление новых методов в список вызовов делегата),
не отражаются на копии, которая уже была передана и используется внутри метода PassDealDamageToAnotherLayer.
Продолжение 🔽
К сожалению, корректный корректно ответила меньшая часть опрашиваемых.
public class Root : BaseMonoBehaviour
{
private delegate void DealDamage();
private void Start()
{
// инстанцируем делегат с лямбдой - заглушкой
DealDamage dealDamage = () =>
{
Debug.Log("Deal damage stub");
};
// тут условно передаем делегат в слой, который отвечает за вызов делегата
PassDealDamageToAnotherLayer(dealDamage);
// подписываемся логикой на делегат
dealDamage += () =>
{
Debug.Log("Deal damage subscriber");
};
}
private void PassDealDamageToAnotherLayer(DealDamage dealDamage)
{
// через 5 секунд сделаем вызов делегата
Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(_ =>
{
dealDamage();
});
}
}
❗Поскольку делегат - это ref тип, большинство посчитало, что в вышеприведенном коде вторая подписка сработает после вызова dealDamage в методе PassDealDamageToAnotherLayer, но это не так.
Для понимания происходящего в коде, опишу принцип работы:
Когда мы передаём делегат dealDamage в метод PassDealDamageToAnotherLayer, происходит передача по значению.
Это означает, что внутри PassDealDamageToAnotherLayer используется копия ссылки на тот же список вызовов делегата, что и в оригинальном делегате dealDamage на момент передачи.
Все изменения, которые происходят с делегатом dealDamage после его передачи в PassDealDamageToAnotherLayer (например, добавление новых методов в список вызовов делегата),
не отражаются на копии, которая уже была передана и используется внутри метода PassDealDamageToAnotherLayer.
Продолжение 🔽