Pull to refresh
85
2
Григорий Дядиченко @DyadichenkoGA

Master of Unity3D

Send message

Согласен. Звучит неплохо. Но всё равно вызовет вопросы что это. Так же как и "препродакшен". Если вносить это в смету. Но много у кого в сметах просто ТЗ.

Да, спасибо. Отличное дополнение :)

Я мало с жирными в плане звука проектами работал. Ну и по остальным пунктам согласен.

Ща исправлю. 200МБ, если в настройки не лезть телефона. С аойс 13 можно поставить что любой размер будет качаться.


Не, я на старте заявил, что тут разбираются инструменты именно Unity. А там мало неожиданного. Просто остальное очень контекстуально. Возьмём палитры. Допустим у нас 8-ми битная игра. Но допустим большая часть компрессий не позволяет настраивать в Unity битность текстуры. Поэтому получается если у нас текстуры 32 бита, а при этом 8 бит на цвет, то в одной текстуре мы можем хранить 4 текстуры. И можно написать инструмент. Но применимо это только для 8-ми битных игр.

Причём очевидно что лучше не использовать JPEG подобные алгоритмы сжатия, так как по принципу работы JPEG там будут весьма специфичные артефакты. Так как цветовая информация в JPEG ужимается. А без изменений остаётся грейскейл. И так далее.

Ну в целом да. Я перепроверил сейчас. И судя по вот этому.

В билд идёт только анимация. Но есть исключение, которое я добавлю. Если оно лежит в ресурсах. Просто бывает такое, что fbx кладётся в ресурсы. Чтобы загружать модель по пути, а не по ссылке. И нужно дописать, что это конкретно про этот кейс.

Да это скорее я себя так обозвал. Можно почитать в целом на сайте у меня https://noxatra.ru/

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

Ага. Задача решена для техасского холдема через перебор вариантов. Я согласен, что это не совсем "честно". Просто учитывая принцип алгоритма и его число операций в той же омахе ну будет в несколько раз больше вариантов. Определение всё равно займёт несущественно времени. Перевести к нужному виду ничего не стоит, и дальше цикл по 5-ти элементам дважды на 5-ти картах. Решение с mod приводит элегантно к такой скорости одного перебора, что нет смысла это особо оптимизировать даже. Это операция которая вызывается не каждый кадр рендера, а по событию смены состояния.

Я изначально изучая тему думал зайти через матрицы. То есть взять построить матрицу 4 х 13 и поискать какие-то зависимости по определителям, особенно если матрицу приводить к верхнетреугольному виду. Но разобрав этот алгоритм решил, что через матрицы будет не особо то и быстрее. Один проход на 7 картах вместо того чтобы 21 раз перебрать комбинации из 5-ти карт. Но проход этот очень сложный. Так как стритфлеш - 5 карт подряд в строке. Флеш просто 5 карт в строке. Стрит 5 карт подряд в разных столбцах, но не в одной строке. Пары и фуллхаузы определяются примерно так же по столбцам. И у меня была гипотеза что можно двигать каретку с памятью, и дальше получать определение комбинации по "нашёл сильнее". Но я для такого не придумал элегантный обход. Хотя думаю, что в целом он должен быть. Просто паттерны перебирать это явно долго. Так как худшее время это 4 * 13 * 8 = 416 (восемь - это комбинации кроме флеш рояла и старшей руки). Хотя вообще может текущий обход в 21 раз будет чуть помедленнее плюс докинем туда её выбор самих комбинаций из 5-ти карт. Но тут нет драматического выигрыша. А текущей производительности даже для калькулятора в современном мире хватит.

Если вдруг додумаю и придумаю элегантное решение на матрицах - и про него напишу.

Ага. Но это не меняет логику «ячейка-предмет». Просто появляется свойство предмета «форма в инвентаре», как отдельная информация по ID, и валидация возможности положить предмет в инвентарь в зависимости от форм, которые там уже находятся.

