Короче, чуваки, я тоже очень расстроился, что не портировали на C#, но немного поизучав вопрос понял, почему Go. Там в исходных кодах на TS дофига всяких self-referenced структур, типа:
type TypeSymbol struct {
Name string
Flags int
Parent *TypeSymbol
}
И фишка в том, что в dotNet у этого нет аналога! Можно, сделать класс, но это будет не то, потому что нам нужно последовательное хранение в памяти в массивах:
class TypeSymbol {
public string Name;
public int Flags;
public TypeSymbol Parent; // такой код не скомпилируется для struct
}
Вариант делать через unsafe тоже не подходит, потому что в таком случае Parent не будет менеджериться сборщиком мусора, в отличии от TS и Go.
unsafe struct TypeSymbol
{
// Фиксированный буфер для хранения имени (если хотим полностью unmanaged-структуру)
public fixed char Name[128];
public int Flags;
public TypeSymbol* Parent; // нужно делать освобождение памяти самому Marshal.FreeHGlobal
}
Лучшим решением был бы ввод что-то типа NativePtr (в F# такое есть, но и там он не под GC), но я не знаю насколько это реалистично добавить в dotNet.
struct TypeSymbol {
public string Name;
public int Flags;
public NativePtr<TypeSymbol > Parent;
}
Жаль всё равно, что решили не тратить на это ресурсы, поддержка разрабам AOT не помешала бы, но технические проблемы тут действительно серьёзные, а не просто нежелание писать неидиоматический код на С#.
ещё без Start (а с инстаншиэйтом только через фабрику) можно уйти от .Builder()
просто все монобехи у которых есть [Dependency] дописывать в switch. Хотя не очень понятно, что мешает и сейчас генерить BuildUp для них автоматически...
с методом Start, который вызывает инициализацию класса, вы как-будто бы нарушаете свой принцип - отсутствие зависимости на фреймворк DI.
По-моему стрёмно, когда каждый монобех сам себя инициализирует - ваш Composition.Shared превращается в какой-то сервис локатор в таком случае. С инстаншиэйтом через фабрики (как сделано во всех di фреймворках на юнити) - можно перейти на pure.di просто изменив атрибут с [Inject] на [Dependency] и не нужно ничего дописывать (ну кроме фабрики).
foreach (var innerMonoBeh in GetAllInnerMonobehs(go))
{
// вот тут бы тогда кодогенерить switch на все поддерживаемые монобехи,
// для которых builder зарегистрирован
switch (innerMonoBeh)
{
case Clock clock:
BuildUp(clock);
break;
default: break;
}
}
как будто бы не очень удобно - хочется чтобы был какой-то factory метод для инстаншиэйта, который бы инициализировал все монобэхи на префабе. типа _composition.Instantiate(prefab);
и внутри было что-то типа
public GameObject Instantiate(GameObject go)
{
var result = GameObject.Instantiate(go);
foreach (var innerMonoBeh in GetAllInnerMonobehs(go))
{
BuildUp(innerMonoBeh);
}
return result;
}
Вы ограничивали как-то дизайнеров в фигме? Например, был ли запрет на использование эффектов, векторных элементов? Как потом переносили тени, блюр и прочие вещи в движок? Какой вообще был пайплайн по импорту пнг/векторов из фигмы в проект?
Так можно избавиться от GameAssembler, который будет только бухнуть со временем (или их будет много разных?).
Да и сервис-локатор можно взять статический https://github.com/Leopotam/globals/blob/master/src/Service.cs , убрав GameLocatorInstaller. Всё-таки сервисы, которые юзаются через сервис-локатор, обычно знают, что они единичные, и регистрировать самим себя - нагляднее при чтении их кода.
Но это всё актуально, только если идти честным путём сервис-локатора, а не как промежуточный этап к самописному DI :)
Раз уж у GameMachine нет интерфейса, и значит мокать для тестов его не планируют, то не проще ли вместо кучи фабрик(а в фабрики ведь тоже нужно как-то этот GameMachine прокидывать) сделать этот GameMachine синглтоном, ну или получать его через какой-нибудь сервис локатор?
Вообще, даже для текущей реализации напрашивается улучшения - если количество ивентов у GameMachine останется таким же, то логично уйти от кастов типов, и сделать List<IFinishGameListener> _finishGameListeners и т. п.
Ну и обращение к приватным методам и переменным через this. - режет глаз. Понятно, что это вкусовщина, и можно даже найти какие-то аргументы за использование this, но так не принято. Лучше придерживаться более распространённых конвенций в обучающих статьях.
А если по ходу игру нужно инстанциировать какой-нибудь объект с монобехом, который должен реагировать на паузу игры? Как в него прокинуть GameMachine, чтобы вызвать GameMachine.AddListner(this)? Получается какая-то циклическая зависимость.
По-моему первая картинка выглядит как результат рефакторинга последней :) Функционал идентичный, но разница в простоте огромная.
Судя по статье - какие варианты расширения предполагает автор? Добавление новых типов оружия. В начальном случае для добавления нового типа оружия нужно добавить... новый наследник Weapon. Звучит просто и логично.
А во втором? Новые наследники IAttack и AttackData, да ещё не забыть добавить кейс в AttackManager.GetAttack
И почему тогда второй вариант гибче первого, если добавлять во втором приходится больше, и в несколько мест? Одна из задач архитектуры - локализовать точки расширяемости кода, и первый вариант справляется с этой задачей лучше.
Вообще архитектура, т.е. добавление слоёв абстракции - это увеличение сложности программы, и за счёт этого должны решаться какие-то проблемы, но в статье не показаны никакие проблемы первого варианта, поэтому все эти трансформации выглядят как оверинженеринг.
Это работает для такого простого случая, но что случится, если кто-то перевернёт экран во время анимации, или после перемещения какого-нибудь элемента со своей стартовой позиции?
Обычно для поддержки нескольких ориентаций экрана, или более точного лэйаута (например, отдельно для 4:3 планшета и 21:9 смартфона) создают под каждый случай свою вьюшку. И при ивенте смене ориентации пересоздают и переинициализируют окошки.
Короче, чуваки, я тоже очень расстроился, что не портировали на C#, но немного поизучав вопрос понял, почему Go. Там в исходных кодах на TS дофига всяких self-referenced структур, типа:
которые в Go переходят в
И фишка в том, что в dotNet у этого нет аналога!
Можно, сделать класс, но это будет не то, потому что нам нужно последовательное хранение в памяти в массивах:
Вариант делать через unsafe тоже не подходит, потому что в таком случае Parent не будет менеджериться сборщиком мусора, в отличии от TS и Go.
Лучшим решением был бы ввод что-то типа NativePtr (в F# такое есть, но и там он не под GC), но я не знаю насколько это реалистично добавить в dotNet.
Жаль всё равно, что решили не тратить на это ресурсы, поддержка разрабам AOT не помешала бы, но технические проблемы тут действительно серьёзные, а не просто нежелание писать неидиоматический код на С#.
ещё без Start (а с инстаншиэйтом только через фабрику) можно уйти от .Builder()
просто все монобехи у которых есть [Dependency] дописывать в switch. Хотя не очень понятно, что мешает и сейчас генерить BuildUp для них автоматически...
с методом Start, который вызывает инициализацию класса, вы как-будто бы нарушаете свой принцип - отсутствие зависимости на фреймворк DI.
По-моему стрёмно, когда каждый монобех сам себя инициализирует - ваш Composition.Shared превращается в какой-то сервис локатор в таком случае. С инстаншиэйтом через фабрики (как сделано во всех di фреймворках на юнити) - можно перейти на pure.di просто изменив атрибут с [Inject] на [Dependency] и не нужно ничего дописывать (ну кроме фабрики).
и ещё лучше инжектить не через проперти, а через метод, типа
Вот это:
как будто бы не очень удобно - хочется чтобы был какой-то factory метод для инстаншиэйта, который бы инициализировал все монобэхи на префабе. типа _composition.Instantiate(prefab);
и внутри было что-то типа
Удивляют те, кто гордится, что в компании одни сеньоры. Ведь всю рутину джунов и мидлов им самим приходится делать.
а кого вы видите своими конкурентами? Можно какой-то список рефов/аналогов в вашем жанре глянуть?
Рекламу покупали/планируете закупать? Какой cpi и можно ссылку на креатив?
Вы ограничивали как-то дизайнеров в фигме? Например, был ли запрет на использование эффектов, векторных элементов? Как потом переносили тени, блюр и прочие вещи в движок? Какой вообще был пайплайн по импорту пнг/векторов из фигмы в проект?
Я уже запутался в сущностях. Раз уж сервис-локатор всё-равно используется, то MoveController проще выглядит c таким методом.
Так можно избавиться от GameAssembler, который будет только бухнуть со временем (или их будет много разных?).
Да и сервис-локатор можно взять статический https://github.com/Leopotam/globals/blob/master/src/Service.cs , убрав GameLocatorInstaller. Всё-таки сервисы, которые юзаются через сервис-локатор, обычно знают, что они единичные, и регистрировать самим себя - нагляднее при чтении их кода.
Но это всё актуально, только если идти честным путём сервис-локатора, а не как промежуточный этап к самописному DI :)
Раз уж у GameMachine нет интерфейса, и значит мокать для тестов его не планируют, то не проще ли вместо кучи фабрик(а в фабрики ведь тоже нужно как-то этот GameMachine прокидывать) сделать этот GameMachine синглтоном, ну или получать его через какой-нибудь сервис локатор?
Вообще, даже для текущей реализации напрашивается улучшения - если количество ивентов у GameMachine останется таким же, то логично уйти от кастов типов, и сделать List<IFinishGameListener> _finishGameListeners и т. п.
Ну и обращение к приватным методам и переменным через this. - режет глаз. Понятно, что это вкусовщина, и можно даже найти какие-то аргументы за использование this, но так не принято. Лучше придерживаться более распространённых конвенций в обучающих статьях.
А если по ходу игру нужно инстанциировать какой-нибудь объект с монобехом, который должен реагировать на паузу игры? Как в него прокинуть GameMachine, чтобы вызвать GameMachine.AddListner(this)? Получается какая-то циклическая зависимость.
Библиотека очень нужная, но поход к байндингу uxml-first не нравится :(
Я бы предпочёл делать связку через код:
По-моему первая картинка выглядит как результат рефакторинга последней :) Функционал идентичный, но разница в простоте огромная.
Судя по статье - какие варианты расширения предполагает автор? Добавление новых типов оружия. В начальном случае для добавления нового типа оружия нужно добавить... новый наследник Weapon. Звучит просто и логично.
А во втором? Новые наследники IAttack и AttackData, да ещё не забыть добавить кейс в AttackManager.GetAttack
И почему тогда второй вариант гибче первого, если добавлять во втором приходится больше, и в несколько мест? Одна из задач архитектуры - локализовать точки расширяемости кода, и первый вариант справляется с этой задачей лучше.
Вообще архитектура, т.е. добавление слоёв абстракции - это увеличение сложности программы, и за счёт этого должны решаться какие-то проблемы, но в статье не показаны никакие проблемы первого варианта, поэтому все эти трансформации выглядят как оверинженеринг.
По-прежнему актуален и очень полезен прошлогодний доклад про nullable в unity c девгамма.
Это работает для такого простого случая, но что случится, если кто-то перевернёт экран во время анимации, или после перемещения какого-нибудь элемента со своей стартовой позиции?
Обычно для поддержки нескольких ориентаций экрана, или более точного лэйаута (например, отдельно для 4:3 планшета и 21:9 смартфона) создают под каждый случай свою вьюшку. И при ивенте смене ориентации пересоздают и переинициализируют окошки.
Не знаю насчёт OnTriggerExit, но всегда OnDestroy вызывается на выключенном объекте.