Можно найти много обучающих материалов "как написать игру", про Змейку там, Арканоид или ещё что-то нибудь простое. Даже ИИ их пишет. Но могу поспорить, что практически ни одно такое руководство не будет содержать ничего про интерфейс. Его как будто нет вообще. После запуска вы сразу начинаете играть, а после окончания игры допустим нажимаете клавишу повтора и опять начинаете играть.
Между тем даже в самой примитивной игре, если вы хотите сделать её законченным продуктом, значительная часть кода будет посвящена интерфейсу в самых различных видах. Это и главное меню, и настройки, и выбор уровня, и экран паузы, и какой-нибудь инвентарь, и так далее.
Код, не связанный непосредственно с игровым процессом и технически скучный, вызывает естественное отвращение, его не хочется делать.
Но если его всё-таки приходится делать, то первое решение это взять какую-то готовую библиотеку. Она может рисовать окна, кнопки, текстовые поля, и обрабатывать ввод с клавиатуры и нажатия мыши.
Беда, однако, в том, что эти библиотеки никогда не бывают легковесными. Они потянут в проект тонны зависимостей, о которых вы и не подозревали, а 90% предоставленного функционала вы не используете, потому что вам допустим надо нарисовать лишь простенькое меню, но так как вы слишком мамкин программист, то предпочтёте нагрузить свой проект кучей ненужного мусора.
Вторая беда будет в том, что этот функционал всё равно придётся изучать и понимать, как им пользоваться.
Третья беда заключается в том, что он будет вас ограничивать. Он может делать то-то и то-то, а если вам нужно сделать ещё что-то, вы сразу уткнётесь в глухую стену. Потому что библиотеку писали не вы, и пользоваться ей это одно, а дорабатывать – уже совсем другое.
Не нужно переиспользовать
Все эти беды проистекают ровно из одной причины – принято считать, что код должен быть повторно используемым, поэтому любая библиотека заточена на то, чтобы быть как можно более универсальной. И как следствие, громоздкой.
Представьте себе, но можно забить на повторную используемость кода и каждый раз просто писать его заново. Скажете, что это тяжело? Совсем нет.
По крайней мере для игр.
Игровой интерфейс, во-первых, относительно незамороченный, а во-вторых, он наиболее выигрывает тогда, когда стилизован под игру или даже интегрирован непосредственно в её процессы. Чем он менее стандартный, тем лучше. И значит, проблема с повторным использованием автоматически снимается.
Свой интерфейс будет действительно легковесным, потому что вам не нужно поддерживать его для всех мифических ситуаций.
Вы будете полностью понимать, как он работает, потому что вы его и написали. У вас не возникнет проблем и затыков, а почему вот это работает не так, а как заставить вот это работать так, и т.д.
И тогда интерфейс из чужеродного компонента превратится в часть игры, а его программирование перестанет быть обузой и станет интересным и креативным процессом. Нетрудно догадаться, что это пойдёт на пользу вашему проекту.
В игре Kinetic Worm курсор меню в виде горизонтальной линии одновременно был графическим отображением звуковой волны в реальном времени:
Интерфейс Life построен на кнопках и ползунках:
Интерфейс Minesweeper прозаичен, но использует собственные растровые шрифты в собственной кодировке:
В каждом из этих случаев мне никуда не упёрлось повторное использование либо универсальность, так как все интерфейсы уникальные и заточены под конкретную игру.
Когда нужно, я просто делаю интерфейс с нуля, как например в игре Pengu5.
Это черновая версия из редактора, поэтому там разные шрифты и прочая кривизна, но суть в том, что это уже более-менее классическое окно с кнопками и информационными элементами. Посмотрим, как оно устроено, и действительно ли так сложно и долго сделать его самостоятельно.
Во-первых, каждое окно в Pengu5 это стандартный графический объект, который рисуется на сцене наравне с другими графическими объектами.
Чтобы собрать элементы окна вместе, делается слой-подложка, на котором размещаются отдельные спрайты. Начнём с фона:
Добавим элементы, которые выделяют определённые области, но также являются фоном:
Добавим изображения кнопок:
И аналогичным образом разместим остальные элементы.
Сложно ли нарисовать такое окно? Нет, надо просто взять кучу разных элементов и расположить их на подложке в нужных местах. Какую библиотеку вы бы ни использовали, вам всё равно пришлось бы это сделать, потому что элементы сами себя не расположат. Так что здесь вы ничего не теряете.
Далее надо заставить работать кнопки.
Изображение кнопки, которое мы видим – это лишь изображение, но к нему можно привязать отдельный объект, на который можно повесить обработчик событий.
Это event-driven архитектура, очень похожая и во Flash, и в JavaScript, то есть пишется что-то вроде
button_play.addEventListener(MOUSE_CLICK, play);
И нам это также пришлось бы прописывать в любом случае, с любой библиотекой. Так что никакой лишней работы мы здесь не делаем, а наоборот, экономим время и нервы, не пользуясь ничем посторонним.
Итого, окно в Pengu5 это графическая подложка, на которой расположены в нужных местах спрайты, и некоторые спрайты являются кнопками и имеют обработчики событий.
Например, вот это меню:
тоже окно. Оно содержит три надписи-кнопки, расположенные на невидимой подложке. К каждой кнопке можно привязать своё действие. Всё окно описывается следующим кодом:
Каждое уникальное окно это отдельный класс, который наследуется от базового класса PenguWindow. Задача базового класса – обеспечить типовые методы окна, а задача дочернего класса – расположить собственные уникальные элементы как надо и назначить обработчики событий.
Система событий
В JavaScript можно добавить обработчик события на любой объект, но с одним ограничением – это должен быть DOM-объект, то есть элемент HTML-страницы. Изображение кнопки, нарисованное в видеобуфере, не является DOM-объектом, поэтому движок Pengu5 поддерживает собственную систему событий с необходимыми уровнями абстракции.
Так, когда в браузере нажимается кнопка мыши, первоначально срабатывает нативный обработчик DOM-объекта, коим является весь игровой экран (canvas). Затем событие клика транслируется во внутреннее событие PenguEvent.
Кнопка в окне получает событие клика PenguEvent.CLICK, уже не связанное с DOM, то есть достигнут один уровень абстракции. Затем обработчик события, связанный с этой кнопкой, порождает новое событие PenguEvent.PW_CONFIRM. Суть его в том, что игра не получает событие "нажата такая-то кнопка в окне". Игре это неинтересно. Вместо этого она получает событие "получено подтверждение от текущего окна".
Каким образом оно получено, неважно, так? Можно к примеру нажать пробел, или провести пальцем. Что именно является подтверждением, решает само окно. Что делать с подтверждением от данного конкретного окна, решает игра.
Так мы получаем второй уровень абстракции, уже в отношении игровой логики.
Менеджер окон
Окон может быть несколько, и например в самом начале появляется главное меню, а после него меню выбора уровня. Но если мы откажемся от выбора уровня, то попадём назад в главное меню.
Для того чтобы окна появлялись и исчезали, сделан менеджер окон, который умеет ими управлять.
Как я писал выше, лучше всего когда игровой интерфейс непосредственно интегрирован в игру, поэтому окна Pengu5 не просто появляются и исчезают, а всплывают или тонут.
Короче, получилось что-то вроде карусели, но по вертикали. Менеджер хранит стек окон и поэтому может автоматически делать скролл на активное окно.
Подборка всех материалов про Pengu5: