На выходных прикрутил элементарный CI, который хочется развить до полноценной проверки всех интересующих платформ. Пока что завел MSVS билд под windows, тк на текущий момент только он живой.
Попробовал пару сервисов appveyor и github actions. Оба весьма похожи, но гитхаб нативно интегрирован в гитхаб, поэтому в итоге выбрал его. Теперь в репозитории o2 красуется бейджик о статусе сборки. Теперь хочу восстановить linux платформу и так же загнать ее в CI.
Попробовал пару сервисов appveyor и github actions. Оба весьма похожи, но гитхаб нативно интегрирован в гитхаб, поэтому в итоге выбрал его. Теперь в репозитории o2 красуется бейджик о статусе сборки. Теперь хочу восстановить linux платформу и так же загнать ее в CI.
Немного об удобстве CI через github actions/appveyor. У них похожие концепции - сборка настраивается через конфигурационный файл.
Я всегда был сторонником графического интерфейса, а не текстового. Поэтому не люблю всякие терминалы, CMAKE и подобные вещи. В github action то же самое, там все настройки в текстовом файле с определенным синтаксисом. Сходу навязывается некий синтаксис, то есть правила, которые необходимо соблюдать. Нарушишь эти правила - беда. Это плохо признак UX, ведь это прямое нарушение принципа защиты от ошибки. Все мы люди, можем опечататься, что-то забыть, что поломает систему. Гораздо лучше интерфейсы, где возможность ошибки прост исключена.
Так же текстовый интерфейсы по умолчанию не интуитивны. В GUI можно увидеть тултипы, выпадающие списки, имена полей, то есть можно методом тыка разобраться в функциональности. В текстовом варианте у тебя максимум есть команда —help, которая вываливает тебе тонну текста практически без форматирования.
С github actions у меня сразу такая проблема и возникла, при выборе типа агента на котором запускать сборку. По умолчанию там Linux, мне нужно было выбрать windows.... и я просто не знал что там написать. Пришлось лезть в документацию, специально искать нужную информацию, то есть делать лишние действия. Если бы это был GUI, то у меня был бы выпадающий список с возможными вариантами агентов, и я бы просто выбрал за 5 секунд вместо гугления 5 минут. Но в итоге скрипт мне сгенерировал Chat GPT :)
Мне кажется так происходит из-за проф деформации программистов. Так уж вышло что мы 95% времени работаем с текстом. А так же со сложными вещами. Поэтому идем по накатанной: запихиваем все в текст и не стараемся упростить. Делать GUI как правило лениво, это гораздо дольше чем распарсить аргументы командной строки или текстовый файл.
Но на мой взгляд сейчас со всякими GPT это уже не проблема, достаточно грубо описать функциональность и получить почти работающий GUI код. Даже с генерацией python-gui хорошо справляется ChatGPT, если стравить ему текст из команды —help
Я всегда был сторонником графического интерфейса, а не текстового. Поэтому не люблю всякие терминалы, CMAKE и подобные вещи. В github action то же самое, там все настройки в текстовом файле с определенным синтаксисом. Сходу навязывается некий синтаксис, то есть правила, которые необходимо соблюдать. Нарушишь эти правила - беда. Это плохо признак UX, ведь это прямое нарушение принципа защиты от ошибки. Все мы люди, можем опечататься, что-то забыть, что поломает систему. Гораздо лучше интерфейсы, где возможность ошибки прост исключена.
Так же текстовый интерфейсы по умолчанию не интуитивны. В GUI можно увидеть тултипы, выпадающие списки, имена полей, то есть можно методом тыка разобраться в функциональности. В текстовом варианте у тебя максимум есть команда —help, которая вываливает тебе тонну текста практически без форматирования.
С github actions у меня сразу такая проблема и возникла, при выборе типа агента на котором запускать сборку. По умолчанию там Linux, мне нужно было выбрать windows.... и я просто не знал что там написать. Пришлось лезть в документацию, специально искать нужную информацию, то есть делать лишние действия. Если бы это был GUI, то у меня был бы выпадающий список с возможными вариантами агентов, и я бы просто выбрал за 5 секунд вместо гугления 5 минут. Но в итоге скрипт мне сгенерировал Chat GPT :)
Мне кажется так происходит из-за проф деформации программистов. Так уж вышло что мы 95% времени работаем с текстом. А так же со сложными вещами. Поэтому идем по накатанной: запихиваем все в текст и не стараемся упростить. Делать GUI как правило лениво, это гораздо дольше чем распарсить аргументы командной строки или текстовый файл.
Но на мой взгляд сейчас со всякими GPT это уже не проблема, достаточно грубо описать функциональность и получить почти работающий GUI код. Даже с генерацией python-gui хорошо справляется ChatGPT, если стравить ему текст из команды —help
Апдейт! Завел linux, теперь можно и там запускать игру или редактор. Особо ничего нового со стороны кода не было сделано, т.к. linux был поддержан еще прошлым летом для ребят из гемблинга.
Но пришлось поправить некоторые ошибки компиляции после перехода на умные указатели. Одна из них потребовалась из-за разницы работы компиляторов. При создании ссылки, у типа проверяется наличие метода
Решилось все небольшим рефакторингом. Метод конструирования ссылки был friend'ом классов, которые имели приватный
Сейчас еще остаются проблемы на linux платформе: есть креш на закрытии редактора, не поддерживается сохранение размеров окна между запусками, не меняется тип курсора и другие небольшие недоработки. Все это буду делать планомерно, цель завести Linux полноценной платформой, где запускается редактор со всем нужным функционалом.
Но пришлось поправить некоторые ошибки компиляции после перехода на умные указатели. Одна из них потребовалась из-за разницы работы компиляторов. При создании ссылки, у типа проверяется наличие метода
PostRefConstruct
. Если такой есть, он вызывается. Проблема была в том, что этот метода приватный, а type trait, Определяющий наличие типа - в глобальном неймспейсе. Компилятору MSVS все равно, и он успешно компилировал код, говоря что метод есть. На gcc все строже, и т.к. метод приватный, то взять указатель на функцию невозможно, а значит type trait возвращал false при проверке наличия метода. Из-за этого он не вызывался и где-то там далее поведение программы портилось (а точнее в системе ассетов).Решилось все небольшим рефакторингом. Метод конструирования ссылки был friend'ом классов, которые имели приватный
PostRefConstruct
. По этому же принципу вся инфраструктура по созданию ссылки, в т.ч. type trait, были вынесены в отдельный класс, который уже и дружился с типами с PostRefConstruct
.Сейчас еще остаются проблемы на linux платформе: есть креш на закрытии редактора, не поддерживается сохранение размеров окна между запусками, не меняется тип курсора и другие небольшие недоработки. Все это буду делать планомерно, цель завести Linux полноценной платформой, где запускается редактор со всем нужным функционалом.
О, и конечно же, завел CI на github чтобы проверять собираемость linux. Есть что-то в этих бейджиках на странице проекта 😃
image_2024-08-18_12-17-55.png
14.9 KB
когда перемудрил с кодировкой...
Небольшой апдейт! Сейчас занимаюсь системой частиц, о ней будет попозже. А пока - 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 секунды.
Точно такое же запекание происходит и при обычном апдейте, когда мы запустили систему частиц и она просто симулируется. В конце работы системы частиц она содержит в себе слепки всех кадров. Время симулации, и системы частиц впринципе, определяется суммой времени эмитирования и жизни частицы. По прошествии этого времени гарантированно не останется ни одной живой частицы, дальнейшая симуляция и запекание не нужны