Да согласен. Тут нет вопросов, всё понятно и аргументировано. Спасибо. Я немного обсудил в своём канале в комментах к этому посту. Мой подход немного в другом, скопирую сюда часть, но суть та же. Enum это тоже не очень хорошо.

Вопрос:

Допустим потребуется у каждого предмета в инвентаре указывать его количество. Для реализации этого нужно будет добавить int Count в BaseItem? Или нужно будет отнаследоваться? Мне, честно, не очевидно, какой именно из вариантов подразумевается.

Или если посложнее - нужно будет добавить новую фичу - айтем-рюкзак. Как это реализовывать нужно будет? Наследоваться от BaseItem? Вводить какой-то enum в BaseItem, указывающий на тип предмета? Добавлять список компонентов и в этот список добавлять объект, отнаследованный от IInventoryActions? По-хорошему - над этим должен думать не исполнитель, которому поручена эта задача, а архитектор, изначально написавший этот код.

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

Ответ:

Да нет, Count в BaseItem добавлять нельзя. Это приведёт к тому, что придётся делать его Nullable, а обработка на null (или -1) из-за того, что предмет не имеет понятия Count везде — это плохо. Нужно отнаследоваться из этих двух выборов. А вообще нужно по хорошему завести свойство, которое запрашивается по айди из стораджа. И от него уже скачет реализация.

Миллион свойств, миллион обработок в ячейке на отображение и на клик?) Да нет, так как там будут только свойства обрабатываться относящиеся к инвентарю — это зависит уже от сложности реализации инвентаря. Чистое наследование — это скорее плохо, так как прийдётся либо тайпкастить, либо разделять ячейки на типы по типу предмета для отрисовки предмета. Логичнее, как и с иконкой идти по чекналл и отрисовке свойств в самой ячейке, вряд ли их там будет миллиард :)

И любой айтем можно таким образом добавить, так как вопрос лишь в том, как получить к нему данные, а для этого нам нужен только айди. А инвентарь занимается отрисовкой свойств относящихся к нему. И ему плевать что сумка-айтем умеет что-то ещё. Так что BaseItem надо было запечатать. А само по себе это для читаемости.

Это даже не класс модели. По сути этот класс можно было запечатать и свести к идентификатору. Называется он так для читаемости. Так как это по сути скелет, на который набрасываются свойства. Он даже не знает о существовании этих свойств, они набрасываются в зависимости от контекста использования. Как иконка. И дальше идёт базовый механизм расширения функциональности любого вью, без кучи "наследников вью, тайпкастов" и прочего мракобесия. Проверка обладает ли этот ID этим свойством, и если да то его отрисовкой в соответствующей панели. Так можно расширять до бесконечности от конкретики интерфейса. Просто есть стораджи разных свойств, эти свойства приписаны к идентификатором предмета. Вью или контроллеры запрашивают из стораждей (по сути моделей). "А у этого айди есть это свойство?" Если ответ да, то подключают нужную функциональность.

Просто это не делает код ужасным. Спорным в контексте конкретных применений - да. И как раз про это статья. Просто если скажем предметов у нас два типа или у них нет множества свойств. То это вообще не имеет значения. И можно наследоваться. А подход описанный мной сейчас - это оверинжиниринг.

Дак дело не в аргументации. Дело в том, что я говорю по компонентный подход. У меня наследование даже не используется. По сути про наследование только говорится :)

Аргументация может быть эмоциональной. А теперь давайте пойдём в ООП. Предмет это абстрактный класс или же интерфейс? Так как это объект с данными, а не объект поведения - это абстрактный класс. А вот стоит ли навешивать функциональность компонентами или же делать это через наследование - вопрос к системе. Разное поведение, Ну предположим у нас в инвентаре есть шмот перса, а есть расходники. Это объект одного класса? Если да, то почему и зачем? На клике что происходит, кто определяет и как. расходник не является предметом или шмотка не является предметом.

Просто вот реальный косяк тут тоже есть, но его будто бы никто не замечает, что меня огорчает. И это IEnumerable в интерфейсе инвентаря, но при этом я завязываюсь на логику индексов в идентификаторах ячеек. То есть по сути определяю выше поведение списка, и заменив реализацию на уровне инвентаря - всё сломается и будет баг. И это ошибка проектировки. Причём довольно стандартная)

