This media is not supported in your browser
VIEW IN TELEGRAM
На текущем этапе разработки я планировал закругляться, поскольку минимальный набор функций готов и пора пробовать редактор в бою. Но обнаружилась еще одна серьезная проблема – отсутствие отмены изменений.
Добавил в редактор сохранение истории операций с возможностью отмены на Ctrl+Z. Текста получилось многовато, поэтому о деталях реализации можно прочитать в telegra.ph статье.
Добавил в редактор сохранение истории операций с возможностью отмены на Ctrl+Z. Текста получилось многовато, поэтому о деталях реализации можно прочитать в telegra.ph статье.
❤7🔥6👍4❤🔥2👏1
Заключительная часть воркшопа по написанию платформера.
Добавили на уровень противников и написали боевую систему.
https://youtu.be/_fkUeX2QDtQ
Добавили на уровень противников и написали боевую систему.
https://youtu.be/_fkUeX2QDtQ
❤8👍3🔥3❤🔥1👏1
Во время проведения воркшопов всплыл вопрос: можно ли как-то уменьшить количество констант в коде проекта?
В системах, для получения какого-либо компонента из объекта, нужно указать его имя в формате строки. Делать это приходится очень часто, поэтому со временем проект обрастает кучей строковых констант.
Все становится еще хуже с появлением TypeScript – извлеченный компонент нужно каждый раз кастить к нужному типу, чтобы что-то с ним сделать.
Обе эти проблемы я решил переработав методы получения и установки компонентов: вместо передачи имени, теперь можно передать сам класс и использовать имя класса в качестве ключа. Тип возвращаемого значения при этом можно вывести через дженерик.
Единственная проблема – в результате сборки все переменные и имена классов схлопываются в однобуквенные сокращения, а JSON проекта ссылается на компоненты через оригинальные имена, в результате чего связь теряется.
Поэтому я добавил необходимость указывать специальные статические строковые поля с указанием имени компонента или системы. Как displayName у компонентов в React. А движок уже под капотом получив на вход класс, достанет имя через это статическое поле.
В системах, для получения какого-либо компонента из объекта, нужно указать его имя в формате строки. Делать это приходится очень часто, поэтому со временем проект обрастает кучей строковых констант.
Все становится еще хуже с появлением TypeScript – извлеченный компонент нужно каждый раз кастить к нужному типу, чтобы что-то с ним сделать.
Обе эти проблемы я решил переработав методы получения и установки компонентов: вместо передачи имени, теперь можно передать сам класс и использовать имя класса в качестве ключа. Тип возвращаемого значения при этом можно вывести через дженерик.
Единственная проблема – в результате сборки все переменные и имена классов схлопываются в однобуквенные сокращения, а JSON проекта ссылается на компоненты через оригинальные имена, в результате чего связь теряется.
Поэтому я добавил необходимость указывать специальные статические строковые поля с указанием имени компонента или системы. Как displayName у компонентов в React. А движок уже под капотом получив на вход класс, достанет имя через это статическое поле.
🔥5👍3🙏3👏1💯1
This media is not supported in your browser
VIEW IN TELEGRAM
Обработка ввода – это вообще первое, что я написал в движке и сейчас, когда я смотрю на код написанный 5 лет назад, некоторые моменты хочется поменять. Для начала я отказался от своих кастомных именований событий в пользу того как они есть в нативном JavaScript. Может “RIGHT_CLICK” выглядит и понятнее чем “contextmenu”, но для Frontend разработчиков скорее всего второй вариант будет привычнее. Далее вместо привязки к числовому коду клавиши “keyCode”, который уже давно deprecated, я переехал на более актуальный строковый код клавиши. Тем самым избавился от необходимости держать в проекте маппинг с числа на символ. Это нужно было чтобы при настройке управления ты мог указать именно символ клавиши, а не ее числовой код, о значении которых никто не знает.
Вместе с отказом от маппинга клавиш понадобилось доработать виджет KeyboardControl компонента в редакторе. Раньше я брал этот маппинг и пихал в селект, но его больше нет да и селект на все возможные клавиши это очень неудобно. Поэтому я реализовал специальное поле, которое слушает нажатия клавиш и вставляет в поле код нажатой клавиши. Чтобы не ломать навигацию с клавиатуры, по умолчанию поле находится в режиме только для чтения и по нажатию на кнопку справа или на Enter, включается режим смены клавиши.
Вместе с отказом от маппинга клавиш понадобилось доработать виджет KeyboardControl компонента в редакторе. Раньше я брал этот маппинг и пихал в селект, но его больше нет да и селект на все возможные клавиши это очень неудобно. Поэтому я реализовал специальное поле, которое слушает нажатия клавиш и вставляет в поле код нажатой клавиши. Чтобы не ломать навигацию с клавиатуры, по умолчанию поле находится в режиме только для чтения и по нажатию на кнопку справа или на Enter, включается режим смены клавиши.
👍6❤2🔥2
С конца прошлого года я копаюсь в движке и переделываю механизмы, которые оказались неудобными. Один из самых важных инструментов – это работа с событиями и обмен информацией между системами. Текста получилось много, поэтому приглашаю к прочтению статьи.
https://telegra.ph/Pro-rabotu-s-sobytiyami-02-18
https://telegra.ph/Pro-rabotu-s-sobytiyami-02-18
Telegraph
Про работу с событиями
Шесть лет назад когда я вел летний курс по разработке игры на плюсах, один студент спросил как правильно передавать события от одной системы к другой. Я тогда замялся и не нашел, что ответить потому что сам не знал. Системы в игровом цикле запускаются по…
❤8🔥4👍3👏1
Всплытие событий
Для чего вообще может понадобиться всплытие событий в движке?
Представим, что есть система, которая обрабатывает различные эффекты вроде "Нанести персонажу X урона", "Вылечить игрока на Y хп" или "Отравить врага на Z секунд". Событие эффекта может иметь единый тип, но разный набор параметров и тогда системе достаточно следить только за этим событием и подбирать правильную стратегию реагирования. Но как узнать о том, что к какому-то объекту применили эффект? Без всплытия придется перебрать вообще все объекты сцены и на каждый подписаться. При этом нужно не забывать, что объекты могут исчезать и появляться, поэтому нужно следить и за этим тоже. На выходе имеем больше boilerplate кода и кучу одинаковых обработчиков на каждом объекте. В то же время, если бы события умели всплывать наверх, то одной подписки на корневой объект (например сцену) было бы достаточно чтобы следить за всеми сразу.
Само по себе всплытие реализовать достаточно просто. Если у объекта есть доступ к родителю и списку его подписчиков, то поднимаясь наверх можно по очереди уведомить о событии всех заинтересованных.
Связь между объектами у меня уже есть, но для того чтобы событие поднималось аж до сцены, должна существовать родительская связь и со сценой тоже.
Все бы ничего, но апи сцены по добавлению объектов совсем не подходило под текущие требования. К примеру, все объекты лежат в сцене плоским списком в независимости от их связей между собой. Когда-то это было просто и удобно, но сейчас все хочется переделать и привести к одному виду.
Чтобы у сцены и объекта был единый интерфейс по работе с родителями и детьми я добавил немножечко наследования. В основе лежит EventTarget класс c возможностями подписки и генерации событий, затем идет Entity – базовый класс, поддерживающий методы вроде appendChild(), removeChild(), getEntityById() и так далее. Наконец, от него уже наследуются Scene и Actor (переименовал GameObject в Actor потому что надоело писать много букв да и приставка Game ни туда ни сюда).
Код движка стал немного аккуратнее и у меня даже получилось щегольнуть знаниями, полученными после нарешивая задач на литкоде. Для поиска объекта в дереве по идентификатору я использовал BFS метод с очередью на односвязном списке.
Для чего вообще может понадобиться всплытие событий в движке?
Представим, что есть система, которая обрабатывает различные эффекты вроде "Нанести персонажу X урона", "Вылечить игрока на Y хп" или "Отравить врага на Z секунд". Событие эффекта может иметь единый тип, но разный набор параметров и тогда системе достаточно следить только за этим событием и подбирать правильную стратегию реагирования. Но как узнать о том, что к какому-то объекту применили эффект? Без всплытия придется перебрать вообще все объекты сцены и на каждый подписаться. При этом нужно не забывать, что объекты могут исчезать и появляться, поэтому нужно следить и за этим тоже. На выходе имеем больше boilerplate кода и кучу одинаковых обработчиков на каждом объекте. В то же время, если бы события умели всплывать наверх, то одной подписки на корневой объект (например сцену) было бы достаточно чтобы следить за всеми сразу.
Само по себе всплытие реализовать достаточно просто. Если у объекта есть доступ к родителю и списку его подписчиков, то поднимаясь наверх можно по очереди уведомить о событии всех заинтересованных.
const event = {
type,
target: this,
currentTarget: this,
};
let target = this;
while (target !== null) {
event.currentTarget = target;
const listeners = target.getListeners(event.type);
listeners?.forEach((listener) => listener(event));
target = target.parent;
}
Связь между объектами у меня уже есть, но для того чтобы событие поднималось аж до сцены, должна существовать родительская связь и со сценой тоже.
Все бы ничего, но апи сцены по добавлению объектов совсем не подходило под текущие требования. К примеру, все объекты лежат в сцене плоским списком в независимости от их связей между собой. Когда-то это было просто и удобно, но сейчас все хочется переделать и привести к одному виду.
Чтобы у сцены и объекта был единый интерфейс по работе с родителями и детьми я добавил немножечко наследования. В основе лежит EventTarget класс c возможностями подписки и генерации событий, затем идет Entity – базовый класс, поддерживающий методы вроде appendChild(), removeChild(), getEntityById() и так далее. Наконец, от него уже наследуются Scene и Actor (переименовал GameObject в Actor потому что надоело писать много букв да и приставка Game ни туда ни сюда).
Код движка стал немного аккуратнее и у меня даже получилось щегольнуть знаниями, полученными после нарешивая задач на литкоде. Для поиска объекта в дереве по идентификатору я использовал BFS метод с очередью на односвязном списке.
Wikipedia
Breadth-first search
algorithm for searching the nodes of a graph in order by their hop count from a starting node
🔥5👍4❤2
Очередь событий
Со всплытием разобрались, теперь посмотрим какие еще улучшения можно добавить. Какую бы книгу или статью по проектированию игровых движков я не брал, всегда есть упоминание того, что события можно обрабатывать синхронно или же откладывать выполнение формируя некую очередь. Каждый подход имеет право на существование, у каждого есть свои плюсы и минусы.
В отличии от синхронной рассылки событий, очередь более предсказуема. Системы спокойно заканчивают свое выполнение и только потом накопившиеся события рассылаются всем заинтересованным. Но это так же может быть и проблемой, когда к примеру, система слушает события об удалении объектов и запоздалая реакция приведет к нежелательным сайд-эффектам вроде коллизии с тем чего уже нет.
Для начала реализовал очередь без усложнений. Никаких лимитов или приоритезации. Несмотря на то, что каждый объект сам является источником событий, очередь одна на всех. В JavaScript синглтон реализуется просто – из модуля экспортируется не сам класс очереди, а уже созданный экземпляр, который используется там где нужно.
Полностью перейти на использование очереди у меня не получилось. Как я говорил выше, в некоторых случаях когда дело касается добавления или удаления объектов из сцены, отложенная реакция приводила к ошибкам и попыткам обратиться к уже уничтоженным данным. Поэтому наряду с методом dispatchEvent() для отправки события в очередь, я оставил dispatchEventImmediately() для немедленного оповещения. Второй используется только для уведомления об изменениях на сцене и вообще служит только для исключительных кейсов, когда срочность очень важно и без нее никак.
Со всплытием разобрались, теперь посмотрим какие еще улучшения можно добавить. Какую бы книгу или статью по проектированию игровых движков я не брал, всегда есть упоминание того, что события можно обрабатывать синхронно или же откладывать выполнение формируя некую очередь. Каждый подход имеет право на существование, у каждого есть свои плюсы и минусы.
В отличии от синхронной рассылки событий, очередь более предсказуема. Системы спокойно заканчивают свое выполнение и только потом накопившиеся события рассылаются всем заинтересованным. Но это так же может быть и проблемой, когда к примеру, система слушает события об удалении объектов и запоздалая реакция приведет к нежелательным сайд-эффектам вроде коллизии с тем чего уже нет.
Для начала реализовал очередь без усложнений. Никаких лимитов или приоритезации. Несмотря на то, что каждый объект сам является источником событий, очередь одна на всех. В JavaScript синглтон реализуется просто – из модуля экспортируется не сам класс очереди, а уже созданный экземпляр, который используется там где нужно.
Полностью перейти на использование очереди у меня не получилось. Как я говорил выше, в некоторых случаях когда дело касается добавления или удаления объектов из сцены, отложенная реакция приводила к ошибкам и попыткам обратиться к уже уничтоженным данным. Поэтому наряду с методом dispatchEvent() для отправки события в очередь, я оставил dispatchEventImmediately() для немедленного оповещения. Второй используется только для уведомления об изменениях на сцене и вообще служит только для исключительных кейсов, когда срочность очень важно и без нее никак.
👍6❤5🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
После большой задачи с событиями хочется немного откиснуть и сделать что-то несложное. Тем более через две недели Ludum Dare на котором я буду писать игру.
При работе с редактором бывают ситуации, когда он реагирует на изменение в коде проекта и рисует уведомление с просьбой его перезагрузить.
Как оказалось на практике это происходит слишком часто и алерт надоедает.
Поэтому я заменил его на небольшую кнопку перезагрузки в футере, которая появляется когда это требуется.
Ну и раз я выделил в окне редактора место под футер, то можно еще и текущие координаты курсора в него затолкать. Может быть полезно при точном позиционировании объектов или чтобы сориентироваться где ты находишься. На перерисовку координат воткнул небольшой троттлинг чтобы хоть как-то регулировать частоту. Функцию троттлинга я писал во время подготовки к собеседованиям, поэтому чтобы зря не пропадало, затащил к себе в проект.
При работе с редактором бывают ситуации, когда он реагирует на изменение в коде проекта и рисует уведомление с просьбой его перезагрузить.
Как оказалось на практике это происходит слишком часто и алерт надоедает.
Поэтому я заменил его на небольшую кнопку перезагрузки в футере, которая появляется когда это требуется.
Ну и раз я выделил в окне редактора место под футер, то можно еще и текущие координаты курсора в него затолкать. Может быть полезно при точном позиционировании объектов или чтобы сориентироваться где ты находишься. На перерисовку координат воткнул небольшой троттлинг чтобы хоть как-то регулировать частоту. Функцию троттлинга я писал во время подготовки к собеседованиям, поэтому чтобы зря не пропадало, затащил к себе в проект.
🔥7👍6👏3❤1
Немного подготовился к предстоящему людуму: создал репозиторий, настроил окружение и собрал hello world игры, где один квадрат падает на другой. Сразу воткнул React чтобы сделать экран меню и нарисовать интерфейс.
А еще поставил новый пиксель арт редактор на планшет — Pixquare. Aseprite все никак не появится на iPad зато начали появляться подражатели. Это уже второй, из тех, что я нашел.
Потестил Pixquare. На пару с женой нарисовали нашего пса. Вроде даже получилось похоже на собаку если сильно взгляд не отдалять.
А еще поставил новый пиксель арт редактор на планшет — Pixquare. Aseprite все никак не появится на iPad зато начали появляться подражатели. Это уже второй, из тех, что я нашел.
Потестил Pixquare. На пару с женой нарисовали нашего пса. Вроде даже получилось похоже на собаку если сильно взгляд не отдалять.
❤11🔥7👍4🥰2
Собрались с женой писать игру на людум. Тема в этот раз: Summoning.
Брейнштормили идеи пока гуляли с собакой, надумали игру про молодого некроманта. Сам он драться не умеет, зато за него это делают восставшие мертвецы.
Планируем сделать платформер с непрямой боевой системой когда за тебя дерутся поднятые NPC.
При убиении очередного противника опционально можно отпустить текущего подопечного и призвать нового (пехотинца заменить на лучника к примеру).
На все про все у нас есть только два дня. Посмотрим, что получится.
PS: картинка для питча идеи Омскому филиалу людума :)
Брейнштормили идеи пока гуляли с собакой, надумали игру про молодого некроманта. Сам он драться не умеет, зато за него это делают восставшие мертвецы.
Планируем сделать платформер с непрямой боевой системой когда за тебя дерутся поднятые NPC.
При убиении очередного противника опционально можно отпустить текущего подопечного и призвать нового (пехотинца заменить на лучника к примеру).
На все про все у нас есть только два дня. Посмотрим, что получится.
PS: картинка для питча идеи Омскому филиалу людума :)
👍13🔥5❤4
This media is not supported in your browser
VIEW IN TELEGRAM
Закончили. За два дня мы провели за ноутами по меньшей мере часов 20 с перерывами на покушать и погулять пса.
Я писал код и собирал уровень в редакторе, а Таня нарисовала практически весь арт, кроме персонажей. Их рисовал я.
Старался писать быстро как мог, где можно было наговнокодить – говнокодил, где скопипастить – копипастил.
Не успел прикрутить управление с мобильных телефонов, добавлю чуть позже. Поэтому пока только десктоп.
Очень устали, но получилось симпатично, к тому же это первый людум, где я использовал редактор.
Кажется игра получилась сложноватой. Мне пришлось немного потренироваться прежде чем я научился справляться с лучниками.
Поиграть можно тут: https://michailremmele.github.io/ludum-dare-55/
Я писал код и собирал уровень в редакторе, а Таня нарисовала практически весь арт, кроме персонажей. Их рисовал я.
Старался писать быстро как мог, где можно было наговнокодить – говнокодил, где скопипастить – копипастил.
Не успел прикрутить управление с мобильных телефонов, добавлю чуть позже. Поэтому пока только десктоп.
Очень устали, но получилось симпатично, к тому же это первый людум, где я использовал редактор.
Кажется игра получилась сложноватой. Мне пришлось немного потренироваться прежде чем я научился справляться с лучниками.
Поиграть можно тут: https://michailremmele.github.io/ludum-dare-55/
🔥17👍2👏2🏆1
This media is not supported in your browser
VIEW IN TELEGRAM
Прошла неделя с момента как мы закончили игру. Как оказалось дедлайн по публикации это только половина дела. Дальше идет оценка игр и определение топа по различным категориям (графика, звук, инновационнсть, фан и тд). Процесс занимает чуть больше двух недель.
Если ты хочешь чтобы твою игру заметили, поиграли в нее и оставили фидбек, то тебе придется приложить не меньше усилий чем при разработке. Начинается самый настоящий маркетинг на минималках.
Для того, чтобы игра попадалась на первых страницах сайта ldjam.com тебе нужно играть и оценивать чужие игры. Так ты будешь получать карму и твоя игра будет получать более высокий приоритет. До тех пор, пока кто-нибудь не напишет "Nice game!" и твоя игра снова не улетит в помойку.
На текущий момент мы с Таней оценили чуть больше 20-ти игр, по возможности старались писать развернуто о своих впечатлениях. Наша же игра набрала 20 оценок – это критическая отметка после которой по итогам голосования твоя игра попадет в общий рейтинг. Ну и комментариев под игрой нам тоже немного накидали.
Очень классное ощущение читать фидбек на свою игру, особенно если он детальный. Заряжает энергией на то чтобы работать дальше. В среднем большинство людей страдает от лучников, но оно и понятно.
А еще мы накатали большую статью о том как мы делали игру и с какими сложностями в процессе столкнулись. Опубликовали на сайте людума чтобы собрать классов и привлечь еще немного внимания к нашей игре.
Прочитать можно тут (на английском): ссылка
Ну и игру мы пока не забрасывали. Таня перерисовала некоторые элементы, а я поправил хитбоксы у персонажей чтобы игрок ловил меньше стрел в лицо и добавил мобильную версию с тач управлением.
Поскольку обновлять игру после дедлайна несолидно, я завел новый репозиторий, поэтому актуальная ссылка с игрой вот: ссылка
У странички есть манифест, поэтому игру можно сохранить как пва на домашний экран и играть на телефоне в фуллскрине.
Если ты хочешь чтобы твою игру заметили, поиграли в нее и оставили фидбек, то тебе придется приложить не меньше усилий чем при разработке. Начинается самый настоящий маркетинг на минималках.
Для того, чтобы игра попадалась на первых страницах сайта ldjam.com тебе нужно играть и оценивать чужие игры. Так ты будешь получать карму и твоя игра будет получать более высокий приоритет. До тех пор, пока кто-нибудь не напишет "Nice game!" и твоя игра снова не улетит в помойку.
На текущий момент мы с Таней оценили чуть больше 20-ти игр, по возможности старались писать развернуто о своих впечатлениях. Наша же игра набрала 20 оценок – это критическая отметка после которой по итогам голосования твоя игра попадет в общий рейтинг. Ну и комментариев под игрой нам тоже немного накидали.
Очень классное ощущение читать фидбек на свою игру, особенно если он детальный. Заряжает энергией на то чтобы работать дальше. В среднем большинство людей страдает от лучников, но оно и понятно.
А еще мы накатали большую статью о том как мы делали игру и с какими сложностями в процессе столкнулись. Опубликовали на сайте людума чтобы собрать классов и привлечь еще немного внимания к нашей игре.
Прочитать можно тут (на английском): ссылка
Ну и игру мы пока не забрасывали. Таня перерисовала некоторые элементы, а я поправил хитбоксы у персонажей чтобы игрок ловил меньше стрел в лицо и добавил мобильную версию с тач управлением.
Поскольку обновлять игру после дедлайна несолидно, я завел новый репозиторий, поэтому актуальная ссылка с игрой вот: ссылка
У странички есть манифест, поэтому игру можно сохранить как пва на домашний экран и играть на телефоне в фуллскрине.
🔥5🍾3❤1👍1
This media is not supported in your browser
VIEW IN TELEGRAM
После людума передо мной встал выбор, вернуться к доработке движка и погрязнуть в переписывании физики на пару месяцев или еще немного позаниматься игрой и развить механики. Я выбрал второй вариант. В конце концов я пишу движок чтобы делать игры.
Одна только проблема не давала мне покоя. Если персонаж прыгнет находясь под платформой, то он ударится об нее головой. Обычно, платформеры позволяют игроку запрыгивать на платформу снизу, то есть платформа вроде как и твердое тело на котором можно стоять, но в то же время пропускает сквозь себя объекты в определенном направлении.
Я пока плохо представляю как это сделать правильно и хочу поскорее продолжить писать игру, поэтому сделал специальную костыльную версию движка. В ней я навтыкал дополнительных условий в физическую систему, где я проверяю нужно ли как-то разрешать коллизию и раздвигать объекты или нет.
Теперь если объект с компонентом RigidBody обладает свойством isPlatform и возникает коллизия с другим объектом, вектор движения которого направлен вверх, то такая коллизия пропускается и будет игнорироваться до тех пор, пока объекты не разойдутся в стороны и возникнет событие CollisionLeave.
Напомню, что все обновления можно посмотреть на страничке игре.
Одна только проблема не давала мне покоя. Если персонаж прыгнет находясь под платформой, то он ударится об нее головой. Обычно, платформеры позволяют игроку запрыгивать на платформу снизу, то есть платформа вроде как и твердое тело на котором можно стоять, но в то же время пропускает сквозь себя объекты в определенном направлении.
Я пока плохо представляю как это сделать правильно и хочу поскорее продолжить писать игру, поэтому сделал специальную костыльную версию движка. В ней я навтыкал дополнительных условий в физическую систему, где я проверяю нужно ли как-то разрешать коллизию и раздвигать объекты или нет.
Теперь если объект с компонентом RigidBody обладает свойством isPlatform и возникает коллизия с другим объектом, вектор движения которого направлен вверх, то такая коллизия пропускается и будет игнорироваться до тех пор, пока объекты не разойдутся в стороны и возникнет событие CollisionLeave.
Напомню, что все обновления можно посмотреть на страничке игре.
👍7❤3🔥2
На днях закончился подсчет голосов на людуме и опубликовали результаты.
Всего в категории Jam (команда в несколько человек, три дня на разработку и отсутствие серьезных ограничений) было отправлено 1679 игр.
Мы заняли 435-е в общем зачете и 208-е за графику. Довольно далеко от верхушки, но вместо трех дней мы работали над игрой только два (понедельник рабочий) да и опыта у нас особого нет.
Это наша первая серьезная попытка засабмитить игру на сайте джема и получить оценку. Таня участвовала в первый раз, а для меня это вторая игра, которую удалось довести до рабочего состояния к концу мероприятия.
Так что, пойдет. Будем продолжать и посмотрим как пойдет прогресс.
Всего в категории Jam (команда в несколько человек, три дня на разработку и отсутствие серьезных ограничений) было отправлено 1679 игр.
Мы заняли 435-е в общем зачете и 208-е за графику. Довольно далеко от верхушки, но вместо трех дней мы работали над игрой только два (понедельник рабочий) да и опыта у нас особого нет.
Это наша первая серьезная попытка засабмитить игру на сайте джема и получить оценку. Таня участвовала в первый раз, а для меня это вторая игра, которую удалось довести до рабочего состояния к концу мероприятия.
Так что, пойдет. Будем продолжать и посмотрим как пойдет прогресс.
👍13🔥10❤6👏1🙏1
This media is not supported in your browser
VIEW IN TELEGRAM
Во время людума я хотел чтобы призраки умели самостоятельно добираться до врагов даже если те находятся не в прямой доступности, а где-то на платформах. Быстро это сделать не получилось, поэтому я добавил кнопку призыва приведений к игроку.
Сейчас времени у меня достаточно, поэтому я решил снова подумать, что с этим можно сделать.
Ранее мне доводилось слышать про A* алгоритм и построение графа перемещения, но я пока плохо представляю как все это делать поскольку в тему не погружался. Когда-нибудь я изучу этот вопрос, но пока что нужен способ проще.
Первым решением была способность телепортироваться к выбранной цели. Благодаря телепортации, приведение сможет без проблем дотянуться до врагов, где бы они не находились да и за игроком следовать будет проще.
Как я уже упомянул, на людуме сделать это не вышло. Причиной было непонимание как определять когда можно телепортироваться, а когда нет. Можно смотреть на расстояние по оси Y до цели и если оно выше какого-то минимального порога, то делать скачек. Но что если игрок находится в прыжке? Как определить висит ли он в воздухе или стоит на платформе?
В компоненте RigidBody, описывающем физические параметры объекта, хранятся вектора приложенных сил и вектор текущей скорости. Поскольку прыжок реализован через импульс, а бег через обычное смещение координат в зависимости от времени, то я решил смотреть на значение вектора скорости. Если вектор ненулевой, значит возможно цель висит в воздухе, поэтому лучше подождать.
Для эффектности я нарисовал анимированные клубы дыма, которые появляются в точках откуда и куда призрак переместился.
Ну и напоследок, мне также не нравилось, что всех врагов нужно провоцировать своим лицом и терпеть пока не добегут союзники, поэтому призракам с копьями я дал возможность телепортации к врагу даже если он стоит на том же уровне. Полностью проблему не решило, но стало чуть получше.
(Продолжение в комментариях)
Сейчас времени у меня достаточно, поэтому я решил снова подумать, что с этим можно сделать.
Ранее мне доводилось слышать про A* алгоритм и построение графа перемещения, но я пока плохо представляю как все это делать поскольку в тему не погружался. Когда-нибудь я изучу этот вопрос, но пока что нужен способ проще.
Первым решением была способность телепортироваться к выбранной цели. Благодаря телепортации, приведение сможет без проблем дотянуться до врагов, где бы они не находились да и за игроком следовать будет проще.
Как я уже упомянул, на людуме сделать это не вышло. Причиной было непонимание как определять когда можно телепортироваться, а когда нет. Можно смотреть на расстояние по оси Y до цели и если оно выше какого-то минимального порога, то делать скачек. Но что если игрок находится в прыжке? Как определить висит ли он в воздухе или стоит на платформе?
В компоненте RigidBody, описывающем физические параметры объекта, хранятся вектора приложенных сил и вектор текущей скорости. Поскольку прыжок реализован через импульс, а бег через обычное смещение координат в зависимости от времени, то я решил смотреть на значение вектора скорости. Если вектор ненулевой, значит возможно цель висит в воздухе, поэтому лучше подождать.
Для эффектности я нарисовал анимированные клубы дыма, которые появляются в точках откуда и куда призрак переместился.
Ну и напоследок, мне также не нравилось, что всех врагов нужно провоцировать своим лицом и терпеть пока не добегут союзники, поэтому призракам с копьями я дал возможность телепортации к врагу даже если он стоит на том же уровне. Полностью проблему не решило, но стало чуть получше.
(Продолжение в комментариях)
👍9❤4👏3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Кто-то мог заметить неприятный баг в предыдущей игре, когда персонаж внезапно проваливается сквозь платформу и бесконечно падает в бездну. Особенно часто это возникало при переключении вкладок или сворачивании браузера.
Проблема кроется в дискретности обновления игры. Каждый игровой такт, физическая система прикладывает силы к объектам сцены и перемещает их в зависимости от набранной скорости и времени, которое прошло с предыдущего такта игрового цикла. Далее, при возникновении коллизий, физика расталкивает твердые тела чтобы одно не проваливалось сквозь другое. Все эти этапы происходят до отрисовки, поэтому на экране игрок видит как персонаж устойчиво стоит на платформе.
Но что если пауза между кадрами будет слишком большой? К примеру, если устройство пользователя не справится с нагрузкой и игра начнет тормозить. В таком случае, за одну итерацию объект может переместиться настолько далеко, что система обнаружения коллизий не увидит никакого столкновения. Один объект будто бы перескочит через другой. Именно это и происходит в нашей игре.
Решение может заключаться в жесткой фиксации количества итераций физического пайплайна и другой игровой логики, связанной с перемещением объектов по сцене. То есть, вне зависимости от частоты кадров и возможных просадок в производительности, всегда нужно запускать физику одинаковое количество раз с фиксированной дельтой по времени.
Реализовать это можно через добавление вложенного цикла внутрь основного цикла игры и счетчика, накапливающего время выполнения. Цикл крутится уменьшая счетчик на фиксированное количество миллисекунд пока значение не упадет до меньшего или равного нулю. Таким образом, если игра работает медленнее чем нужно, то физика отработает несколько раз, а если игра работает слишком быстро, то она не отработает вообще.
Проблема кроется в дискретности обновления игры. Каждый игровой такт, физическая система прикладывает силы к объектам сцены и перемещает их в зависимости от набранной скорости и времени, которое прошло с предыдущего такта игрового цикла. Далее, при возникновении коллизий, физика расталкивает твердые тела чтобы одно не проваливалось сквозь другое. Все эти этапы происходят до отрисовки, поэтому на экране игрок видит как персонаж устойчиво стоит на платформе.
Но что если пауза между кадрами будет слишком большой? К примеру, если устройство пользователя не справится с нагрузкой и игра начнет тормозить. В таком случае, за одну итерацию объект может переместиться настолько далеко, что система обнаружения коллизий не увидит никакого столкновения. Один объект будто бы перескочит через другой. Именно это и происходит в нашей игре.
Решение может заключаться в жесткой фиксации количества итераций физического пайплайна и другой игровой логики, связанной с перемещением объектов по сцене. То есть, вне зависимости от частоты кадров и возможных просадок в производительности, всегда нужно запускать физику одинаковое количество раз с фиксированной дельтой по времени.
Реализовать это можно через добавление вложенного цикла внутрь основного цикла игры и счетчика, накапливающего время выполнения. Цикл крутится уменьшая счетчик на фиксированное количество миллисекунд пока значение не упадет до меньшего или равного нулю. Таким образом, если игра работает медленнее чем нужно, то физика отработает несколько раз, а если игра работает слишком быстро, то она не отработает вообще.
👍6🔥4❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Недавно добавил параллакс.
Что это вообще такое и как его можно реализовать?
Парралакс в играх – это когда фоновые объекты двигаются с разной скоростью относительно наблюдателя в зависимости от их удаленности. Это дает ощущение объема и картинка выглядит живее.
Реализация может быть очень простой и моя система уместилась в 60 строк кода, а основная часть и вовсе состоит из пары строк вычислений (код немного порезан чтобы было проще прочитать):
Для каждого объекта фона, к которому мы хотим применить параллакс эффект, добавляется компонент Parallax со свойством distance. Текущее положение объекта с параллаксом вычисляется относительно его начальных координат, координат камеры и коэффициента distance, который принимает значения от -1 до 1. Если к примеру distance положительный, то объект будет перемещаться вслед за персонажем, поэтому будет казаться, что объект находится далеко от точки наблюдения и чтобы объект пропал из поля зрения нужно потратить больше времени чем когда параллакса нет.
Что это вообще такое и как его можно реализовать?
Парралакс в играх – это когда фоновые объекты двигаются с разной скоростью относительно наблюдателя в зависимости от их удаленности. Это дает ощущение объема и картинка выглядит живее.
Реализация может быть очень простой и моя система уместилась в 60 строк кода, а основная часть и вовсе состоит из пары строк вычислений (код немного порезан чтобы было проще прочитать):
function update() {
const {
x: cameraX,
y: cameraY,
} = camera.Transform;
actorCollection.forEach((actor) => {
const t = actor.Transform;
const p = actor.Parallax;
t.x = p.startX + (cameraX - p.startX) * p.distance;
t.y = p.startY + (cameraY - p.startY) * p.distance;
});
}
Для каждого объекта фона, к которому мы хотим применить параллакс эффект, добавляется компонент Parallax со свойством distance. Текущее положение объекта с параллаксом вычисляется относительно его начальных координат, координат камеры и коэффициента distance, который принимает значения от -1 до 1. Если к примеру distance положительный, то объект будет перемещаться вслед за персонажем, поэтому будет казаться, что объект находится далеко от точки наблюдения и чтобы объект пропал из поля зрения нужно потратить больше времени чем когда параллакса нет.
👍9❤4🔥2👀1
Казалось бы, у пет-проекта должна быть конечная цель. В какой-то момент можно было бы сказать, что я сделал все что хотел, научился тому что хотел, поэтому можно пойти дальше и переписать движок на Rust.
Вместо этого я только добавляю себе работы.
Например:
– в движке нужно доделывать физику;
– все еще нету звука;
– от рендерера на Three.js хочется отказаться и попробовать снова написать свой рендеринг, но уже на WebGPU.
А еще есть редактор и там свои проблемы:
– в редакторе не хватает инструментов;
– нет валидации в формах инспектора;
– состояние не сохраняется и при перезапуске нужно натыкивать все по новой.
Не говоря уже о том, что хочется писать игры и к каждой уже существующей хочется что-нибудь добавить.
Я как будто насоздавал рабочих мест, но сидеть на них буду в одиночку.
Помогает ведение досок с задачами по каждому проекту. В задачах я веду заметки по ходу работы и подвожу итоги того что сделал. Это позволяет в случае чего восстановить последовательность действий, быстро вернуться в контекст проекта после перерыва или понять зачем я написал такой код пять лет назад.
Было неприятно, когда в конце прошлого года Atlassian заблочил мою учетку с Trello и похерил весь лог работы. Как оказалось они уведомляли меня по почте что аккаунт скоро заблокируют и я потеряю все данные, но я это письмо успешно пропустил, поскольку после сотен писем с заголовком "Taco from Trello" перестал обращать на них внимание. К счастью, по просьбе в поддержку мне дали один день, чтобы все выгрузить и я экспортировал свои доски в Notion. В целом норм.
Короче говоря, конечной цели у меня никакой нет. Возможно я до старости лет буду заниматься движком или пока совсем не надоест. За шесть лет пока не надоело. Пожалуй, в этом я пошел в родителей, которые после приобретения дачи открыли для себя неисчерпаемый источник работы и уже больше 15-ти лет что-то там ремонтируют.
Вместо этого я только добавляю себе работы.
Например:
– в движке нужно доделывать физику;
– все еще нету звука;
– от рендерера на Three.js хочется отказаться и попробовать снова написать свой рендеринг, но уже на WebGPU.
А еще есть редактор и там свои проблемы:
– в редакторе не хватает инструментов;
– нет валидации в формах инспектора;
– состояние не сохраняется и при перезапуске нужно натыкивать все по новой.
Не говоря уже о том, что хочется писать игры и к каждой уже существующей хочется что-нибудь добавить.
Я как будто насоздавал рабочих мест, но сидеть на них буду в одиночку.
Помогает ведение досок с задачами по каждому проекту. В задачах я веду заметки по ходу работы и подвожу итоги того что сделал. Это позволяет в случае чего восстановить последовательность действий, быстро вернуться в контекст проекта после перерыва или понять зачем я написал такой код пять лет назад.
Было неприятно, когда в конце прошлого года Atlassian заблочил мою учетку с Trello и похерил весь лог работы. Как оказалось они уведомляли меня по почте что аккаунт скоро заблокируют и я потеряю все данные, но я это письмо успешно пропустил, поскольку после сотен писем с заголовком "Taco from Trello" перестал обращать на них внимание. К счастью, по просьбе в поддержку мне дали один день, чтобы все выгрузить и я экспортировал свои доски в Notion. В целом норм.
Короче говоря, конечной цели у меня никакой нет. Возможно я до старости лет буду заниматься движком или пока совсем не надоест. За шесть лет пока не надоело. Пожалуй, в этом я пошел в родителей, которые после приобретения дачи открыли для себя неисчерпаемый источник работы и уже больше 15-ти лет что-то там ремонтируют.
👍19❤3🔥3😁3
This media is not supported in your browser
VIEW IN TELEGRAM
Лето выдалось не очень продуктивным. После гейм джема я вяло вносил правки в игру, а потом поехал в отпуск, после которого тоже надо было отдохнуть.
Ludum Dare проходит два раза в год и следующий раз уже не за горами – 4 октября. Самое время вспомнить какие проблемы возникали во время разработки игры на апрельском эвенте.
Одна из таких проблем – при перезапуске редактора сбрасываются все настройки вроде размера сетки, выбранного уровня или открытых табов. Перезапускать его при этом приходится часто. Чтобы созданные для игры компоненты и системы подгружались в редакторе, нужно обновить страницу, поскольку под капотом редактора webpack пересобирает код, который берет из проекта с игрой. Возможно тут можно прикрутить hot module replacement чтобы перезагрузка не требовалась, но даже если редактор был закрыт, хотелось бы продолжить с того места на котором остановился.
Один из вариантов – использовать local storage, но порт на котором запускается редактор выбирается случайным образом для избежания коллизий с другими приложениями, поэтому доступ к сохраненным данным можно легко потерять.
Другой вариант – хранить состояние в файле. Способ простой и надежный. На нем я и остановился. Написал класс прослойку PersistentStorage, который отдает get, set методы, под капотом хранит все в обычном объекте и периодически пишет изменения в файл через node апишку проброшенную через electron. Далее во всех местах редактора, где было желание прихранить состояние, я добавил обращение к PersistentStorage.
Теперь при перезапуске редактора восстанавливается выбранный уровень, положение камеры на сцене, настройки грида и некоторые другие параметры вроде открытых табов и раскрытых элементов в древовидных списках.
Возвращаться к работе после долгого перерыва может быть тяжело, особенно если тебе за нее не платят, но я постарался взять быструю и понятную задачу и заставлял себя хотя бы по минут 15-20 в день смотреть в проект и писать пару строчек кода. Так и вернулся настрой.
Ludum Dare проходит два раза в год и следующий раз уже не за горами – 4 октября. Самое время вспомнить какие проблемы возникали во время разработки игры на апрельском эвенте.
Одна из таких проблем – при перезапуске редактора сбрасываются все настройки вроде размера сетки, выбранного уровня или открытых табов. Перезапускать его при этом приходится часто. Чтобы созданные для игры компоненты и системы подгружались в редакторе, нужно обновить страницу, поскольку под капотом редактора webpack пересобирает код, который берет из проекта с игрой. Возможно тут можно прикрутить hot module replacement чтобы перезагрузка не требовалась, но даже если редактор был закрыт, хотелось бы продолжить с того места на котором остановился.
Один из вариантов – использовать local storage, но порт на котором запускается редактор выбирается случайным образом для избежания коллизий с другими приложениями, поэтому доступ к сохраненным данным можно легко потерять.
Другой вариант – хранить состояние в файле. Способ простой и надежный. На нем я и остановился. Написал класс прослойку PersistentStorage, который отдает get, set методы, под капотом хранит все в обычном объекте и периодически пишет изменения в файл через node апишку проброшенную через electron. Далее во всех местах редактора, где было желание прихранить состояние, я добавил обращение к PersistentStorage.
Теперь при перезапуске редактора восстанавливается выбранный уровень, положение камеры на сцене, настройки грида и некоторые другие параметры вроде открытых табов и раскрытых элементов в древовидных списках.
Возвращаться к работе после долгого перерыва может быть тяжело, особенно если тебе за нее не платят, но я постарался взять быструю и понятную задачу и заставлял себя хотя бы по минут 15-20 в день смотреть в проект и писать пару строчек кода. Так и вернулся настрой.
🔥11👍8👏2
This media is not supported in your browser
VIEW IN TELEGRAM
В редакторах кода так много функций, которые даже не замечаешь. Например, при переключении между табами с открытыми исходниками, в проводнике происходит подскролл к выбранному файлу, а если файл был в свернутой директории, то весь путь до него раскрывается.
Когда пишешь редактор сам, такие мелочи быстро замечаешь, потому что у тебя этого нет и реализовывать все нужно самостоятельно.
Добавил скролл, авто раскрытие родительских нод и переключение вкладки на ту, что соответствует выбранному элементу. Мелочь, но времени на ручные переключения будет тратиться чуть меньше.
Когда пишешь редактор сам, такие мелочи быстро замечаешь, потому что у тебя этого нет и реализовывать все нужно самостоятельно.
Добавил скролл, авто раскрытие родительских нод и переключение вкладки на ту, что соответствует выбранному элементу. Мелочь, но времени на ручные переключения будет тратиться чуть меньше.
🔥15👍8💯4👏2
This media is not supported in your browser
VIEW IN TELEGRAM
Расскажу немного про работу с шаблонами в редакторе.
При создании актора (напоминаю, что актором в моем движке называется любой объект на сцене) из шаблона, копируется вся его иерархия, включая вложенные шаблоны. Зачем это нужно? После создания актора из шаблона его можно кастомизировать, что-то переопределить или добавить дополнительные объекты. Чтобы различать кастомные акторы от наследуемых, каждый актор помечается признаком, указывающим от какого вложенного шаблона он наследуется.
Проблема возникает когда в шаблон нужно добавить новые объекты. Если на сцену ранее уже добавлялись акторы, созданные из этого шаблона, то при изменении структуры шаблона, нужно отыскать все наследуемые акторы и обновить их.
В свое время я не стал этого делать, решив что работа с шаблонами очень уж сложная и лучше не развивать эту историю, а позднее все переписать.
Очень зря, потому что ничего лучше я так и не придумал, а во время людума после редактирования шаблонов персонажей мне приходилось вручную удалять и добавлять заново все наследуемые акторы тратя кучу времени.
Проблему я исправил и теперь при добавлении вложенных шаблонов, редактор модифицирует всех наследников автоматически. Происходит это путем перебора дерева уровней в поиске акторов с подходящим идентификатором родительского шаблона.
Не все проблемы удалось решить за один заход, но это уже другая история. В любом случае ручной работы снова стало немного меньше.
При создании актора (напоминаю, что актором в моем движке называется любой объект на сцене) из шаблона, копируется вся его иерархия, включая вложенные шаблоны. Зачем это нужно? После создания актора из шаблона его можно кастомизировать, что-то переопределить или добавить дополнительные объекты. Чтобы различать кастомные акторы от наследуемых, каждый актор помечается признаком, указывающим от какого вложенного шаблона он наследуется.
Проблема возникает когда в шаблон нужно добавить новые объекты. Если на сцену ранее уже добавлялись акторы, созданные из этого шаблона, то при изменении структуры шаблона, нужно отыскать все наследуемые акторы и обновить их.
В свое время я не стал этого делать, решив что работа с шаблонами очень уж сложная и лучше не развивать эту историю, а позднее все переписать.
Очень зря, потому что ничего лучше я так и не придумал, а во время людума после редактирования шаблонов персонажей мне приходилось вручную удалять и добавлять заново все наследуемые акторы тратя кучу времени.
Проблему я исправил и теперь при добавлении вложенных шаблонов, редактор модифицирует всех наследников автоматически. Происходит это путем перебора дерева уровней в поиске акторов с подходящим идентификатором родительского шаблона.
Не все проблемы удалось решить за один заход, но это уже другая история. В любом случае ручной работы снова стало немного меньше.
👍9🔥7👏1