Небольшой апдейт! Сейчас занимаюсь системой частиц, о ней будет попозже. А пока - color picker!
Казалось бы, простая штука - сделать поля ввода RGBA, вывести цвет... Но это уже прошлый век какой-то. Хочется ползунков и интерактивности.
Сделал у себя довольно классический вид, совместив все нужное:
✅ есть классическая зона выбора цвета на пространстве от темного к светлому
✅ есть классические RGBA
✅ есть HSL (hue, saturation, lightness) - оч удобный способ выбирать цвет, порой гораздо понятнее обычных RGB
✅ есть текстовые поля ввода RGBA и HEX представления цвета
Чего еще хотелось бы:
- цветовые пресеты
- выбор цвета пипеткой с экрана
Немного подробностей, где получились интересные штуки.
Под каждым ползунком есть динамическая текстура, растр которой каждый раз пересчитывается, исходя их текущих параметров цвета. Прямо на каждом ползунке можно увидеть, как поменяется цвет, если его передвинуть. Так же есть шахматная подложка, чтобы оценить прозрачность.
Работа с цветом в формате HSL усложнена тем, что эти параметры высчитываются из RGB, а значит они ведут себя нестабильно. Например, если увести цвет в черный, то уже фиг пойми какие там HSL, и формулы начинают выдавать разные результаты. Но ведь юзер, двигая один из ползунков HSL не хочет видеть как все остальные ползунки сбиваются. Поэтому при изменении одного из параметров HSL, остальные фиксируются, чтобы их не "увело"
Ну а в остальном обычный GUI, который на o2 делать уже довольно легко. Весь color picker занимает около 450 строк кода
Казалось бы, простая штука - сделать поля ввода RGBA, вывести цвет... Но это уже прошлый век какой-то. Хочется ползунков и интерактивности.
Сделал у себя довольно классический вид, совместив все нужное:
✅ есть классическая зона выбора цвета на пространстве от темного к светлому
✅ есть классические RGBA
✅ есть HSL (hue, saturation, lightness) - оч удобный способ выбирать цвет, порой гораздо понятнее обычных RGB
✅ есть текстовые поля ввода RGBA и HEX представления цвета
Чего еще хотелось бы:
- цветовые пресеты
- выбор цвета пипеткой с экрана
Немного подробностей, где получились интересные штуки.
Под каждым ползунком есть динамическая текстура, растр которой каждый раз пересчитывается, исходя их текущих параметров цвета. Прямо на каждом ползунке можно увидеть, как поменяется цвет, если его передвинуть. Так же есть шахматная подложка, чтобы оценить прозрачность.
Работа с цветом в формате HSL усложнена тем, что эти параметры высчитываются из RGB, а значит они ведут себя нестабильно. Например, если увести цвет в черный, то уже фиг пойми какие там HSL, и формулы начинают выдавать разные результаты. Но ведь юзер, двигая один из ползунков HSL не хочет видеть как все остальные ползунки сбиваются. Поэтому при изменении одного из параметров HSL, остальные фиксируются, чтобы их не "увело"
Ну а в остальном обычный GUI, который на o2 делать уже довольно легко. Весь color picker занимает около 450 строк кода
О, курва!
Добавляя новые эффекты для системы частиц, обнаружил что мои курвы, то есть кривые, не совсем подходят. Дело в том, что кривая у меня - это график безье, где значению по X соответствует только одно значение по Y. То есть можно прямо в редакторе задать какую-то функцию от руки. Под это уже давно есть простенький редактор
Но для системы частиц зачастую нужна некая вариативность, "рандом" при обработке частицы. Чтобы каждая частица летела немного отлично от других, за счет чего достигается эффект. Одно значение по Y для такого не подходит, и первая мысль была - сделать две курвы! А значения между ними рандомно интерполировать. Но это не очень хорошо выглядело в редакторе. Отдельно редактировать две кривые совсем не удобно, а хочется чтобы они были вместе, да еще и промежуток между ними подкрашивался для визуализации, какое значение рандома может выпасть
В итоге пришел к решению, где у курвы в каждой ключевой точке, помимо основного значения, добавляется "разброс" - еще одно число. Далее, нужно было модифицировать код курвы и редакторов к ней к такой модификации.
Первый заход был основан на простой идее - к уже имеющейся просчитанной траектории безье добавлять интерполированный разброс между ключевыми кадрами. Это работает, но в ключевых точках получаются острые углы из-за линейной интерполяции. Добавив интерполяцию по синусоиде лучше не стало, поэтому пришлось использовать более сложный в расчетах подход
Второй подход был основан на идее, что строится два графика безье, от нижней и от верхней точек, заданных разбросом. Такой вариант сложнее в расчетах, фактически интерполяцию нужно считать два раза, в два раза больше кешировать точек для предрасчета. Но он дал именно тот результат, который ожидаешь, со сглаженными углами
Плюсы интеграции такого решения в общую курву движка в том, что это можно использовать где угодно, хоть в частицах, хоть в анимациях, хоть в пользовательском коде.
Добавляя новые эффекты для системы частиц, обнаружил что мои курвы, то есть кривые, не совсем подходят. Дело в том, что кривая у меня - это график безье, где значению по X соответствует только одно значение по Y. То есть можно прямо в редакторе задать какую-то функцию от руки. Под это уже давно есть простенький редактор
Но для системы частиц зачастую нужна некая вариативность, "рандом" при обработке частицы. Чтобы каждая частица летела немного отлично от других, за счет чего достигается эффект. Одно значение по Y для такого не подходит, и первая мысль была - сделать две курвы! А значения между ними рандомно интерполировать. Но это не очень хорошо выглядело в редакторе. Отдельно редактировать две кривые совсем не удобно, а хочется чтобы они были вместе, да еще и промежуток между ними подкрашивался для визуализации, какое значение рандома может выпасть
В итоге пришел к решению, где у курвы в каждой ключевой точке, помимо основного значения, добавляется "разброс" - еще одно число. Далее, нужно было модифицировать код курвы и редакторов к ней к такой модификации.
Первый заход был основан на простой идее - к уже имеющейся просчитанной траектории безье добавлять интерполированный разброс между ключевыми кадрами. Это работает, но в ключевых точках получаются острые углы из-за линейной интерполяции. Добавив интерполяцию по синусоиде лучше не стало, поэтому пришлось использовать более сложный в расчетах подход
Второй подход был основан на идее, что строится два графика безье, от нижней и от верхней точек, заданных разбросом. Такой вариант сложнее в расчетах, фактически интерполяцию нужно считать два раза, в два раза больше кешировать точек для предрасчета. Но он дал именно тот результат, который ожидаешь, со сглаженными углами
Плюсы интеграции такого решения в общую курву движка в том, что это можно использовать где угодно, хоть в частицах, хоть в анимациях, хоть в пользовательском коде.
Пришло время написать про систему частиц. Сейчас она уже более-менее финальная и в ней есть все что я хотел:
- эмитирование частиц, собственно
- различные формы, откуда частицы рождаются
- спрайт-шиты для анимированных частиц
- prewarm, предварительный прогрев системы частиц
- пачка начальных параметров: скорость, вращение, направление, размер
- набор эффектов: гравитация, цвет/градиент, направление движения, вращение и тп
- запекание и перепроигрывание в редакторе. Можно отмотать симуляцию на определенный кадр, изменить параметры, и увидеть изменения
- контроль системы частиц из анимации
Вроде бы базовый список и все понятно, но улетело ппц куча времени на это все. Особенно куча времени улетела в тулзы вокруг всего этого: color picker, редактор градиента, рейнджи для курв и куча-куча фиксов-улучшалок базового редакторе.
Немного о концепции и как все устроено под капотом. Самое главное - система частиц довольно проста и атомарна. По сути это просто один единственный источник частиц, без всякого наследования, дочерних геометрий и типов частиц. Один источник, один рендер, один набор эффектов. Более сложные системы частиц делаются через комбинацию эмиттеров, через сцену и ее иерархию. Нужно несколько источников - делается несколько эмиттеров. Здесь подключается вся база сцены - иерархия, префабы, наследование и т.д. Поверх всего этого, и сцены в целом, существует "дирижирование" через анимации. То есть если нужно сделать какой-то сложный эффект, например в UI что-то движется/скрывается/показывается и при этом бахаются эффекты частиц - все это настраивается через анимацию и дочерние анимационные треки. Система частиц здесь является тем же анимационным треком: у нее есть время и апдейт. По сути это такая же анимация как и обычная скелетная, по крайней мере со стороны интерфейса.
- эмитирование частиц, собственно
- различные формы, откуда частицы рождаются
- спрайт-шиты для анимированных частиц
- prewarm, предварительный прогрев системы частиц
- пачка начальных параметров: скорость, вращение, направление, размер
- набор эффектов: гравитация, цвет/градиент, направление движения, вращение и тп
- запекание и перепроигрывание в редакторе. Можно отмотать симуляцию на определенный кадр, изменить параметры, и увидеть изменения
- контроль системы частиц из анимации
Вроде бы базовый список и все понятно, но улетело ппц куча времени на это все. Особенно куча времени улетела в тулзы вокруг всего этого: color picker, редактор градиента, рейнджи для курв и куча-куча фиксов-улучшалок базового редакторе.
Немного о концепции и как все устроено под капотом. Самое главное - система частиц довольно проста и атомарна. По сути это просто один единственный источник частиц, без всякого наследования, дочерних геометрий и типов частиц. Один источник, один рендер, один набор эффектов. Более сложные системы частиц делаются через комбинацию эмиттеров, через сцену и ее иерархию. Нужно несколько источников - делается несколько эмиттеров. Здесь подключается вся база сцены - иерархия, префабы, наследование и т.д. Поверх всего этого, и сцены в целом, существует "дирижирование" через анимации. То есть если нужно сделать какой-то сложный эффект, например в UI что-то движется/скрывается/показывается и при этом бахаются эффекты частиц - все это настраивается через анимацию и дочерние анимационные треки. Система частиц здесь является тем же анимационным треком: у нее есть время и апдейт. По сути это такая же анимация как и обычная скелетная, по крайней мере со стороны интерфейса.
GitHub
o2/Framework/Sources/o2/Render/Particles/ParticlesEmitter.h at master · zenkovich/o2
2D Game Engine with visual WYSIWYG editor and JS scripting - zenkovich/o2
Окей, что там под капотом, собственно. Здесь я попытался реализовать какое-то приближение DOD, ибо системы частиц как правило очень тяжелые на апдейт.
Минимальный примитив - это частица. Она хранит в себе основные параметры, необходимые для апдейта. В частицы уже изначально зашито физичное поведение, а именно скорость и вращение. В некоторых системах частиц эти параметры идут отдельно, через эффекты. Но они настолько часто используются, что мне показалось нет смысла их выносить.
Минимальный примитив - это частица. Она хранит в себе основные параметры, необходимые для апдейта. В частицы уже изначально зашито физичное поведение, а именно скорость и вращение. В некоторых системах частиц эти параметры идут отдельно, через эффекты. Но они настолько часто используются, что мне показалось нет смысла их выносить.
Все частицы сложены в линейный массив, с заранее зарезервированным количеством частиц. Частицы переиспользуются, через флаг alive. После смерти индекс частицы помещается в пул мертвецов, откуда они восстают при необходимости эмитироваться снова.
У частиц могут быть дополнительные параметры, необходимые для эффектов. Например кеш для поиска фрейма кривых или начальные параметры для эффекта. Они складируются в линейных массивах внутри самих эффектов
На апдейте сначала прогоняется общий апдейт частиц, где они перемещаются в зависимости от скоростей. Далее, каждый эффект, друг за другом, применяют свое действие на массив частиц. Там тоже идет линейный проход по массивам. По идее должно быть все кеш-френдли
У частиц могут быть дополнительные параметры, необходимые для эффектов. Например кеш для поиска фрейма кривых или начальные параметры для эффекта. Они складируются в линейных массивах внутри самих эффектов
На апдейте сначала прогоняется общий апдейт частиц, где они перемещаются в зависимости от скоростей. Далее, каждый эффект, друг за другом, применяют свое действие на массив частиц. Там тоже идет линейный проход по массивам. По идее должно быть все кеш-френдли
Рендер частиц привязан к контейнерам, которые получают список частиц и рисуют в зависимости от своей логики: просто спрайт, спрайт-шит, или другие кастомные рендеры (напр. когда частица - это актор на сцене). Контейнер получает сообщения о рождении или смерти частицы, а на этапе отрисовки получает актуальный массив частиц. Внутри себя он строит геометрию и рендерит ее. Для обычных спрайтов все просто - частицы это квады. Для спрайт-шитов дополнительно рассчитывается UV из атласа для натягивания на те же квады. Меши геометрии предсозданы заранее, но обновляются каждый раз. Аллокаций нет, только расчет вершин и отправка в рендер
Эмитирование происходит в апдейте. Существует буффер времени, который копится пока не нужно будет сэмитировать частицу (или несколько). Работает просто - на апдейте прибавляется время кадра dt, рассчитывается через какие промежутки должны эмитироваться частицы, и этот буффер "разгребается", пока он больше чем время эмитирования частицы.
Частице устанавливаются стартовые параметры и применяется рандом, в заданных промежутках. Так же определяется стартовая точка - внутри или на границе геометрии эмиттера. Геометрии пока что простые - круг и прямоугольник. Но можно добавить и сплайн, хотя это будет довольно ресурсозатратным с точки зрения производительности.
При эмитировнии частицы оповещается контейнер (как писал выше), а так же все эффекты. Каждый эффект может сделать все что нужно с частицей.
Эмитирование происходит в апдейте. Существует буффер времени, который копится пока не нужно будет сэмитировать частицу (или несколько). Работает просто - на апдейте прибавляется время кадра dt, рассчитывается через какие промежутки должны эмитироваться частицы, и этот буффер "разгребается", пока он больше чем время эмитирования частицы.
Частице устанавливаются стартовые параметры и применяется рандом, в заданных промежутках. Так же определяется стартовая точка - внутри или на границе геометрии эмиттера. Геометрии пока что простые - круг и прямоугольник. Но можно добавить и сплайн, хотя это будет довольно ресурсозатратным с точки зрения производительности.
При эмитировнии частицы оповещается контейнер (как писал выше), а так же все эффекты. Каждый эффект может сделать все что нужно с частицей.
Сама система частиц привязана к трансформации актора, на которой она находится. Геометрия привязана к размеру актора так же. То есть нет отдельных параметров типа ширины и высоты, они просто берутся из актора, т.к. в движке он уже обладает этими параметрами.
Позиционирование частиц может быть относительным или мировым. В первом случае частицы живут относительно самого эмиттера, и при его движении они последуют за ним. При мировом позиционировании, частица эмитируется относительно эмиттера, но дальше существует сама по себе в мировых координатах
Позиционирование частиц может быть относительным или мировым. В первом случае частицы живут относительно самого эмиттера, и при его движении они последуют за ним. При мировом позиционировании, частица эмитируется относительно эмиттера, но дальше существует сама по себе в мировых координатах
Теперь о механизме запекания и "перемотке". Это используется только для редактора, т.к. при настройке частиц важно видеть все изменения и уметь привязывать частицы к таймлайну. например, если есть корневая анимация, которая управляет частицей, то при перемотке анимации курсором система частиц должна соответствовать именно тому заданному времени, в которой находится таймлайн. Иначе редактирование комплексных анимаций превратится в кашу, невозможно будет отмотаться к каким-то деталям, сделать идеальную синхронизацию. Ну и при настройке самой системы частиц бывает удобно отмотать ее к определенному моменту и отсмотреть в замедленном режиме
В игре, в обычном рантайме редко происходят такие вещи с анимациями, обычно они просто приоигрываются вперед. В таком случае достаточно простого апдейта частиц
В игре, в обычном рантайме редко происходят такие вещи с анимациями, обычно они просто приоигрываются вперед. В таком случае достаточно простого апдейта частиц
Под капотом это устроено через фиксацию зерна рандома эмитирования и запекание фреймов частиц. При перемотке или апдейте происходит запрос состояния частиц на кадр Х. Система проверяет, есть ли запеченный слепок. Если есть, то просто восстанавливает массив частиц из запеченого кадра Х и передает это все в рендер-контейнер. Система частиц принимает определенное положение, которое было на кадре Х
Если такого кадра нет, то система ищет последний запеченый кадр и восстанавливает его состояние. Здесь важно что восстанавливаются не только состояния частиц, но и состояние эмиттера - напр. буффер времени эмитирования частиц. Далее происходит симуляция апдейта частиц до необходимого кадра Х, с одновременным запеканием кадров. Симуляция происходит с фиксированным delta time 1/60 секунды.
Точно такое же запекание происходит и при обычном апдейте, когда мы запустили систему частиц и она просто симулируется. В конце работы системы частиц она содержит в себе слепки всех кадров. Время симулации, и системы частиц впринципе, определяется суммой времени эмитирования и жизни частицы. По прошествии этого времени гарантированно не останется ни одной живой частицы, дальнейшая симуляция и запекание не нужны
Если такого кадра нет, то система ищет последний запеченый кадр и восстанавливает его состояние. Здесь важно что восстанавливаются не только состояния частиц, но и состояние эмиттера - напр. буффер времени эмитирования частиц. Далее происходит симуляция апдейта частиц до необходимого кадра Х, с одновременным запеканием кадров. Симуляция происходит с фиксированным delta time 1/60 секунды.
Точно такое же запекание происходит и при обычном апдейте, когда мы запустили систему частиц и она просто симулируется. В конце работы системы частиц она содержит в себе слепки всех кадров. Время симулации, и системы частиц впринципе, определяется суммой времени эмитирования и жизни частицы. По прошествии этого времени гарантированно не останется ни одной живой частицы, дальнейшая симуляция и запекание не нужны
Самое крутое, что это позволяет поставить систему частиц на паузу и изменить параметры. Например, начальную скорость. Редактор пересимулирует систему частиц к этому моменту с учетом новых параметров. И так как зерно рандома зафиксировано, не будет дикого разброса частиц и все будет похоже на нативное изменение, что очень удобно в подстройке частиц
Для работы частиц в виде управляемого трека используется единый интерфейс движка IAnimation. Его суть проста - он умеет апдейтиться, стартовать и останавливаться, и обладает длительностью. Частицы обладают всем этим, и легко поддерживают этот интерфейс
Через него и работают дочерние треки анимаций. Просто делается специальный тип саб-трека, который содержит в себе ссылку на интерфейс IAnimation. Это может быть и другая такая же анимация. Все это позволяет "дирижировать" более простыми анимациями и эффектам, для создания чего-то более сложного, например кат-сцены или анимации UI
Для работы частиц в виде управляемого трека используется единый интерфейс движка IAnimation. Его суть проста - он умеет апдейтиться, стартовать и останавливаться, и обладает длительностью. Частицы обладают всем этим, и легко поддерживают этот интерфейс
Через него и работают дочерние треки анимаций. Просто делается специальный тип саб-трека, который содержит в себе ссылку на интерфейс IAnimation. Это может быть и другая такая же анимация. Все это позволяет "дирижировать" более простыми анимациями и эффектам, для создания чего-то более сложного, например кат-сцены или анимации UI
Пара слов о ткущей работе - прикрутил к движку spine. Это распространенная тулза и runtime для скелетных анимаций. Интересно то, что фактически в движке у меня уже есть аналогичная система скелетной анимации, в т.ч. с поддержкой скиннинга.
Но для стороннего использование наличие интеграции spine в движок все же важна, т.к. банально у многих уже настроены пайплайны под него. И, так как мой движок отчасти позиционируется как встраиваемый в другие движки и проекты, то там вероятно уже есть ассеты spine анимаций и их нужно уметь использовать.
Своя система, конечно же интегрирована в движок более нативною Она построена на иерархии сцены и она более плотно интегрирована со всеми подсистемами движка: те же анимации и частицы. Мой классический пример: в UI есть скелетная анимация персонажа с эффектами частиц в руке (фаерболл например). Если пытаться все это связать из сторонних инструментов, получается франкинштейн: spine и UI не связаны, придется делать какие-то мокапы и подложки. То же самое с частицами, их не получится увидеть в редакторе spine. В этом плане свое решение гораздо более выигрышно, ведь все находится в одной экосистеме и нативно состыкуется друг с другом.
Однако, в текущее время spine - это стандарт индустрии, и, с учетом вышеперечисленных доводов, без его поддержки не обойтись. Хотя, у меня есть идея попробовать сделать конвертер spine анимаций в мой нативный формат. Тогда получится взять плюсы с обоих подходов.
Тем не менее, я стараюсь сделать все чтобы тот же сторонний spine как можно лучше был интегрирован в экосистему движка. Его анимации для движка выглядят как реализация интерфейса IAnimation, соответственно их можно использовать как и нативные анимации движка - делать саб-треки, использовать из кода и т.п.
Так же сейчас делаю аналог Animator'а из Unity3D - граф анимационных состояний. Он сможет работать как с нативными анимациями, так и со spine.
Но для стороннего использование наличие интеграции spine в движок все же важна, т.к. банально у многих уже настроены пайплайны под него. И, так как мой движок отчасти позиционируется как встраиваемый в другие движки и проекты, то там вероятно уже есть ассеты spine анимаций и их нужно уметь использовать.
Своя система, конечно же интегрирована в движок более нативною Она построена на иерархии сцены и она более плотно интегрирована со всеми подсистемами движка: те же анимации и частицы. Мой классический пример: в UI есть скелетная анимация персонажа с эффектами частиц в руке (фаерболл например). Если пытаться все это связать из сторонних инструментов, получается франкинштейн: spine и UI не связаны, придется делать какие-то мокапы и подложки. То же самое с частицами, их не получится увидеть в редакторе spine. В этом плане свое решение гораздо более выигрышно, ведь все находится в одной экосистеме и нативно состыкуется друг с другом.
Однако, в текущее время spine - это стандарт индустрии, и, с учетом вышеперечисленных доводов, без его поддержки не обойтись. Хотя, у меня есть идея попробовать сделать конвертер spine анимаций в мой нативный формат. Тогда получится взять плюсы с обоих подходов.
Тем не менее, я стараюсь сделать все чтобы тот же сторонний spine как можно лучше был интегрирован в экосистему движка. Его анимации для движка выглядят как реализация интерфейса IAnimation, соответственно их можно использовать как и нативные анимации движка - делать саб-треки, использовать из кода и т.п.
Так же сейчас делаю аналог Animator'а из Unity3D - граф анимационных состояний. Он сможет работать как с нативными анимациями, так и со spine.
This media is not supported in your browser
VIEW IN TELEGRAM
Немного красоты 🤩
анимация переключения стейтов графа анимаций в редакторе. Синенькое - запланировано, красное - пройдено
анимация переключения стейтов графа анимаций в редакторе. Синенькое - запланировано, красное - пройдено
Самое важное качество UID'ов заключается в их уникальности. Случайно сгенерированный 128-битный UID практически гарантирует абсолютную уникальность. Конечно, теоретически повтор возможен, но вероятность этого настолько ничтожна, что на практике, даже если генерировать по несколько идентификаторов в секунду, вы не столкнётесь с таким событием на протяжении всей своей жизни.
Дополнительно некоторые «взрослые» реализации UID'ов включают в себя временную метку (таймстемп) генерации, что ещё сильнее снижает вероятность дублирования. Также типичная реализация UID может включать информацию о версии UID (тип генерации: рандомный, с таймстемпом или на основе MAC-адреса), данные о машине или устройстве (например, MAC-адрес, идентификатор узла или процессора) и служебные или зарезервированные биты для совместимости и будущих расширений.
Но зачем вообще использовать UID'ы? Ответ прост: они незаменимы для идентификации уникальных сущностей. Например, в разработке игр и движков часто возникает необходимость чётко различать различные игровые ассеты.
Кажется, можно просто использовать строковый идентификатор на основе имени ассета. Такой подход удобен, но быстро проявляется проблема повторяющихся названий. Например, часто возникает необходимость назвать два разных объекта одинаково, скажем "door". Тогда можно было бы использовать префиксы и суффиксы (например, "door_left", "door_right") или включать в идентификатор путь по папкам и подпапкам (например, "rooms/left/door", "dooms/right/door").
Однако, если возникает необходимость переместить ассет в другую папку или переименовать его, строковый идентификатор становится неактуальным. Вместо строки можно использовать уникальный идентификатор или индекс. Например, последовательный счётчик, увеличивающийся с каждым новым ассетом, обеспечивает простой и понятный способ генерации идентификаторов. Но если ассеты генерируются независимо на нескольких компьютерах или в разных точках проекта, счётчик не будет синхронизирован, и повторения идентификаторов гарантированы.
Здесь идеально подходят UID'ы. Их уникальность достигается за счёт случайной генерации и большой разрядности, что исключает повторения даже при параллельной генерации.
Внутри же других ассетов (префабов, конфигов, сцен и т.д.) ссылки могут быть сохранены в виде UID'ов. Пользователям не важно, в каком виде хранятся эти ссылки, главное — удобство и понятность названий в редакторе.
Ассеты — не единственная область применения UID'ов. Они отлично подходят для идентификации любых уникальных объектов, таких как ноды на сцене, ключи анимаций и многие другие.
Дополнительно некоторые «взрослые» реализации UID'ов включают в себя временную метку (таймстемп) генерации, что ещё сильнее снижает вероятность дублирования. Также типичная реализация UID может включать информацию о версии UID (тип генерации: рандомный, с таймстемпом или на основе MAC-адреса), данные о машине или устройстве (например, MAC-адрес, идентификатор узла или процессора) и служебные или зарезервированные биты для совместимости и будущих расширений.
Но зачем вообще использовать UID'ы? Ответ прост: они незаменимы для идентификации уникальных сущностей. Например, в разработке игр и движков часто возникает необходимость чётко различать различные игровые ассеты.
Кажется, можно просто использовать строковый идентификатор на основе имени ассета. Такой подход удобен, но быстро проявляется проблема повторяющихся названий. Например, часто возникает необходимость назвать два разных объекта одинаково, скажем "door". Тогда можно было бы использовать префиксы и суффиксы (например, "door_left", "door_right") или включать в идентификатор путь по папкам и подпапкам (например, "rooms/left/door", "dooms/right/door").
Однако, если возникает необходимость переместить ассет в другую папку или переименовать его, строковый идентификатор становится неактуальным. Вместо строки можно использовать уникальный идентификатор или индекс. Например, последовательный счётчик, увеличивающийся с каждым новым ассетом, обеспечивает простой и понятный способ генерации идентификаторов. Но если ассеты генерируются независимо на нескольких компьютерах или в разных точках проекта, счётчик не будет синхронизирован, и повторения идентификаторов гарантированы.
Здесь идеально подходят UID'ы. Их уникальность достигается за счёт случайной генерации и большой разрядности, что исключает повторения даже при параллельной генерации.
Внутри же других ассетов (префабов, конфигов, сцен и т.д.) ссылки могут быть сохранены в виде UID'ов. Пользователям не важно, в каком виде хранятся эти ссылки, главное — удобство и понятность названий в редакторе.
Ассеты — не единственная область применения UID'ов. Они отлично подходят для идентификации любых уникальных объектов, таких как ноды на сцене, ключи анимаций и многие другие.