А многие делают поведения компонентами. Вопрос не в том, что все знают. В этом я выразился некорректно, согласен. Вопрос в том, что обсуждаемое, мантры из SOLID, и т.п. не говорят об одной важной вещи. В чём конкретно проблема этого подхода? Почему стоит сделать иначе? Какой выигрыш это даст?

Суть не в паттернах, солиде, MVC, MVVM и т.п. А в том "для чего". Пример ужасного кода я так же могу показать, но суть в том, что код это далеко не он.

Я не обозначал целью статьи конкретную реализацию инвентаря. Все перекладывания и т.п. в текущей реализации вообще делаются на уровне префабов при желании. Часть с мелкими доработки. Изначальный тезис статьи, не бывает идеального кода в абстрактном случае. А в конкретном случае писать нет смысла, так как у каждого такой конкретный случай свой. И исходя из своего опыта к любому коду можно придраться, даже если он целиком решает задачу.

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

Да не нужен тут шаблон состояние, это пере усложнение бизнес логики ни за чем.

Про Instantiate. Ну не зная как работает в Unity GC и в целом сборка мусора, а так же Destroy, и что он в этом случае будет делать, то конечно можно представить невообразимые ужасы. В в данном контексте использования объект будет небольшим и удалится всего лишь ямль конфиг, который соберётся и при стратегии работы с большой кучей не вызовет никогда проблемы. Можно оптимизировать, когда понятна конкретика и контекст.

Про SO, ну кем-то используется, ток я его кажется использую чисто как статический ресурс. В шарпе тоже много вещей, которые нужно когда юзаешь понимать, как их стоит юзать)

Но давайте я по отвечаю

  • Не существует никакого "идеального" инвентаря. Любая разработка начинается с определения набора требований. Как я должен понять требования к инвентарю по описанию эффекта Даннинга-Крюгера и некой непонятой картинке инвентаря? Что означает каждая секция инвентаря? Почему у ячейки инвентаря нет количества предметов?

Потому что я не решаю задачу с картинки и не заявлял этого темой статьи

  • Почему абстрактный класс BaseItem без ключевого слово abstract? Зачем вообще строить какую-то невнятную иерархию наследования без обозначения, кто его наследники? Какая вообще проблема решается через наследование? Почему просто не создать структуру только для чтения под названием InventoryCell (BaseItem - слишком абстрактное название)?

Стандартный подход в геймдеве. Да это может быть класс контейнер в дальнейшем компонентном подходе. Это классика, это при разработке 5-6 взрослых игр знать надо

  • Почему количество ячеек в инвентаря определяется динамически, если на картинке нет полос прокруток?

Потому что это абстрактный инвентарь. Когда мы пишем по MVVM в целом странно такие вещи определять на каком-то уровне кроме View. Зачем ограничивать размер инвентаря в изначальной архитектуре системы? Для какой цели?

  • Почему у Inventory ровно один интерфейс с очень странным названием IInventoryActions? Почему не просто IInventory? Зачем этот интерфейс вообще нужен, если в классе User он не используется? Почему IInventory не наследуется хотя бы от IEnumerable? Почему не используется индексатор для доступа к ячейке инвентаря? Зачем Add и Remove возвращают bool? Я должен поверить на слово? Это прямое нарушение YAGNI и принципа единственной ответственности из SOLID.

Название - мой кодстайл. Это не нарушает единственность ответственности. Это нарушает что каждая фигня должна быть реализована, с чем я изначально в солиде не совсем согласен. А изначально это заложено для асинхронных операций, чтобы при доступе на сервер или куда-то ещё при ошибке авторизации или сетевых ошибках знать, что что-то пошло не так. Но я бы написал TryAdd, а не просто Add, это было бы вернее

  • Почему UIInventoryCell содержит OnClickCell? Где вообще было сказано, что по ячейкам кликают мышкой? Почему OnClickCell не часть BaseItem? Почему у события неправильное название? OnCellClicked - это название для обработчика события, а не для события. Зачем вообще нужно это событие, какая проблема решается? У такой реализации событий очень много проблем, что проще забыть вообще про ключевое слово event. Одна из проблем прямо наглядно видна в методе OnPointerClick с ненужной проверкой на null.

