#сериализация #десериализация
Приветствую 👋
Сегодня говорим про конвертеры в JsonNet. Первая часть статьи про сериализацию и десериализацию тут
Конвертеры в JsonNET представляют собой механизм для настройки пользовательской сериализации и десериализации определенных типов данных или объектов в формате JSON. Дают возможность настроить процесс преобразования объектов в JSON и обратно.
JsonNet предоставляет два основных типа конвертеров:
- JsonConverter;
- JsonConverter<T>, где T - это конкретный тип данных, для которого конвертер применяется.
За определение логики сериализации и десериализации отвечают методы WriteJson и ReadJson, затронем их ниже по тексту.
Как работают конвертеры:
Наследование - создание собственных классов наследованием от JsonConverter или JsonConverter<T>.
Сериализация:
В методе WriteJson определяем преобразование объекта в JSON.
Создаем JSON объекты, устанавливаем свойства и вызываем методы для добавления данных в JSON строку.
Десериализация:
В методе ReadJson определяем преобразование JSON обратно в объект.
Читаем свойства из JSON строки, создаем и инициализируем объекты, затем возвращаем получившийся объект.
Рассмотрим пример простого конвертера для преобразования строки в верхний регистр:
В этом примере UpperCaseConverter преобразует строку в верхний регистр как при сериализации, так и при десериализации.
Настройка единой конфигурации сериализации и десериализации для всего проекта.
Класс JsonSerializerSettings позволяет настроить единую конфигурацию сериализации и десериализации для всего проекта и управлять процессом из единого места.
Разберем подробнее
Настроим ContractResolver и NamingStrategy с помощью JsonSerializerSettings.
Contract Resolver (ContractResolver) - определяет, как JsonNet будет находить и настраивать контракты для сериализации и десериализации. Позволяет контролировать, какие свойства будут включены в процесс, игнорировать некоторые свойства, управлять настройками атрибутов и т. д.
Naming Strategy (NamingStrategy) - позволяет управлять форматированием имен свойств в JSON, определяет, как будут преобразованы имена свойств объектов в имена полей JSON и наоборот.
Рассмотрим пример использования JsonSerializerSettings:
В примере ContractResolver устанавливается в DefaultContractResolver, а NamingStrategy в CamelCaseNamingStrategy, что преобразует имена свойств в стиле Camel Case.
Хорошего дня!
Приветствую 👋
Сегодня говорим про конвертеры в JsonNet. Первая часть статьи про сериализацию и десериализацию тут
Конвертеры в JsonNET представляют собой механизм для настройки пользовательской сериализации и десериализации определенных типов данных или объектов в формате JSON. Дают возможность настроить процесс преобразования объектов в JSON и обратно.
JsonNet предоставляет два основных типа конвертеров:
- JsonConverter;
- JsonConverter<T>, где T - это конкретный тип данных, для которого конвертер применяется.
За определение логики сериализации и десериализации отвечают методы WriteJson и ReadJson, затронем их ниже по тексту.
Как работают конвертеры:
Наследование - создание собственных классов наследованием от JsonConverter или JsonConverter<T>.
Сериализация:
В методе WriteJson определяем преобразование объекта в JSON.
Создаем JSON объекты, устанавливаем свойства и вызываем методы для добавления данных в JSON строку.
Десериализация:
В методе ReadJson определяем преобразование JSON обратно в объект.
Читаем свойства из JSON строки, создаем и инициализируем объекты, затем возвращаем получившийся объект.
Рассмотрим пример простого конвертера для преобразования строки в верхний регистр:
В этом примере UpperCaseConverter преобразует строку в верхний регистр как при сериализации, так и при десериализации.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
public class UpperCaseConverter : JsonConverter<string>
{
public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer)
{
writer.WriteValue(value.ToUpper());
}
public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;
return reader.Value.ToString().ToUpper();
}
}
Применение этого конвертера:
class Program
{
static void Main()
{
var settings = new JsonSerializerSettings
{
Converters = { new UpperCaseConverter() }
};
string json = JsonConvert.SerializeObject("hello", settings);
Console.WriteLine(json); // Output: "HELLO"
string value = JsonConvert.DeserializeObject<string>(json, settings);
Console.WriteLine(value); // Output: "HELLO"
}
}
Настройка единой конфигурации сериализации и десериализации для всего проекта.
Класс JsonSerializerSettings позволяет настроить единую конфигурацию сериализации и десериализации для всего проекта и управлять процессом из единого места.
Разберем подробнее
Настроим ContractResolver и NamingStrategy с помощью JsonSerializerSettings.
Contract Resolver (ContractResolver) - определяет, как JsonNet будет находить и настраивать контракты для сериализации и десериализации. Позволяет контролировать, какие свойства будут включены в процесс, игнорировать некоторые свойства, управлять настройками атрибутов и т. д.
Naming Strategy (NamingStrategy) - позволяет управлять форматированием имен свойств в JSON, определяет, как будут преобразованы имена свойств объектов в имена полей JSON и наоборот.
Рассмотрим пример использования JsonSerializerSettings:
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Program
{
static void Main()
{
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy() // Use CamelCase naming strategy
},
Formatting = Formatting.Indented // Optional formatting for readability
};
var data = new { UserName = "Sergey", UserAge = 40 };
string json = JsonConvert.SerializeObject(data, settings);
Console.WriteLine(json);
}
}
В примере ContractResolver устанавливается в DefaultContractResolver, а NamingStrategy в CamelCaseNamingStrategy, что преобразует имена свойств в стиле Camel Case.
Хорошего дня!
#сериализация #десериализация
Приветствую 👋
Сегодня говорим про применение конвертеров для реактивных свойств.
Данная публикация третья, предыдущие по ссылкам:
часть 1
часть 2
Конвертеры в JsonNET можно применять как для обычных типов данных, так и для более сложных структур - реактивных свойств (например, ReactiveProperty), в ситуации когда необходимо корректно выполнять JSON сериализацию и десериализацию объектов, содержащих реактивные свойства.
Использование конвертеров позволяет решить проблему корректной обработки такого типа объектов, поскольку стандартные механизмы JSON сериализации не всегда способны сделать это корректно.
Пример создания конвертера для ReactiveProperty:
Рассмотрим применение написанного выше конвертера для сериализации и десериализации ReactiveProperty:
В примере выше ReactivePropertyConverter позволяет корректно выполнять JSON сериализацию и десериализацию ReactiveProperty<int> объектов.
Это дает возможность сохранять и восстанавливать состояние реактивных свойств при работе с JSON форматом.
❗JsonNET позволяет выполнять процесс сериализации и десериализации JSON без использования дополнительных атрибутов в классах. Что особенно удобно, если у вас нет возможности или желания модифицировать существующие классы добавлением атрибутов.
Вместо атрибутов можно использовать JsonSerializerSettings, ContractResolver и NamingStrategy (писал ранее). Они позволят контролировать процесс сериализации и десериализации без внесения изменений в классы данных.
Пример использования JsonSerializerSettings для сериализации и десериализации без атрибутов.
Подытожу
Сериализация и десериализация
- неотъемлемая часть разработки игровых проектов.
- обеспечивают сохранение данных, синхронизацию состояний в сетевых играх и общую целостность игрового мира.
- позволяют обеспечить высокую производительность и качество игрового опыта.
Хорошего дня!
Приветствую 👋
Сегодня говорим про применение конвертеров для реактивных свойств.
Данная публикация третья, предыдущие по ссылкам:
часть 1
часть 2
Конвертеры в JsonNET можно применять как для обычных типов данных, так и для более сложных структур - реактивных свойств (например, ReactiveProperty), в ситуации когда необходимо корректно выполнять JSON сериализацию и десериализацию объектов, содержащих реактивные свойства.
Использование конвертеров позволяет решить проблему корректной обработки такого типа объектов, поскольку стандартные механизмы JSON сериализации не всегда способны сделать это корректно.
Пример создания конвертера для ReactiveProperty:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Reactive.Subjects;
public class ReactivePropertyConverter<T> : JsonConverter<ReactiveProperty<T>>
{
public override void WriteJson(JsonWriter writer, ReactiveProperty<T> value, JsonSerializer serializer)
{
if (value != null)
{
serializer.Serialize(writer, value.Value);
}
else
{
writer.WriteNull();
}
}
public override ReactiveProperty<T> ReadJson(JsonReader reader, Type objectType, ReactiveProperty<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
T innerValue = serializer.Deserialize<T>(reader);
return new ReactiveProperty<T>(innerValue);
}
}
Рассмотрим применение написанного выше конвертера для сериализации и десериализации ReactiveProperty:
class Program
{
static void Main()
{
var settings = new JsonSerializerSettings
{
Converters = { new ReactivePropertyConverter<int>() }
};
ReactiveProperty<int> reactiveValue = new ReactiveProperty<int>(42);
string json = JsonConvert.SerializeObject(reactiveValue, settings);
Console.WriteLine(json); // Output: 42
ReactiveProperty<int> deserializedValue = JsonConvert.DeserializeObject<ReactiveProperty<int>>(json, settings);
Console.WriteLine(deserializedValue.Value); // Output: 42
}
}
В примере выше ReactivePropertyConverter позволяет корректно выполнять JSON сериализацию и десериализацию ReactiveProperty<int> объектов.
Это дает возможность сохранять и восстанавливать состояние реактивных свойств при работе с JSON форматом.
❗JsonNET позволяет выполнять процесс сериализации и десериализации JSON без использования дополнительных атрибутов в классах. Что особенно удобно, если у вас нет возможности или желания модифицировать существующие классы добавлением атрибутов.
Вместо атрибутов можно использовать JsonSerializerSettings, ContractResolver и NamingStrategy (писал ранее). Они позволят контролировать процесс сериализации и десериализации без внесения изменений в классы данных.
Пример использования JsonSerializerSettings для сериализации и десериализации без атрибутов.
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
class Program
{
static void Main()
{
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
},
Formatting = Formatting.Indented
};
// Serialize
var data = new { UserName = "Sergey", UserAge = 40 };
string json = JsonConvert.SerializeObject(data, settings);
Console.WriteLine(json);
// Deserialize
var deserializedData = JsonConvert.DeserializeObject(json, settings);
Console.WriteLine(deserializedData);
}
}
Подытожу
Сериализация и десериализация
- неотъемлемая часть разработки игровых проектов.
- обеспечивают сохранение данных, синхронизацию состояний в сетевых играх и общую целостность игрового мира.
- позволяют обеспечить высокую производительность и качество игрового опыта.
Хорошего дня!
#задача #unity
Приветствую 👋
Предлагаю разобрать задачу, которая легко может ввести в ступор разработчика 🫣
Итак, рассмотрим пример из проекта 👇
У нас в игре есть турель (GameObject), которая стреляет по некоторому таргету (Transform).
Дуло пушки (находится внутри GameObject), поднято на некоторый оффсет (x,y,z).
При стрельбе пушка должна поворачиваться к таргету так, чтобы дуло смотрело точно в центр таргета.
Напишите в комментариях, как бы вы решали данную задачу ✍️
Ближе к выходным расскажу вам свое решение и дам комментарий по вашим подходам!
Хорошего дня 🤝
Приветствую 👋
Предлагаю разобрать задачу, которая легко может ввести в ступор разработчика 🫣
Итак, рассмотрим пример из проекта 👇
У нас в игре есть турель (GameObject), которая стреляет по некоторому таргету (Transform).
Дуло пушки (находится внутри GameObject), поднято на некоторый оффсет (x,y,z).
При стрельбе пушка должна поворачиваться к таргету так, чтобы дуло смотрело точно в центр таргета.
Напишите в комментариях, как бы вы решали данную задачу ✍️
Ближе к выходным расскажу вам свое решение и дам комментарий по вашим подходам!
Хорошего дня 🤝
Приветствую 👋
Сегодняшняя публикация посвящена разбору задачи про турель.
Благодарю за активность всех, кто подумал над решением и предложил свой вариант в комментариях 🤝
Напомню вводные В игре есть турель (GameObject), которая стреляет по некоторому таргету (Transform).
Дуло пушки (находится внутри GameObject), поднято на некоторый оффсет (x,y,z).
При стрельбе пушка должна поворачиваться к таргету так, чтобы дуло смотрело точно в центр таргета.
Мой вариант строится на векторной алгебре.
Для его рассмотрения обратимся к приложенной иллюстрации.
Как видно из рисунка, для поворота пушки в положение, когда ее дуло нацелено на таргет, необходимо, чтобы форвард пушки был параллелен векторной разнице (target.position - muzzle.position).
Результатом разницы будет вектор, начало которого лежит в центре дула, конец - в центре таргета.
Код:
Хороших выходных 🤝
Сегодняшняя публикация посвящена разбору задачи про турель.
Благодарю за активность всех, кто подумал над решением и предложил свой вариант в комментариях 🤝
Напомню вводные В игре есть турель (GameObject), которая стреляет по некоторому таргету (Transform).
Дуло пушки (находится внутри GameObject), поднято на некоторый оффсет (x,y,z).
При стрельбе пушка должна поворачиваться к таргету так, чтобы дуло смотрело точно в центр таргета.
Мой вариант строится на векторной алгебре.
Для его рассмотрения обратимся к приложенной иллюстрации.
Как видно из рисунка, для поворота пушки в положение, когда ее дуло нацелено на таргет, необходимо, чтобы форвард пушки был параллелен векторной разнице (target.position - muzzle.position).
Результатом разницы будет вектор, начало которого лежит в центре дула, конец - в центре таргета.
Код:
transform.rotation = Quaternion.LookRotation(target.position - muzzle.position);
Хороших выходных 🤝
#Unity3D
Друзья, приветствую👋
Предлагаю запустить цикл вопрос/ответ!
Жду ваши вопросы в комментариях!
В рамках недели буду порционно отвечать на каждые 5-10 в зависимости от объема.
Хорошего дня.
Друзья, приветствую👋
Предлагаю запустить цикл вопрос/ответ!
Жду ваши вопросы в комментариях!
В рамках недели буду порционно отвечать на каждые 5-10 в зависимости от объема.
Хорошего дня.
#вопрос_ответ
Приветствую👋
Сегодня разберу один из недавно заданных вопросов. Ответ получился объемным, на публикацию 📚
Вопрос от @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 мин начинаем стрим, стрим будет проходить в этом канале.