Это потом выпиливается. Но если брать до того, чтобы ячейка не знала про окно, но окно рулило кросс взаймодействием как контекст. Потом я решил контекст завести отдельно

  • Почему у класса User нет интерфейса? Почему приватное поле названо с большой буквы? Почему User знает как создавать инвентарь и самого себя? Почему не используется современный C# (или вовсе избавиться от приватного поля):

Опечатка, не относится к архитектуре. Сахар, тоже не относится к архитектуре и мелочь. Рихтер в целом считает сахар злом. Вкусовщина

  • И что с того, что в играх на одного человека всего один игрок? Как этот код поддерживать? Как написать тесты на этот код? Про существование шаблона одиночка лучше вообще забыть.

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

  • ID чего принимает GetData в интерфейсе IStorageActions? Зачем здесь вообще обобщение используется?

Доступ к статическим ресурсам, чтобы получить единицу даты по неопределённому алгоритму. Сторажд может быть SO, на диске, сетевой реализацией и много чем ещё

  • В классе ItemVisualData (плохое название, класс описывает информацию о предмете) можно повесить атрибут SerializeField на свойство. Зачем вообще нужен этот атрибут? Для сериализации он не нужен. Почему VisualName - это строка, а не ID ресурса локализации? Статическое и только для чтения - это разные слова с разным смыслом, т.е. "статическое хранилище" - вообще не означает "только для чтения".

Коммент не знания контекста юнити. Он нужен для Unity сериализации. Статическое хранилище в моём контексте ридонли ресурсы, не изменяемое в рантайме

  • ScriptableObject - это очень ужасное дизайнерское решение разработчиков Unity, что-то сопоставимое с шаблоном одиночка. Не надо в коде использовать сокращения, такие как SO. Почему эти данные не берутся из ресурсов?

Это оболочка над текстовым файлом с готовым интерфейсом. Обычный статический ресурс и в коде он берётся из ресурсов, а не синглтоном. Просто не умение пользоваться Unity. Это как ругаться на ньютонсофт json для хранения статических конфигов.

  • Для тестов надо писать тесты, а не портить класс User.

Не понимаю вообще к чему и почему

  • Storage и Inventory. Раз приходится пояснять, почему это не одно и тоже, значит в коде что-то не так. У меня прослеживается логика, что Inventory хранит ячейки инвентаря, а Storage хранит описания предметов. Т.е. Storage - изначально неправильное название сущности + класс занимается загрузкой данных, чем нарушает принцип единственной ответственности из SOLID и отсутствие инверсии зависимостей очень сильно усложняет тестирование такого кода. Почему это не репозиторий, который возвращает обычный массив?

Не нарушает солид, банальное разделение сущностей по доменам если уж мы в термины пойдём подобные. Это два несвязанных домена, поэтому не стоит их обобщать

  • Что такое null в классе UIInventoryCell? Есть же хотя бы шаблон Null Object, который упростит код в методе SetItem. То, как достается иконка в методе, тихий ужас.

Хотелось бы пояснение в чём. Null - отсутствие предмета. Ничё особеннного

  • DragContext. Как быть, если инвентарь пользователя взаимодействует с инвентарем ящика?

Использовать тот же контекст

  • Не надо вызывать Instantiate в классе UIInventoryDragContainer. Зачем использовать Tuple, когда в Unity поддерживается C# с нормальными именованными кортежами, а не вот это вот Item1.

Почему не надо вызывать? Тоже вкусовщина не относящаяся к архитектуре.

  • Зачем вводить IDynamicStorageActions, когда уже был обобщенный класс для хранения?

Мутабельные и иммутабельные данные. Просто так удобнее разделить

  • Не надо было в класс User добавлять в свойство вызов метода. Следовало в таком случае отказаться от свойства вообще. И не надо смешивать доменную модель с способом хранения: атрибут JsonProperty не к месту и нарушает принцип единственной ответственности в SOLID. Не надо делать такую жесть как сохранения пользователя на изменение инвентаря. Или возвращение false, когда ничего не удалось загрузить. Никто не вызовет метод Load, если загружать нечего.

На это я ответил выше. Оно ничего не нарушает (хотя это дискуссионный вопрос), просто удобство сериализации данных юзера, чтобы не выносить их в отдельные классы с данными. Это уже спорный момент, но не ведущий ни к каким проблемам с точки зрения проектировки

Эта проблема и не решалась. Хотя в рамках текущей архитектуры решается базово имплементацией второго инвентаря. Но вот про это в статье я и говорю :)

Писать про архитектуру дело неблагодарное. Слово abstact допустим. А что это меняет с точки зрения архитектуры?) Синтаксическое ограничение того, что класс абстрактный в контексте примера?)

Половина списка - это вкусовщина, которая никак не влияет не на расширение, ни на эксплуатацию реального решения. Про SRP вообще в целом не правда, так как атрибут не добавляет дополнительной ответственности классу. Особенно такой, которая создавала бы проблемы. Вот поэтому я и не пишу про хороший код, так как это просто неблагодарный труд. Так как придут люди без продуктового обоснования почему, а просто со вкусовыми предпочтениями :)

Сломать можно что угодно, если пользоваться советами бездумно. Все эти настройки - это максимальная степень оптимизации. Конечно если убрать из проекта пакет UI, где и лежит неймспейс EventSystem или же просто все галочки включать, то всё перестанет работать. Стрипинг удаляет модули, которые считаются не используемыми. И внося любые изменения в боевой проект нужно проверять, что они делают. Это просто советы не для новичков)

Да я просто как-то не интересуюсь именно PWA. Не моя специфика)

Что же касается движков. Unreal с вебом вообще не дружит. Там был какой-то плагин от комьюнити, но он давно не поддерживается вроде как. Так что только Unity, если брать прям движки

А про сравнивать. Ну я бы конечно прям не сравнивал, если были бы альтернативы хорошие. Сейчас разрабатывать на pure web почти равно разрабатывать свой движок :)

Я за пол года попробовал всё. Максимально близкое PlayCanvas из-за наличия визуального редактора. Просто визуальный редактор очень сильно влияет на скорость разработки. Но проблема везде одна. Исследовал я для решения на чём и как делать https://whitelabelgames.ru/ я упёрся в то, что представим что проект полетит и мне нужна будет команда или несколько команд. А теперь идём на hh и вбиваем babylon.js в поиск. Видим две вакансии. И как бы прекрасно не было любое веб сдк — так со всеми. А это значит захочешь масштабировать команду — расти своих. А это очень дорого и звучит сильно дороже связки Unity + React. И по скорости разработки фич, и по масштабированию команды, и по много чему ещё. Типа с точки зрения бизнеса я не нашёл ни одного аргумента перебивающего эти недостатки. Технологии то хорошие, просто толку то, если их никто не знает. Да и людям невыгодно учить, так как потом они пойдут на рынок труда и не найдут работу. А это даже немного нечестно по отношению к своей команде :)

Наличие разработчиков на рынке, комьюнити, числа решённых проблем, числа готовых решений которые можно прикрутить — слишком сильно решает. Кто-то идёт в такие риски и думаю работает эффективно, но для своих проектов и продуктов я обосновать такое решение не смог :)

Недостатки Unity я решил и в статье описал как решается большая часть. Или посчитал их несущественными относительно бизнес недостатков остальных решений :) Я же разрабатываю проекты под заказ сейчас для разных компаний. И у меня очень сильно скачет загрузка. Иногда бывает по 10 проектов одновременно. И толку мне от pure web технологий, если я не могу масштабировать команду под такие случаи :)

Просто тут надо сравнивать. И сравнивать смотря с чем. Скажем возьмём чистый веб. Пикси, плейкансвас и т.п. Я пришёл к тому, что процесс Unity + React эффективнее по бизнес причинам масштабирования команды. Unity разработчиков можно найти много на рынке, React разрабов тоже хватает. Но если мне нужно будет масштабировать команду на Pixi или Three.js, да просто повеситься проще :) Тех кто знает эти технологии очень мало. И мы начинаем учить своих. Это долго, дорого и менее эффективно) Чем за гуй отвечает реакт, за механику юнитисты. Тут думаю любому понятно, как масштабировать команду. Это не задача с кучей звёздочек. Так как и те, и другие специалисты на рынке есть :)

Да требуется иметь две экспертизы для поддержки проекта, но это не такая высокая цена с точки зрения бизнеса в долгосрочной перспективе скажем так. Чем когда бюджеты есть и ты просто тратишь пол года на обучение людей твоему фреймворку написанному поверх Three.js, обучая нюансам Three.js. Конечно это не всё, чем ты платишь юзая Unity. Так как скажем ты теряешь в возможности динамически грузить шейдеры, так как их придётся компилировать и пересобирать сборку. Но так как это Web для меня это тоже "малое зло" :)

За цену двух экспертиз поддержки проекта ты получешь отличное и удобное SDK для механик (Unity) с огромным комьюнити. И тоже самое для SDK разработки своего интерфейса (React) с таким же огромным комьюнити. Просто исследуя разные подходы реализации веб проектов я пришёл к этому из соображений мышления студии нежели разработчика :)

Я бы тут обозначил другую проблему. Данный подход применим только для чисто веб проектов. Потому что смешивая технологии мы теряем возможность сделать адекватное приложение. Можно конечно запихнуть в стим билд засунув туда в сборку локальный сервер и chromium для отрисовки игры. А на мобилке вебвью по сути запускать. Но это "костыль" обладающий всеми недостатками веба. Скажем так сделал Warcraft 3: Reforged с главным меню, и видно что там внутри веб :)

Поэтому делая такую смесь мы теряем в некотором смысле в кроссплатформенности. Плюс это требует двух разработчиков. Но для мобильного веба делать проект чисто на юнити — это натуральное самоубийство. Потому что там постоянно будут лезть проблемы и нужно будет писать воркэраунды, так как официальной поддержки веба нет. Но как я потестировал чаще всего ломаются всякие инпут филды и т.п. Плюс нужно всё равно иметь JS экспертизу, чтобы правильно давать разрешения на скажем гироскоп или камеру. Правильно пробрасывать аналитику и т.п. И поддержка всех платформ становится гемором. Поэтому это решение (как и любое существующее решение) — не серебряная пуля. И всё очень зависит от контекста. Просто есть и такой подход :)

Для рендера не GUI задач. В основном 3д или же 2д со сложной механикой взаимодействия типа платформера. Игра - это же не гуй. Ну собственно вот частный пример. У меня есть такой продукт https://whitelabelgames.ru/game/ar-bow И это очень простая игра, и очень простая механика. Разработка на три, плейканвасе и т.п. просто по моему опыту медленнее

Когда я пытался ту же самую механику сделать на Three.js и полностью на веб технологиях я потратил в разы больше времени, чем на Unity + React. Ну, а на реакте такое вообще проблематично сделать. Всё очень зависит от игры

Если делается текстовый квест, hidden object и т.п. без VFX или с VFX который проще сделать на стороне веба, то Unity действительно нет нужды использовать. Скажем вот игра целиком на реакте, так как Unity тут ни к чему https://whitelabelgames.ru/game/card-game

Если коротко, очень зависит от игры. Описанный кейс вероятно будет решать реакт разработчик вообще. Так как весь гуй на стороне реакта)

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

Information

Rating
1,360-th
Location
Москва, Москва и Московская обл., Россия
Registered
Activity

Specialization

Game Developer, Chief Technology Officer (CTO)
Lead
Git
C#
C++
Python
OOP
.NET
English
Research work
Algorithms and data structures
Applied math