Академический Документы
Профессиональный Документы
Культура Документы
Автор
Alberto Benavent Ramón
Преподаватель
Francisco José Gallego Duran
Ciencia de la Computación e Inteligencia Artificial
Аннотация
Playdate — это новая портативная консоль, разработанная Panic, которая будет выпущена
в 2021 году. Её цель — предложить уникальный и удивительный опыт для любителей
видеоигр, и по этой причине она обладает нетрадиционными характеристиками: в
дополнение к обычным кнопкам управления и действия, она имеет отражающий
монохромный черно-белый экран, акселерометр и рукоятку (crank) на боковине, которая
тоже действует как контроллер.
В этой бакалаврской диссертации будут рассмотрены возможности дизайна видеоигр,
для такого своеобразного оборудования, посредством создания нескольких прототипов,
охватывающих все поддерживаемые языки программирования и их оценки. С точки
зрения производительности Playdate достаточно скромен; по этой причине основное
внимание будет уделяться низкоуровневому программированию для достижения
максимально возможной производительности. Знания, полученные на первом этапе,
будут применены при разработке полноценной игры «TinySeconds» на языке C.
«TinySeconds» — это 2D-платформер, в котором каждый уровень необходимо пройти за
2,5 секунды. В дополнение к этому лимиту, чтобы перейти в следующий мир, игрок
должен последовательно пройти все последующие уровни в течение общего ограничения
по времени. Это делает игру захватывающей с отличной возможностью повторного
прохождения, поскольку она предлагает игроку попрактиковаться и улучшить время
прохождения. Помимо ограничения по времени, различные типы препятствий
добавляют разнообразия уровням, используя уникальные для консоли характеристики,
например, такие как рукоятка.
В дополнение к документированию разработки этих проектов будет включено
руководство по программированию на C для Playdate, в котором обучаются основным
принципам настройки среды программирования в Windows и разрабатывается пример
программы. Цель этой главы — восполнить недостаток документации по
программированию на C для Playdate на платформе Windows, поскольку официальное
руководство сосредоточено на языке Lua в средах Mac.
Resumen
Playdate es una nueva consola portátil desarrollada por Panic que será lanzada al Mercado en
2021. Su objetivo es ofrecer una experiencia distinta y sorprendente a entusiastas de los
videojuegos, y por ello, presenta características poco convencionales: además de los habituales
botones direccionales y de acción, tiene una pantalla monocroma reflectante en blanco y negro
puros, acelerómetro, y una manivela en el lado que sirve como controlador.
En esta memoria, se explorarán las posibilidades de diseño de videojuegos que ofrece un
hardware tan peculiar mediante la creación de diversos prototipos, cubriendo los diferentes
lenguajes de programación que soporta y realizando una evaluación de los mismos. A nivel de
hardware es una consola de potencia modesta, por lo que se optará por la programación a bajo
nivel para buscar el mejor rendimiento posible. Este conocimiento adquirido será después
aplicado al desarrollo de un juego completo en C, “TinySeconds”.
“TinySeconds” es un videojuego de plataformas en vista lateral donde cada nivel debe ser
completado en menos de 2,5 segundos. Además, para poder progresar de un mundo al siguiente,
los niveles de un mismo mundo deben ser superados consecutivamente en un tiempo limitado.
Esto dota al juego de un ritmo frenético y de gran rejugabilidad al inviter a los jugadores a
practicar para mejorar sus tiempos. Además de la limitación temporal, diversos tipos de
obstáculos añaden variedad a los niveles utilizando características propias de la consola como la
manivela.
Además de documentar el desarrollo de estos proyectos, se desarrollará un tutorial de
programación en C para Playdate, instruyendo los principios básicos de configuración del
entorno de programación en Windows, y desarrollando un programa de ejemplo. Este capítulo
nace para suplir la falta de documentación oficial sobre programación en C para la consola en un
entorno Windows, ya que los recursos oficiales se centran en el lenguaje Lua y entornos Mac.
Благодарность
Эта работа была бы невозможна без поддержки, которую я получил от своего окружения
во время её разработки.
Я хотел бы поблагодарить моего научного руководителя Франциско Хосе Гальего за его
руководство при разработке этой бакалаврской диссертации, а также за то, что он
поделился страстью и знаниями, которые он хранит в области видеоигр, со своими
студентами.
Друзьям, которых я приобрёл во время учебы в университете, и тем, кто всегда со мной
рядом: спасибо за то, что вы заставили меня насладиться этими прошедшими пятью
годами моей жизни. Вы все были постоянным источником радости и поддержки, и мне не
терпится пережить ещё множество приключений вместе с вами.
Спасибо моей семье за то, что выслушали мои рассуждения о разработке этой
диссертации, за то, что они были самыми любящими и поддерживали меня, а также за то,
что они сыграли главную роль в моих самых счастливых воспоминаниях; вы превратили
меня в того, кем я являюсь сегодня.
Итак, занимательные истории, весёлые игровые системы... Они уже существуют в
этом мире.
Я хочу увидеть, что находится за этой стеной.
Как бы вы это ни называли, это пространство,
куда ещё никто не заходил.
Йоко Таро
Содержание
1. Введение 1
2. Основные цели 2
3. Теоретическая основа 5
3.1. Playdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.1.1. Характеристика оборудования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.2. Художественная составляющая . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1. Игры Playdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1.1. Crankin’s Time Travel Adventure . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1.2. Daily Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2.1.3. PlayMaker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2.2. Другие игры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.2.1. Super Mario 3D World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.2.2. Rhithm Heaven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.2.3. BOXBOY! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.2.4. Minit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2.3. Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4. Методология 15
5. Работа с Playdate на языке C 17
5.1. Настройка среды . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.1.1. Создание шаблона . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.1.2. Структура проекта Playdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.2. Hello, World! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.2.1. Некоторые улучшения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.2.2. По частоте кадров . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.2.3. Подпрыгивания по кругу . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.4. Прокрутите рукоятку . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2.5. Дополнительные шаги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6. Разработка 27
6.1. Итерация 0 – Знакомство с Playdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.1.1. Итерация 0.1 - язык Lua . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.1.2. Итерация 0.2 - язык С и С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.1.3. Итерация 0.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2. Игра: TinySeconds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.2.1 Концепция . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
xii
xiv Содержание
Источники 67
A. Проведённые эксперименты 71
A.1. Lua . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
A.1.1. Hello world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
A.1.2. Макет Dr. Mario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
A.1.3. Устройте сюрприз . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
A.1.4. Наклонная мини-игра . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
A.1.5. Ритмическая игра . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
A.2. C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
A.2.1. Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
A.2.2. Упрощённый ECS Starfield эффект . . . . . . . . . . . . . . . . . . . . . . . . 77
A.2.3. Полный ECS Starfield эффект . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
A.3. C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
A.3.1. Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
A.4. Pulp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
A.4.1. Приключенческая игра . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
B. Отчёты об ошибках 81
B.1. Ошибка пропуска JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
B.1.1. Ошибка при пропуске пары JSON в методе shouldDecodeTableValueForKey() 81
B.1.1.1. Настройка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
B.1.1.2. Шаги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
B.1.1.3. Ожидаемые результаты . . . . . . . . . . . . . . . . . . . . . . . . . 82
xvi Содержание
C. Программа Tiled 85
xix
1. Введение
«Разработка для Playdate» — это введение в разработку программного обеспечения для
будущей портативной консоли Playdate, написанное до её публичного запуска во время
предварительной версии для разработчиков.
Содержание этой бакалаврской диссертации призвано стать ориентиром для будущих
разработчиков, заинтересованных в этом оборудовании, а также стать хроникой моих
прототипов, экспериментов и процесса обучения, кульминацией которых станет разработка
полноценной игры.
Большая часть содержания сосредоточена на программировании на языке C, с целью
получения базовых знаний об аппаратном обеспечении, осознанного развития с точки
зрения максимизации производительности и передачи знаний, извлеченных из этого опыта,
читателю. Оно также призвано охватить менее документированную область разработки
языка C на Windows для консоли, поскольку большая часть доступных ресурсов
сосредоточена на программировании Lua и среде Mac.
Каждый из созданных прототипов и демоверсий будет стремиться изучить сильные и
слабые стороны устройства, найти в них новые дизайнерские возможности и включить их
в игровой процесс. «TinySeconds», основная игра, разработанная в рамках этой
бакалаврской диссертации, будет использовать опыт, полученный на этапе прототипов, для
разработки увлекательного игрового процесса и инновационных взаимодействий,
адаптированных к функциям оборудования.
«TinySeconds» — это 2D-платформер с элементами головоломки, ориентированными на
прохождение уровней за короткое время. Эта механика требует от игрока быстрой реакции
и повышает возможность повторного прохождения, заставляя его проходить уровни и миры
за наименьшее количество перезапусков.
В дополнение к этой хронике, диссертация включает главу, написанную в виде обычного
учебника, которая проведет новичков в консоли через первые шаги разработки C для
Playdate в Windows. В этой главе подчеркиваются основные способы достижения
производительности на устройстве и включены упражнения для отработки и расширения
изложенных в ней концепций.
В этой диссертации также рассказывается об опыте создания игр на этапе производства
оборудования, процессе, который включал раскрытие или изменение функций и
спецификаций во время разработки, а также отчеты об ошибках и ошибках,
способствующие обеспечению качества (QA) консоли.
1
2. Основные цели
Когда в мае 2019 года была анонсирована консоль Playdate, я сразу же был очарован
простотой и свежестью её предложения; зачастую творчество стимулируется
ограничениями, и хотя Playdate — это консоль с современными характеристиками, её
аппаратное обеспечение по-прежнему ограничено по сравнению с современными
консолями и ПК. Его способность программировать на низкоуровневом языке C дала
возможность применить знания, полученные при изучении мультимедийной инженерии,
что заставило меня посчитать его идеально подходящим для моей бакалаврской
диссертации.
Летом 2020 года у меня появилась возможность принять участие в Playdate Developers
Preview — программе, которая предоставила мне доступ к консоли и SDK перед запуском.
Я понял, что документации по C API очень мало, и решил, что моя бакалаврская
диссертация может стать полезным ресурсом для других разработчиков после меня.
Итак, я решил разработать свою бакалаврскую диссертацию, посвященную
исследованиям и разработкам для Playdate, а также написанию полезной документации для
разработчиков, заинтересованных в программировании на C для этой новой консоли.
Перечень задач дипломной работы следующий:
3
3. Теоритическая основа
3.1. Playdate
Playdate (3.1) — грядущая портативная консоль, созданная Panic; компания по разработке
программного обеспечения, специализирующаяся на приложениях для Mac и имеющая
опыт работы в индустрии видеоигр в качестве издателя игр «Firewatch» и «Untitled Goose
Game». Впервые об этом было объявлено 22 мая 2019 года*1, одновременно с запуском
официального сайта (https://play.date).
5
6 Теоритическая основа
Размеры: 76 x 74 x 9 мм.
Дисплей: 2,7-дюймовый ЖК-дисплей Sharp Memory с разрешением 400×240 (173
точки на дюйм).
Частота обновления: до 50 Гц для полноэкранного рисования и выше при рисовании
с меньшим количеством строк пикселей.
Процессор: Cortex M7 180 МГц
Память: 16 МБ внешней оперативной памяти плюс 320 КБ встроенной оперативной
памяти.
Память: 4 ГБ.
Возможности подключения: Wi-Fi (b/g/n) @ 2,4 ГГц, Bluetooth 4.2, USB-C, разъем
для наушников.
Вес: 86 грамм.
Daily Driver — это гоночная игра с видом сверху, созданная разработчиком Мэттом
Септоном. В нем представлен широкий выбор автомобилей и подобных транспортных
средств с разной физикой и внешним видом.
Автомобили представляют собой предварительно визуализированные изображения 3D-
объектов, созданные в OpenSCAD, программе компьютерного проектирования (САПР),
которая позволяет создавать модели с помощью сценариев с использованием собственного
языка описания. Частям модели присваиваются чистые красные, зеленые или синие цвета,
а затем они визуализируются под 32 углами вокруг них, чтобы получить выборочный вид
на 360°. Затем полученные изображения подвергаются пакетной обработке с
использованием ImageMagick, библиотеки обработки изображений с открытым исходным
кодом, разделяя их на каналы RGB и присваивая каждому каналу черный, белый цвет или
шаблон размывания.
В ходе разработки для каждого автомобиля были добавлены дополнительные рендеры,
отражающие поворот колес и смещение веса автомобиля. Тени реализуются путем
выравнивания 3D-моделей транспортных средств по вертикальной оси и их рендеринга для
каждого из спрайтов транспортных средств.
В игровом процессе автомобили управляются кнопкой A или стрелкой вверх для
ускорения, кнопкой B или стрелкой вниз для тормозов и рукояткой для дрифта. На уровнях
представлены разнообразные задачи, в которых игрок должен мчаться по трассе, быть
осторожным, чтобы не столкнуться с препятствиями, играть в футбол или собирать монеты,
а также выполнять другие условия победы.
3.2.1.3. PlayMaker
PlayMaker — это набор игрушек для творчества, разработанный Дастином Миро. В нем
представлены режимы музыки, рисования, блоков и танца, а возможные дополнительные
режимы еще не раскрыты.
Музыкальный режим работает аналогично музыкальной шкатулке, где игрок может
размещать ноты на пентаграмме, выбирая тембр и высоту звука, а затем воспроизводить
музыку, поворачивая ручку. Темп зависит от того, насколько быстро игрок поворачивает
рукоятку, что также позволяет воспроизводить песни задом наперёд.
Режим рисования представляет собой простой редактор растровых изображений с
несколькими инструментами рисования, такими как кисть с динамикой обводки, карандаш
для мелких деталей, ведро для заливки цветом, ластик и инструмент рисования
3.2. Художественная составляющая 9
В игре Nintendo эти блоки меняют состояние с фиксированным ритмом, а в нашей игре
игрок управляет их состоянием с помощью рукоятки. Такое поведение, когда игрок
управляет этим типом блока, можно сравнить с «Красно-синими панелями» (рис. 3.7) из той
же игры, которые меняют свое состояние каждый раз, когда игрок прыгает.
3.2.2.3 BOXBOY!
БОКСБОЙ! — это серия игр-платформеров-головоломок, разработанная HAL Laboratory
и изданная Nintendo для системы Nintendo 3DS. Игрок управляет персонажем, который
может создавать коробки и использовать их для решения головоломок. Коробки
прикрепляются к игроку, поэтому их можно свешивать с выступов или использовать в
качестве щита, а затем можно бросить на землю, что может активировать переключатели и
другие виды механики.
БОКСБОЙ! был главным источником вдохновения для художественного стиля TinySe-
12 Теоритическая основа
3.2.2.4. Minit
3.2.2. Заключение
Наша игра TinySeconds является инновационной на рынке Playdate, поскольку не
существует анонсированных игр с сопоставимой механикой, которые могли бы составить
конкуренцию в категории динамичных аркадных платформеров. Он также представляет
новый способ использования рукоятки: она складывается за устройством, ограничивая ее
диапазон задней стороной консоли. Это положение позволяет использовать рукоятку одним
из самых быстрых способов, поскольку ее можно щелкнуть, как переключатель, не выходя
за пределы досягаемости игрока, что устраняет распространенную проблему, когда
рукоятку и кнопки трудно использовать одновременно.
Его особенность еще и в том, что он запрограммирован на C, в то время как более широко
распространенный язык программирования для консоли — Lua. C — один из самых
низкоуровневых языков, в котором память управляется вручную, а код компилируется
непосредственно в ассемблер. Lua-игры, наоборот, собирают мусор и запускаются на
виртуальной машине. Эта разница значительно повышает производительность игры на C,
например, при чтении файлов JSON по сравнению с той же операцией на Lua. Даже если
конечный пользователь не обращает внимания на используемый язык программирования,
хорошая производительность всегда будет приветствоваться, а хроника разработки игры
будет ценна для будущих программистов Playdate C.
4. Методология
Этот проект следует итеративной методологии, основанной на прототипах. Время
разработки разделено на итерации, основанные на предыдущих, что означает, что основная
реализация всех функций будет быстро готова, а улучшения и доработки будут добавляться
к ней волнами. На первых этапах проекта целью итераций будет не продвижение основной
игры, а создание быстрых демоверсий как способа изучения и документирования
использования Playdate SDK.
Каждая итерация делится на три этапа:
1. Планирование. Первым шагом в каждой итерации является определение целей,
которые будут преследоваться на протяжении всей итерации. Это должны быть
краткосрочные, конкретные цели, достижимые за одну итерацию, что в нашем
случае означает четыре недели разработки. Задачи, длина которых превышает одну
итерацию, следует разбить на более мелкие цели, указав, какая часть из них будет
выполнена в текущем сроке.
2. Разработка. Естественно, это самая длинная часть каждой итерации, в ходе которой
работа направлена на достижение целей, определенных на этапе планирования. Это
включает в себя программирование демо-версий или игры и часто натыкается на
ошибки или препятствия, которые могут замедлить или изменить проклятие
разработки. Хотя эти отклонения и нежелательны, они могут быть ценными с точки
зрения обучения и будут собираться и анализироваться на третьем этапе
итеративного процесса.
За пределами этой структуры лежит создание главы 5, которая служит руководством для
новых разработчиков Playdate, заинтересованных в кодировании на C, и не соответствует
циклам разработки основного проекта.
15
5. Работа с Playdate на языке С
Эта глава представляет собой руководство для начинающих по разработке для Playdate на
C. Мы рассмотрим каждый шаг: от настройки инструментов C для кодирования и
компиляции в Windows до создания простой игры об астероидах.
Данное руководство предполагает среднее понимание языка программирования C.
Большинство концепций будет легко понять при наличии общих знаний
программирования, но мы будем использовать некоторые характеристики, специфичные
для C, например указатели.
В этом руководстве, будет использоваться Playdate SDK версии 1.0.8, которую можно
загрузить из официальных источников (на момент написания — форумы разработчиков
Playdate*1).
Откройте папку, которую мы только что скопировали, и удалите файлы .nova, .xcodeproj и
Makefile, поскольку они относятся к другим редакторам и системам сборки, которые мы не
будем использовать. Мы должны изменить содержимое файла CMakeLists.txt, чтобы
адаптировать его к платформе Windows. Этот файл сообщает CMake расположение наших
исходных файлов, имя исполняемого файла, который мы хотим создать, версию CMake,
которую мы хотим использовать, и где найти файлы CMake, предоставленные Panic с SDK.
Замените содержимое файла следующим:
Создайте новую папку в корне проекта с именем .vscode. В этом каталоге будут храниться
файлы конфигурации, которые Visual Studio Code будет читать и использовать. Внутри него
создайте файл cmakekits.json и заполните его следующим:
Это определяет новую цель CMake, которая использует файл Arm_patched.cmake, который
мы создали в предыдущем разделе.
20 Работа с Playdate на языке С
В качестве последнего необязательного шага мы можем легко создать задачи для запуска
общих команд из редактора. В папке .vscode создайте новый файл Tasks.json и добавьте
следующее содержимое:
_______________________
*7. Обратите внимание, что для работы первых двух задач корневая папка проекта должна иметь имя, точно
соответствующее значению переменной PLAYDATE_GAME_NAME в файле CMakeLists.txt, и не содержать
пробелов.
5.2. Hello World 21
Каталог src, папка в которой мы создаем исходные файлы, содержащие код нашей
игры. Именно здесь происходит большая часть разработки. Каждый проект Playdate
будет иметь в этой папке файл main.c, который содержит цикл обновления, который
будет выполнять каждый кадр, и функцию eventHandler, которая позволяет нам
реагировать на различные типы обратных вызовов, таких как запуск игры,
блокировка или разблокировка консоли. или откроется меню паузы.
Во-первых, нам нужно знать размеры «Hello World!» text, чтобы определить, когда одна
из его сторон касается границы экрана, и инвертировать ощущение его движения. Мы знаем
высоту текста из шрифта, который мы указали в строке loadFont(), это «Asheville-Sans14-
Bold.pft», то есть его высота составляет 14 пикселей. Для расчета ширины в Playdate SDK
есть собственный метод pd->graphics->getTextWidth(). Зная это, инициализируйте
переменные textWidth и textHeight сразу после метода setFont() в eventHandler.
Переменные x и y хранят положение текста. Мы хотим, чтобы он начинался в центре
экрана, поэтому на каждой оси позиция должна быть равна размеру экрана минус размер
текста, а затем разделена на два. API Playdate имеет две константы для ширины и высоты
экрана: LCD_COLUMNS и LCD_ROWS. Это просто значения 400 и 240 соответственно,
что соответствует разрешению дисплея, но использование стандартных констант делает
код более читабельным.
Теперь нам нужно обновлять положение текста в каждом кадре, добавляя шаг X к
переменной x и шаг Y к переменной y. Наконец, если текст выходит за пределы экрана, нам
нужно переключить направление движения по каждой оси: это происходит, когда значение
позиции для этой оси меньше 0 или больше, чем размер экрана минус размер текста для
этой оси.
Добавление этих изменений в код приводит к следующему файлу main.c:
5.2. Hello World 25
Мы хотим, чтобы текст подпрыгивал, как и раньше, и увеличивал или уменьшал скорость
в зависимости от скорости рукоятки. Чтобы добиться этого, при добавлении переменных
шага к позиции текста мы также добавим переменные шага, умноженные на значение,
возвращаемое pd->system-> getCrankChange().
Работая над примером, который мы создали на основе этой главы, попробуйте реализовать
некоторые из следующих улучшений или бросьте вызов любым модификациям, которые
только можете себе представить:
В разделе 5.2.2 мы видели, как очистка экрана в каждом кадре ограничивает частоту
обновления до 50 кадров в секунду. Можете ли вы изменить наш процесс
рендеринга, чтобы стереть только необходимую часть экрана? Ознакомьтесь с
функциями рисования геометрии в официальном руководстве Inside Playdate with C
от Panic (2020b) или придумайте собственное решение.
Нарисуйте фон под текстом вместо пустого экрана, который у нас есть сейчас.
Playdate имеет дисплей памяти, что означает, что пиксели сохраняют свое значение
до тех пор, пока над ними не будет выполнен другой вызов отрисовки. Зная это,
можно ли реализовать предыдущую оптимизацию и перерисовывать только ту часть
фона, которая необходима для стирания текста между кадрами?
Поэкспериментируйте с режимами рисования, такими как XOR, OR, и тем, как они
влияют на текст при наведении на фоновое изображение.
Одна из первых рекомендаций, которую разработчик Playdate Panic дал во время прямой
трансляции, посвященной программированию, заключалась в том, чтобы опытные
программисты «сначала проверили интерфейс Lua, вы можете получить от него приличную
производительность, и это намного проще, чем писать в интерфейс C» (Фрэнк, 2020, мин.
4:02). Это предложение показалось разумным, и поэтому первые прототипы были написаны
с использованием Lua SDK.
Даже без предыдущего опыта работы с языком, кривая обучения была умеренной.
27
28 Разработка
Интерфейс Lua оказался понятным и обширным, выходя за рамки основ благодаря готовой
реализации многих распространенных игровых функций, таких как тайловые карты,
эффекты изображения, z-буферизация и обнаружение столкновений.
Hello World. Первый эксперимент представлял собой модификацию примера кода из
официального руководства Inside Playdate от Panic (2020a) и служил для понимания
процесса рисования изображений на дисплее с использованием функций спрайтов,
включенных в SDK, простой обработки ввода и воспроизведение звука.
Макет Dr. Mario. На основе этого первого проекта я быстро реализовал макет того, как
классическая игра Dr. Mario от Nintendo Entertainment System (NES) будет выглядеть на
Playdate. Здесь таблетка свободно перемещается с помощью направляющей и вращается
поворотом рукоятки. Акселерометр используется для определения того, находится ли
устройство боком, и в этом случае переключается на вертикальное расположение.
Приготовьте сюрприз. В третьем эксперименте использовалась та же концепция
определения ориентации устройства с помощью акселерометра, чтобы показать
анимированную картинку собаки, когда дисплей обращен к земле. Здесь изучалась
анимация спрайтов с помощью встроенных функций спрайтов и применение некоторых
эффектов изображения в реальном времени, включенных в SDK.
Наклонная микроигра. После этих демонстраций снова был разработан более длинный
прототип, основанный на входе акселерометра. Результатом стала мини-игра, целью
которой было провести коробку через случайно сгенерированный лабиринт, наклоняя
устройство из стороны в сторону. В коробке была простая физика, реализованная с
помощью уравнений прямолинейного ускоренного движения. В этой демонстрации
использовалась структура состояния игры, предоставленная другим разработчиком
Playdate на официальных форумах, Ником Манье.
После знакомства с Lua SDK разработка снова перешла к изучению интерфейса C путем
создания различных прототипов.
Hello World на языке C. Первый проект представлял собой модификацию примера
проекта Hello World на языке C, который распространяется вместе с SDK. В нём текстовая
строка «Hello World» перемещается по экрану подобно логотипам старых DVD-плееров.
Развивая эту простую демонстрацию, я включил фоновое изображение и выполнил
рендеринг текста в режиме рисования NXOR (это означает, что пиксели текста,
перекрывающие черные, инвертируют свой цвет). Текст стирается путем рисования поверх
него только необходимого прямоугольника фонового изображения, что повышает
производительность за счет исключения полноэкранных вызовов отрисовки.
Hello World на языке C++. Тот же пример был реализован на C++. В этой версии целью
эксперимента была компиляция и запуск кода C++ на Playdate, поскольку этот язык не
является официально поддерживаемым. Изучив пример, включенный в SDK, и изменив
конфигурации CMake, демо-версия была успешно скомпилирована и запущена на
устройстве.
В ходе этого процесса стало очевидным большое ограничение, которое умаляет
преимущества, которые C++ может дать при разработке Playdate: в консоли отсутствует
реализация стандартной библиотеки C++. Тем не менее, в языке есть полезные функции,
для работы которых не требуется стандартная библиотека, например классы, наследование
или шаблоны.
6.1. Знакомство с Playdate 29
Ритмическая игра. После недель разработки на C и C++ я вернулся на Lua, чтобы быстро
создать прототип ритм-игры. В духе классических музыкальных игр, таких как серия Guitar
Hero, Osu! или японские аркадные автоматы. Эта игра состоит из серии падающих нот,
синхронизированных с песней, которую игрок должен ударять в такт. Этот отход от языка
C был сделан для того, чтобы отдать приоритет скорости и простоте разработки, а также
сосредоточить внимание прототипа на игровом дизайне, дизайне взаимодействия и
наличии закрытого продукта.
Ни один из прототипов, начиная с раннего доказательства концепции Доктора Марио,
вообще не использовал рукоятку, что, возможно, является самой знаковой особенностью
консоли. Исследование и использование характеристик, которые делают Playdate
уникальным, является одной из основных целей данной диссертации; пришло время чудаку
сыграть центральную роль в игровом процессе, поэтому концепция этой игры была
задумана вокруг него.
Геймплей следующий: на заднем плане играет песня, центр экрана занимает круг, а игроки
управляют дугой, которая движется по нему в соответствии с текущим углом поворота
рукоятки. Используя эту дугу, игрок должен ловить точки, обозначающие «ноты»,
падающие к центру круга. Чтобы игра приносила удовлетворение, эти ноты должны быть
синхронизированы с музыкой и аранжированы, отражая ее характеристики, такие как ритм,
голоса и общая энергия.
Я реализовал простой конечный автомат для переключения между меню и игровым
процессом игры. Это было сделано с помощью класса GameManager, который содержит
таблицу Lua, ссылающуюся на логику и функции рендеринга текущего состояния.
Изменение состояний осуществляется путем вызова GameManager.changeState() с
функциями обновления и рендеринга, а также дополнительной функцией инициализации в
качестве параметров. Когда этот метод вызывается, он сохраняет функции в таблице
GameManager, а затем один раз выполняет функцию инициализации.
В этой прототипной версии игры есть только три игровых состояния. Первый — это
состояние загрузки, которое в полной версии будет использоваться для загрузки ресурсов
при открытии игры. В настоящее время все, что делает это состояние, — это мгновенно
переходит в следующее состояние — состояние меню. В состоянии меню игроков встречает
титульный экран и музыка. В полной версии появятся другие параметры меню,
реализованные в отдельных состояниях игры; но на данный момент простое нажатие
кнопки A в меню переключает во внутриигровое состояние, в котором начинается игровой
процесс.
Шаблоны нот необходимо было создавать вручную, и этот процесс выиграл бы от
воспроизведения звука, временной шкалы и визуализации формы волны. Audacity,
30 Разработка
программа для редактирования звука с открытым исходным кодом, отвечает всем этим
требованиям и позволяет помечать определенные точки аудиофайла, что делает ее
идеальной для этой работы. Был написан простой парсер для перевода тегов Audacity,
содержащих информацию о времени и угле, во внутриигровые заметки. Более подробную
информацию об этом и других аспектах прототипа можно найти в приложении А.
6.2.1. Концепция
TinySeconds будет платформером с видом сбоку, в котором игрок должен добраться до цели
за одну секунду или меньше, уделяя особое внимание высокоскоростному игровому
процессу, четкому управлению и быстрой реакции. Уровни будут одноэкранными и
нарисованы с использованием тайловых карт.
На всех уровнях несколько препятствий и специальные механики бросят вызов игроку и
добавят разнообразия в игровой процесс. Поскольку уровни очень короткие по
продолжительности, игрокам придется последовательно проходить их серию, не
проигрывая, чтобы перейти к следующей группе.
Перед разработкой, прототип, был создан на фирменном игровом движке Unity3d, в
который можно поиграть в браузере*1. См. рис. 6.2.
Однако в архитектуре ECS мы не будем создавать новый класс для представления игрока;
мы создавали новую сущность, обычно простой идентификатор, и добавляли к ней
необходимые компоненты. Затем, во время цикла обновления, каждая система будет
предоставлять часть функциональности, получая все компоненты нужных типов и работая
с ними, часто не обращая внимания на то, кто ими владеет.
32 Разработка
После завершения разработки первой версии движка пришло время испытать её. Для
34 Разработка
6.3.4. Выводы
(а) Скриншот Super Mario Bros. (б) Повторяющиеся плитки одного цвета
Рис. 6.4: Пример тайловой карты в Super Mario Bros.
36 Разработка
Несмотря на то, что доступность хранилища данных в наши дни обычно не является
проблемой, тайловые карты по-прежнему широко используются, поскольку они
предлагают множество преимуществ. Прежде всего, они предлагают очень экономичный
способ создания графики, поскольку для формирования декораций и платформ необходимо
лишь небольшое количество повторно используемых чертежей. Они также позволяют
быстро выполнить итерацию дизайна на карте, поскольку внесение необходимых
изменений происходит так же быстро, как замена нескольких плиток. Еще одним
преимуществом является пространственное разделение уровней на строки и столбцы,
которое можно использовать (и будет использовать в нашей игре) для оптимизации
коллизий путем проверки только плиток, окружающих игрока. В случаях, когда
столкновение может быть менее точным, например, в ролевых играх, карту можно
разделить на сплошные и проходимые плитки, что делает проверку столкновений такой же
простой, как чтение логического значения из матрицы уровней.
Редактор тайловых карт с открытым исходным кодом Tiled будет использоваться для
создания всех уровней и наборов тайлов в этой игре. Более подробную информацию об этой
программе можно найти в приложении C.
Экран Playdate имеет разрешение 240x400 пикселей. Если мы найдем все делители для
обоих этих размеров и выберем общие, мы получим размеры квадратных плиток, которые
могут идеально покрыть весь экран. Проекционный дисплей (HUD), такой как количество
жизней, счет, время или другая информация, отображаемая графически, обычно занимает
часть экрана, поэтому другие размеры плиток, оставляющие запас по одной из осей, также
могут быть полезны. Размеры квадратных плиток, заполняющих одну или обе оси экрана
Playdate, следующие:
В итоге для этой игры был выбран размер тайла 32х32 пикселей. Поскольку экран не
делится на эти размеры, у нас остаются поля по осям ширины и высоты. Это решается
добавлением дополнительного ряда плиток внизу карты, который будет виден лишь
наполовину. Запас высоты будет использоваться для рисования простого HUD для таймера
уровня.
Одной из полезных функций Tiled является возможность иметь несколько слоев тайловой
карты, что позволяет создавать эффекты глубины или разделять плитки на сталкивающиеся
и не сталкивающиеся, а также многие другие варианты использования. В нашем случае
карты будут иметь слой переднего плана, представляющий платформы, по которым игрок
может ходить и сталкиваться, и фоновый слой, используемый для украшений и другой
неконфликтной графики. См. рис. 6.5.
После создания или изменения карты тайлов экспортируются из Tiled в формате JSON и
сохраняются вместе с остальными файлами игры. Playdate SDK предоставляет анализатор
и средство записи JSON, которые будут использоваться для загрузки уровней во время
выполнения: класс json_decoder внутри pd_api.h.
6.4. Итерация 2 - Тайловые карты и движение 37
При разработке загрузки файла JSON, в версии 0.12.0 Playdate SDK была обнаружена
ошибка: return 0 в shouldDecodeTableValueForKey и shouldDecodeArrayValueAtIndex,
методы должны пропускать чтение значения в паре с текущим ключом, но использование
этой возможности приводило к сбою в работе приложения. Отчет об ошибке был
отправлен в официальные репозитории GitLab и исправлен в следующем выпуске SDK.
Более подробную информацию по этому вопросу можно найти в приложении B.1.
Как только карта тайлов прочитана и сохранена в виде идентификаторов тайлов в массиве
(рис. 6.6 б), наступает время рендеринга уровня. Для каждого слоя тайловой карты,
упорядоченного сзади вперед, мы перебираем идентификаторы тайловой карты,
определяем часть текстуры набора тайлов, которая соответствует этому тайлу, и рисуем ее
в соответствующей строке и столбце экрана.
Для определения части набора плиток, которая будет отрисована, мы исходим из того, что
Tiled присваивает плиткам их идентификаторы на основе их положения в наборе плиток,
начиная с 1 в левом верхнем углу плитки и идя слева направо (6.6a). Зная ширину тайла в
пикселях и количество столбцов в наборе тайлов, мы можем получить смещение в пикселях
по координатам x и y, по соглашению называемое (u, v) соответственно, используя
следующие уравнения*2:
*1. Операцию нижнего деления в 6.1б можно опустить из-за поведения целочисленного деления C по
умолчанию, которое отбрасывает десятичные дроби, уравнивая результат. Реализации на других языках или
использование других типов данных должны включать его, чтобы формула работала.
6.4. Итерация 2 - Тайловые карты и движение 39
В этой итерации также были реализованы основные движения игрока и чтение ввода.
Движение обрабатывается в системе физики: позиция игрока увеличивается при нажатии
правой кнопки на D-Pad или уменьшается при нажатии левой кнопки. При запуске прыжка
переменной сущности vy (скорость по оси Y) присваивается большое значение, так как в
играх-платформерах прыжки ощущаются лучше, если они взрывные, а не ускоренные;
затем каждый кадр на протяжении всего прыжка игрок перемещает вы пикселей и
переменная уменьшается. Прыжок заканчивается, когда игрок возвращается в позицию y, с
которой он начал.
Тот же метод, который использовался для обрезки плиток из набора плиток, использовался
для изменения спрайта игрока в зависимости от выполняемого действия (движение влево
или вправо, стояние на месте и прыжок). Спрайты хранятся в листе спрайтов, который
представляет собой одно изображение, содержащее разные кадры анимации, а не
отдельные изображения. См. рис. 6.7.
6.4.5. Выводы
6.5.1. Столкновение
Теперь, когда загрузка уровня и движение игрока были реализованы, пришло время
запрограммировать столкновения с окружающей средой. Как следует из названия,
платформер основан на движении, когда игрок точно прыгает по разным платформам,
чтобы избежать пробелов и других опасностей. По этой причине очень важно иметь
надежную систему столкновений. Это означает реализацию системы, которая позволяет
игроку стоять на поверхности разной высоты и не позволяет ему перемещаться внутри стен
или потолков.
Принятый подход состоит из следующих шагов: во-первых, определение того, какие
плитки перекрывают спрайт игрока; затем перебираем эти плитки в массиве тайловой карты
и проверяем, являются ли они сплошными или пустым пространством; наконец, если
плитка сплошная, вычисляется, с какой стороны игрок вошел на плитку, и устраняется
перекрытие в этом направлении.
Определение того, какие тайлы перекрываются, игрок начинает с перевода его положения
из координат пикселей в координаты тайла, как в строке и столбце тайловой карты:
В случае горизонтальной оси: если позиция x игрока меньше позиции плитки, игрок
сталкивается с левой стороны и перемещается на overlapx пикселей в этом
направлении; в противном случае игрок входит с правой стороны и перемещается на
такую же величину вправо.
Для вертикальной оси: если позиция y игрока меньше позиции плитки, игрок идет
сверху и перемещается на overlapy пиксели вверх; в противном случае игрок
сталкивается снизу и перемещается на такую же величину вниз.
До сих пор скорость игрока была привязана к частоте кадров в игре, поскольку его позиция
увеличивалась при каждом вызове обновления на фиксированную величину. В некоторых
случаях этот подход может сработать, но это не лучшее решение, поскольку возможное
падение частоты кадров приведет к заметному замедлению действия. Вместо этого
большинство функций, связанных со временем, таких как движение игрока, анимация или
недуги, наносящие урон с течением времени, должны основываться на таймерах,
независимых от частоты кадров.
Обычный способ сделать это — использовать так называемое дельта-время: время,
прошедшее между каждым вызовом обновления. Он состоит из простой системы,
называемой первым делом в цикле обновления, которая хранит две переменные: DeltaTime
и last_time. В Playdate SDK есть функция для получения текущего времени в
миллисекундах, измеренного с произвольного момента времени: playdate->system-
>getCurrentTimeMilliсекунды(). При каждом обновлении система сохраняет в переменной
DeltaTime текущее время минус last_time, в котором хранится отметка времени последнего
вызова системы. Таким образом, система получает время, прошедшее между вызовами
обновления. Наконец, он обновляет last_time текущим временем, подготавливая его к
следующему обновлению. DeltaTime — это общедоступная глобальная переменная,
которую другие системы могут использовать для своих нужд.
До сих пор игрок мог двигаться и прыгать только на одной высоте, как если бы он стоял на
ровной земле, потому что не было системы обнаружения столкновений, которая могла бы
сообщить ему, приземлился ли он на платформу. Таким образом, все прыжки заканчивались
на одной высоте. Реализовав столкновение, необходимо было обновить систему физики,
управляющую движением игрока, чтобы позволить игроку стоять на разных уровнях земли.
42 Разработка
Это реализуется путём добавления «гравитации»: если игрок не прыгает, его положение
по оси Y увеличивается каждый кадр*3, что позволяет ему упасть с уступов. Возможность
стоять на более высоких платформах появляется в результате того, что система
столкновений корректирует перекрытия с платформами, поэтому для этого не требуется
никакого дополнительного программирования.
Еще одним заметным изменением стало использование DeltaTime (преобразованного в
секунды) для определения количества, необходимого игроку для перемещения каждого
кадра, в результате чего скорость определялась в пикселях в секунду, а не как
фиксированная величина за обновление.
6.5.4. Выводы
Несмотря на то, что внедрение первой системы столкновений является важной вехой для
этого проекта, тестирование показало, что текущий способ устранения перекрытий дает
плохие результаты в определенных ситуациях. Платформы могут состоять из более чем
одной плитки, но используемый метод рассматривает каждую плитку, как если бы это была
отдельная платформа, что приводит к разрешению столкновений путем толкания игрока
внутрь соседней плитки (что, в свою очередь, толкает его дальше, что приводит к странному
телепортация). Особенно это происходит при столкновении с платформой снизу.
Тем не менее, основная механика игры присутствует в своей базовой форме, что имеет
основополагающее значение для дальнейшего развития. Две цели этой итерации не были
достигнуты: реализация изменения уровня и способ завершения уровня. Это замедление
можно объяснить недооцененной сложностью обнаружения столкновений, а также
отсутствием инструментов отладки или моделирования для игр C Playdate в Windows на
момент написания статьи. Эти невыполненные цели и проблемы с системой столкновений
будут решены в будущей итерации.
Вообще говоря, есть две категории, в которые может попасть реакция на два
перекрывающихся объекта в игре: одна из них — столкновение, которое моделирует
взаимодействия между физическими телами, исправляя пересечение между ними и обычно
добавляя соответствующие силы реакции; другой — триггеры, которые выполняют
функцию при входе в перекрытие. Триггеры являются фундаментальной функцией плат-
*1. По умолчанию в играх Playdate начало системы координат расположено в верхнем левом углу экрана,
поэтому значения y увеличиваются по направлению к нижнему краю экрана.
6.6. Итерация 4 - Вход в игровой цикл 43
форменных игр, поскольку их можно использовать для определения того, когда игрок
достигает цели уровня, собирает предметы или касается опасностей или врагов, наносящих
урон, а также многих других приёмов и механик.
В этой итерации были реализованы триггеры, которые использовались для изменения
уровня при касании цели и перезапуска уровня, если игрок касается плитки опасности. Для
этого была создана триггерная система и добавлена ее функция обновления в основной
цикл. Эту систему необходимо обновить после системыboundingTiles, поскольку она
зависит от рассчитанных в ней координат тайла и ограничивающей рамки.
Система триггеров вызывается для каждой сущности, но действует только на те из типов,
которые должны реагировать при контакте с игроком, в нашем случае на goal_type (тип
цели) и enemy_type (тип врага). Во-первых, система должна определить, перекрываются ли
игрок и триггерный объект, что будет верно, если выполняется следующее условие: для
каждой оси позиция объекта больше или равна позиции игрока, но меньше, чем позиция
игрока плюс размер ограничивающей рамки на этой оси.
Если игрок и объект-триггер перекрываются, система возвращает значение перечисления
на основе требуемого ответа: trigger_none, trigged_goal или trigged_death. В основном
цикле обновления для этого возвращаемого значения выполняется оператор переключения,
и необходимые действия выполняются для каждого случая.
Теперь, когда в игре появились цели, опасности и место появления игроков, а в будущем
появится больше элементов, стало ясно, что необходимо найти лучший способ размещения
этих объектов.
В качестве распространённого решения этой проблемы было выбрано: размещение этих
объектов в виде тайлов в редакторе тайлов, а в момент загрузки уровня идентификация этих
тайлов и выполнение необходимых действий (например, создание соответствующих
сущностей или установка цели и позиции появления игрока).
Идентификаторы этих специальных тайлов были сохранены в константах. Для тех, кому
требовалось создание или установка положения уникального объекта, в данном случае цели
и плиток появления, в файле tilemap.h была создана глобальная переменная для хранения
их положения и координат плиток.
В средстве чтения JSON обработчик didDecodeArrayValue() был изменён для выполнения
оператора переключения идентификатора считываемого фрагмента, выполняя
необходимые операции в случае специальных фрагментов. Хотя это может показаться
дорогостоящим, компилятор очень оптимизирует операторы переключения, особенно по
сравнению с операторами if-else, поскольку случаи внутри оператора переключателя не
зависят от предыдущих. Добавление этого шага не привело к заметному увеличению
времени загрузки уровня.
Для статических плиток опасностей и целей их индекс плитки в массиве карты тайлов
преобразуется в координаты плитки по следующей формуле:
Затем для появления игрока его индекс тайла переводится в положение в пиксельных
координатах по следующей формуле:
Когда игрок касается опасности или таймер достигает нуля, уровень необходимо
перезапустить. Это делается путем вызова простого метода в файле main.c, который
перемещает игрока и цель в их позиции появления, и вызывает sys_timer_reset(). Установка
положения цели осуществляется потому, что метод перезапуска также вызывается при
изменении уровней.
Реализация загрузки следующего уровня, когда игрок касается цели, была довольно
простой: как объяснялось в подразделе, посвящённом системе триггеров, если игрок
перекрывает цель, система триггеров возвращает значение перечисления trigger_goal в
основной цикл, который, в свою очередь, вызывает метод loadAndDrawMap(). Этот метод
просит менеджера объектов удалить все объекты, помеченные тегом enemy_type,
поскольку опасности уникальны для каждого уровня; вызывает util_tilemap_loadLevel(),
передавая путь к следующему файлу карты тайлов в качестве параметра; преобразует его в
новое полностью белое растровое изображение; и, наконец, вызывает метод restart() для
сброса таймера и положения цели и появления игрока.
6.7. Итерация 5 45
Путь к файлу JSON каждого уровня хранится в массиве в файле main.c в том порядке, в
котором они должны появляться. Затем индекс в массиве текущего уровня сохраняется в
переменной-счетчике. При загрузке следующей карты тайлов счетчик увеличивается, и
путь к следующему индексу массива передается загрузчику карты тайлов.
Единственная трудность, обнаруженная во время разработки этой функции, заключалась
в манипуляциях со строками в C. До сих пор путь к изображению набора тайлов получался
путем чтения его из файла тайлмапов, где он фигурировал под полем «image». Программа
«Tiled» экспортирует этот путь как относительный маршрут, а это означает, что перед ним
должна быть добавлена строка «/media/», чтобы оборудование Playdate могло найти файл.
Это работало хорошо, когда функция загрузки тайловой карты вызывалась только один
раз, но при последовательных вызовах путь к изображению набора тайлов добавлялся к
самому себе, что делало маршрут неверным. Присвоение значения перед вызовом функции
конкатенации strcat() в качестве попытки сброса переменной не привело к каким-либо
изменениям. Некоторое время было посвящено исследованию этой проблемы, но, зная, что
все тайловые карты имеют один и тот же набор тайлов, было решено статически задать
маршрут к изображению и решить эту проблему в будущем, если возникнет необходимость.
6.6.6. Выводы
Это была очень плодотворная итерация, в которой были реализованы оставшиеся основные
функции. Единственная задача, которая не была выполнена, — это добавление
движущегося врага, но взамен был реализован таймер и его HUD. Возможность чтения
специальных плиток, которые порождают сущности или другие объекты, также является
значительным шагом вперед в развитии игры, поскольку это функция, общая для всех
интерактивных объектов на уровне, и она упростит дизайн уровней.
6.7. Итерация 5
Целью этой итерации было разработать и реализовать новую игровую механику, чтобы
добавить разнообразия в дизайн уровней. Кроме того, некоторые области кода нуждались
в рефакторинге, чтобы улучшить читаемость и избежать повторения кода. Одними из таких
случаев были преобразования между системами координат, которые выполнялись на
протяжении всего проекта с повторением кода и плохой разборчивостью.
Это взаимодействие используется для нового типа плитки, которая становится твердой или
неосязаемой в зависимости от положения рукоятки: тумблера. Диапазон поворота рукоятки
разделен на две части посередине, и если угол поворота рукоятки меняет область, блоки
переключателей переходят в противоположное состояние, работая как переключатель.
(a) Рукоятка под углом более 270° (б) Рукоятка под углом менее 270°
Рис. 6.9: Головоломка с противоположными блоками переключения
есть столкновение, путём добавления или удаления сплошного блока под объектом. Для
этого была реализована вспомогательная функция getTilePointer(tileCoords, LayerName). Эта
функция выбирает слой карты тайлов с указанным именем и возвращает указатель на
позицию в массиве тайлов, которая соответствует координатам, переданным параметром.
Для изменения типа плитки достаточно просто записать в этой позиции другой ID плитки.
Рендеринг этих блоков был простым; Функция render_update_one_entity() в системе
рендеринга была изменена и теперь имеет оператор переключения, который обрабатывает
рисование игрока или блоков переключения. Система переключения устанавливает
координаты листа спрайтов объекта в положение ВКЛ или ВЫКЛ при изменении его
состояния, поэтому их рендеринг так же прост, как рисование этой области листа спрайтов.
В результате получилась увлекательная игровая механика, достаточно простая, чтобы её
можно было понять с первого взгляда, но позволяющая решить множество задач по
дизайну. Блоки могут иметь противоположные состояния переключения, как показано на
рис. 6.9, позволяющая игроку переключаться между состояниями, чтобы открывать разные
планировки уровней. В других случаях, игрок должен быстро открыть строение, чтобы
добраться до высокого места, как на рис. 6.10. На рис. 6.11, игрок должен раскрыть
переключатели, чтобы перепрыгнуть через шипы, но затем быстро отключить их, чтобы
достичь цели.
(a) Игрок должен позволить блокам (б) Цель находится под блоками, поэто-
пересекаться му их нужно снова отключить
Рис. 6.11: Головоломка, требующая быстрой координации ВКЛ и ВЫКЛ блоков.
ния между системами выполнялись внутри кода, даже если некоторые из них выполнялись
одинаково в нескольких местах проекта.
В этой итерации для обработки этого повторяющегося кода был создан новый служебный
класс, предоставляющий функции для выполнения этих общих операций. Реализованные
функции осуществляют преобразование между координатами пикселя и тайла по формуле
6.2, между индексом массива тайловой карты и координатами тайла по формуле 6.5 и между
индексом массива тайловой карты и координатами пикселей по формуле 6.6.
6.7.3. Выводы
6.8. Итерация 6
(overlapx, overlapy) = (min (playerwidth, bbox row · tile size) − |playerx − tilex|,
min (playerheight, bbox row · tile size) − |playery − tiley|) (6.7)
6.8. Итерация 6 49
Рис. 6.12: Старый метод 6.12a приводил к чрезмерной коррекции перекрытия оси у.
Зеленый: коррекция оси Y Красный: коррекция оси X
Синий: размеры спрайта Фиолетовый: ограничительная рамка.
состояние победы для отображения экрана поздравлений (6.13) при достижении цели на
последнем уровне.
6.8.4. Выводы
Хотя одна из целей не была достигнута, а именно добавление новой механики, эта итерация
улучшила ощущения от игры и ее отзывчивость, а также исправила некоторые весьма
существенные ошибки, возникшие со времен первых итераций. Реализация состояния игры
также будет использоваться в будущем с добавлением меню и других возможных
состояний.
6.9. Итерация 7
Целью седьмой итерации было реализовать новую механику и создать новые уровни,
дополняющие уже реализованные.
Был создан новый тип, имитирующий структуру Vector2i, уже определенную в нашем
проекте. Как упоминалось ранее, структуры Vector2 содержат два числа, сохраненные в
полях x и y. Определение структуры такого типа является обычной практикой при
разработке игр, поскольку многие переменные идут парами, например положение в
системах координат, координаты текстуры или физические значения в 2D-средах. Новый
тип отличается от существующего тем, что его значения хранятся в виде чисел с плавающей
запятой, а не целых чисел.
6.9.2. Бамперы
Уровни, созданные до этого момента, были ограничены расстоянием, которое игрок может
пройти за 2,5 секунды, а механика была сосредоточена на управлении окружающей средой
с помощью блоков переключения. Чтобы преодолеть это ограничение, следующая
механика должна была влиять на движение игрока, позволяя создавать уровни, на которых
игрок преодолевал большие расстояния или достигал более высокого уровня, чем то, что
дает ему прыжок.
Механика, разработанная с учетом этого, - это бамперы, особый тип плитки, который
придает игроку мгновенную скорость в том направлении, на которое он указывает.
Некоторые из применений этой механики — это пружинные платформы, которые толкают
игрока вверх, заставляя его подпрыгивать; турбопэды, ускоряющие игрока вперед или
назад по горизонтали; ловушки, загоняющие игрока в тупик; или диагональные бамперы,
которые одновременно толкают игрока вперед и вверх. См. рис. 6.14 для скриншотов
уровней.
Был создан новый тип объекта, названный buger_type, а также новая переменная Vector2f,
называемая bugerForces, которая хранит вектор скорости, который будет применен к игроку
при контакте. Кроме того, в плеер была добавлена новая переменная для хранения его
скорости по оси x.
Всего имеется восемь плиток, по одной для каждого направления, которое может иметь
бампер (влево, вправо, вверх, вниз и по диагонали). Они нарисованы в виде стрелки,
указывающей туда, куда будет приложена сила.
52 Разработка
6.9.3. Вывод
6.10. Итерация 8
6.10. Итерация 8 53
Целями этой итерации были разработка карты мира, улучшение заставок, улучшение
конечного автомата и добавление главного меню.
В своей первой итерации бамперы добавляли игроку скорость в каждом кадре, который они
перекрывали. Иногда это приводило к слишком большим движениям, что не приносило
желаемого эффекта. Чтобы исправить это, к сущностям была добавлена новая логическая
переменная под названием bumperTouchedPlayer: когда игрок не перекрывает бампер,
переменная имеет значение false. Если они начинают перекрываться, система триггеров
применяет скорость, а затем устанавливает для неё значение true, чтобы не применять её
снова при следующем обновлении. Как только игрок перестаёт касаться плитки, система
триггеров возвращает логическое значение false. Это означает, что скорость будет
добавлена только после начала перекрытия с бампером.
Чтобы добавить механике больше глубины, нажатие на бампер теперь сбрасывает таймер
прыжка, позволяя игроку инициировать еще один прыжок и получить больше импульса,
чем просто падение на него. Для этого необходимо точно рассчитать время нажатия кнопки
прыжка, что повышает потолок навыков в игре. Сравнение можно увидеть на рис. 6.15.
Вся логика, связанная с состоянием игрового процесса, была перенесена из файла main.c в
отдельный файл состояния. Одним из дополнений была опция меню паузы, позволяющая
игроку выйти с уровня в внешний мир в любой момент; Playdate SDK позволяет добавлять
пользовательские параметры в меню паузы с помощью функции playdate->system-
>addMenuItem(). Выбор выхода из уровня с помощью этой опции вызывает функцию
changeToState(), передающую состояние overworld (внешний мир) в качестве параметра.
Когда игрок проходит последний уровень мира, состояние меняется на состояние победы.
Состояния, которые работают как меню, например, внешний мир и состояние меню, имеют
схожий набор функций. Их можно резюмировать как возможность циклически
переключаться между наборами опций с помощью навигационной панели, отображать
растровое изображение для обозначения активной опции и выполнять действие при
нажатии кнопки A на одной из них.
Эта общая функциональность была реализована в виде системы, позволяющей повторно
использовать ее между состояниями меню: система menu_hotspot. Помимо обязательной
функции обновления, эта система имеет функцию конфигурации для инициализации
переменных-членов при изменении состояния.
В этом файле был объявлен новый тип структуры под названием Hotspot, который
представляет собой опцию меню. В его состав входят переменная Vector2i, обозначающая
положение курсора выбора, когда эта горячая точка активна, и функция, вызываемая при
её выборе.
6.10. Итерация 8 55
При входе в состояние, использующее эту систему, оно должно вызвать функцию
sys_menu_hotspot_config() со следующими параметрами:
Массив со структурами Hotspot, присутствующими в этом меню.
Длина массива горячих точек.
Указатель на изображение (с использованием типа LCDBitmap Playdate SDK),
которое будет отображаться в качестве фона. В текущей реализации эта система не
отображает текст или любую другую графику для параметров, и они должны быть
встроены в это фоновое изображение или отрисованы состоянием каким-либо
другим способом.
Указатель на изображение, которое будет отображаться в качестве курсора.
Vector2i со смещением поворота изображения курсора, чтобы его центр имел
относительную координату, отличную от верхнего левого угла по умолчанию.
Все эти параметры хранятся в статических переменных внутри класса и используются для
обеспечения функциональности системы. Индекс активной в данный момент опции из
массива также сохраняется в переменной.
В функции обновления системы pd->system->getButtonState() используется для запроса
ввода устройства. Эта функция возвращает кнопки, которые в данный момент нажаты, либо
нажаты и отпущены в течение предыдущего цикла обновления. Затем на основе нажатых
кнопок выполняется комплекс операций. Если нажата кнопка A и установлен указатель
текущей горячей точки на функцию, эта функция вызывается. В противном случае, если
нажата стрелка вправо или вверх, текущий индекс горячей точки увеличивается или
устанавливается на ноль, если после текущего индекса больше нет опций. Наконец, если
оба этих условия не выполняются и нажата стрелка влево или вниз, текущий индекс горячей
точки уменьшается или устанавливается на последнее значение массива, если активный
индекс находится в первой позиции.
Метод обновления заканчивается рисованием фонового изображения, а затем
изображения курсора в позиции текущей активной горячей точки за вычетом смещения
поворота.
x = a + (b − a) × t (6.10)
Эта функция была реализована в новом служебном файле под названием lerp, который
является широко распространенной квазиаббревиатурой линейной интерполяции.
Плитка земли теперь меняется в соответствии с текущим миром, усиливая ощущение прог-
ресса, когда игрок видит изменение окружающей среды. Это реализовано в классе утилиты
tilemap путём установки пути к изображению набора тайлов мира при загрузке карты
тайлов в зависимости от папки, к которой она относится.
6.10.8. Вывод
Это была одна из самых плодотворных итераций на данный момент. Поток приложения
наконец-то дополнен представлением общего мира и улучшенным конечным автоматом.
Благодаря системе часов возможность повторного прохождения игры значительно
улучшилась, что позволяет игрокам практиковаться на уровнях и улучшать свои
результаты. Наконец, было много других дополнений, которые добавляют совершенство
игровому опыту. Минимально жизнеспособный продукт (MVP) уже разработан, и все, что
осталось, — это доработать продукт, чтобы закрыть его.
6.11. Итерация 9
Несмотря на то, что состояние внешнего мира было реализовано в прошлой итерации,
ресурс, используемый в качестве фона на экране выбора уровня, представлял собой карту-
6.11. Итерация 9 59
заполнитель, без визуализации того, какие уровни были разблокированы, а какие нет.
Была создана новая система под названием drawOverworld, которая получает из состояния
верхнего мира массив горячих точек, представляющих уровни и их размер, количество
разблокированных уровней в текущем мире и индекс отображаемого мира. В системе есть
функция рендеринга, которая рисует эллипс в положении каждой горячей точки, а затем
соединяет их линиями, выделяя линии между разблокированными уровнями.
Сначала создаются два растровых изображения: одно для хранения полученного изоб-
ражения, а другое для однократного рисования маркера уровня и последующего его пов-
торного использования для каждой горячей точки. Маркер уровня изображается в виде
короткого цилиндра в ортогональной перспективе. Он визуализируется путем рисования
двух эллипсов с помощью playdate->graphics->fillEllipse() для верха и основания и соеди-
нения их главной оси с прямоугольником той же ширины с помощью playdate->graphics-
>drawLine(). Верхний эллипс рисуется последним и окрашен в другой цвет, чтобы улучшить
иллюзию искусственного трёхмерного изображения.
Функции рисования Playdate SDK, которые получают LCDColor, также вместо сплошного
цвета принимают LCDPattern; они определяются как массив из 16 байтов, первые восемь
представляют цвета строки по 8 пикселей каждая, а последние восемь — значение маски
тех же строк. Чтобы понять представление строки в виде байта, нужно взглянуть на её
двоичную форму: число 11110000 будет означать строку, в которой первые четыре пикселя
белые, а последние четыре чёрные. Если байт представляет маску, значение бита 1 означает
сплошной пиксель, а значение 0 — прозрачный пиксель.*4
Первый и последний элементы массива имеют линию, идущую от их позиции до крайнего
левого или крайнего правого края экрана соответственно, чтобы обозначить, что прогресс
начинается в прошлых мирах и продолжается в следующих. В зависимости от
визуализируемого мира для путей между разблокированными уровнями используется
другой шаблон.
Во время этой итерации была создана музыкальная дорожка, которая служила как фоновой
музыкой, так и звуковым индикатором производительности проигрывателя. Продол-
жительность трека составляет тридцать пять секунд, что соответствует продолжительности
таймеров всех уровней мира вместе взятых (2,5 секунд на уровень × 14 уровней = 35 секунд).
Музыкальный трек используется в системе flyingClock следующим образом: если плеер
находится на одном уровне или выше часов, музыка воспроизводится на полную громкость.
Если они отстают, громкость музыки снижается линейно в зависимости от того, на сколько
*4. Эту концепцию можно визуализировать с помощью инструмента https://ivansergeev.com/gfxp/, который
позволяет графически создавать шаблоны для использования при разработке на Playdate.
60 Разработка
Последней важной недостающей функцией было то, что если общий таймер мира истёк, то
часы блокировали переход на следующий уровень. Также должен быть некий запас тай-
мера, когда часы достигнут крайнего предела с демонстрацией обратного отсчёта.
Чтобы включить эти функции, система flyingClock была изменена. Теперь, начиная с
функции инициализации, она получает в качестве параметра уровень, выбранный из внеш-
него мира. Если этот уровень не является первым в мире, система ничего не делает, так как
часы должны пройти от первого уровня к последнему. В класс была добавлена логическая
переменная sys_flyingClock_isGoalOpen. Если она равна true, это означает, что игрок может
разблокировать следующий мир после достижения последней цели; если false, переход в
следующий мир заблокирован. Это логическое значение истинно только в том случае, если
игрок начал на первом уровне мира и таймер часов не истёк.
Самое большое изменение коснулось функции обновления системы. Здесь был создан
новый метод для расчёта положения часов в зависимости от ситуации, в которой они
находятся. Если часы находятся на уровне, предшествующем последнему, их движение
представляет собой интерполяцию между позицией появления игрока и целью, добавляя
смещение по высоте на 1/4 высоты спрайта игрока, чтобы он плавал над плитками, а не
позиционировал себя поверх них. Если часы находятся на последнем уровне, это смещение
увеличивается для цели, чтобы она плавала выше над ней. Затем, если счётчик часов уровня
превысил максимальное значение часов мира, в течение некоторого времени часы будут
плавать над целью и отображать обратный отсчёт до нуля. После прохождения итераций
заданного поля часы вылетают за правую границу экрана, устанавливая цель в качестве
начальной позиции и точку за пределами экрана в качестве конечной позиции. Для
логического значения sys_flyingClock_isGoalOpen устанавливается значение false, и в
последствии они не рисуются снова.
Текст обратного отсчёта рисуется с помощью функции pd->graphics->drawText() в позиции
часов минус двойное вышеупомянутое смещение высоты. В отличие от часов, обратный
отсчёт виден с любого уровня, чтобы игрок знал, сколько времени ему осталось, чтобы
пройти мир.
Во внутриигровом состоянии было добавлено условие для загрузки следующего уровня,
поэтому оно происходит только в том случае, если значение sys_flyingClock_isGoalOpen
истинно.
Поскольку условие для блокировки перехода в следующий мир было установлено, пришло
время графически представить его игроку. Была создана новая система под названием
«fence», которая рисует изображение забора, если sys_flyingClock_isGoalOpen имеет зна-
чение false. Забор представляет собой растровое изображение высотой в три плитки,
которое рисуется перед воротами; прыжок игрока достигает лишь чуть выше двух плиток,
что делает это препятствие непреодолимым. Как и в случае с системой переключения
плиток, динамические столкновения реализуются путем изменения массива тайловой
карты, чтобы пометить плитки под забором как сплошные. Это делается с помощью ранее
реализованной функции util_tilemap_getTilePointer(), которая, учитывая имя слоя, строку и
столбец, возвращает указатель на элемент массива плитки, которая находится на этом слое
и в этой позиции. Чтобы упростить систему, координаты плитки жёстко закодированы в
месте, которое занимает забор на экране. Это можно улучшить, преобразуя положение
забора в координаты плитки и увеличивая в два раза значение строки, или добавляя тип
плитки в карту тайлов и анализируя её в функциях загрузки карты тайлов. Текущая реали-
зация быстрая и понятная, поэтому она была выбрана вместо более сложного подхода.
6.11.5. Выводы
Последняя итерация в разработке игры оказалась очень плодотворной, так как заметно
улучшилось качество игры и были добавлены оставшиеся ключевые функции. Добавление
блокировки прогресса в следующий мир на основе общего таймера завершает
разработанную механику игры, а программно нарисованный верхний мир добавляет
визуальную индикацию прогресса и особый стиль, необходимый в этой области.
Музыкальный трек, написанный для игры, следует за продвижением игрока по уровням,
побуждая его совершенствовать свои способности и пытаться пройти игру в том же темпе.
Конечно, закрытие следующего мира ставит перед игроком неизбежный вызов, но музыка
и летающие часы намекают на эту цель ещё до того, как она будет представлена.
Было проведено более неформальное тестирование, в результате которого игроки,
привыкшие к платформерным играм, смогли перейти во второй мир за несколько попыток,
в то время как менее опытные игроки были разочарованы этим ограничением. Способ
настройки сложности может открыть двери для большего числа потенциальной публики,
сделанный таким образом, чтобы было явно указано, как спроектирован геймплей, и при
этом были предоставлены более простые варианты. На данный момент варианты сложности
выходят за рамки этого проекта, и основное внимание уделяется созданию сложной игры,
в которой можно победить благодаря настойчивости. Состояние внешнего мира задумано
как способ отработать определённые уровни и подготовиться к их последовательному
прохождению, поэтому этот аспект предусмотрен в текущем дизайне.
Учитывая всё это, мы можем утверждать, что игра успешно вышла на первую полно-
ценную версию.
7. Заключение
В этой главе обобщаются итоговое состояние проекта, уроки, извлеченные в ходе его
разработки, а также личная оценка всей работы.
После девяти итераций разработки TinySeconds достигла состояния, которое можно считать
первой законченной версией, со всей установленной базовой механикой, двадцатью
восемью уровнями, разделенными между двумя мирами, оригинальной музыкальной
дорожкой и тремя универсальными типами специальных препятствий, которые
обеспечивают множество возможностей дизайна уровней.
Большая часть запланированных механик была включена в конечный продукт, хотя
некоторые из них были переработаны в процессе разработки, чтобы улучшить их, заменить
ими не лучшие подходы или сбалансировать сложность игры. Использование итеративной
методологии обеспечило проекту такой высокий уровень манёвренности: поскольку каждая
итерация основывается на предыдущих, вскоре определяются способы улучшения
запланированных функций, и разработку можно легко развернуть.
Разработка TinySeconds дала мне возможность понять и улучшить свои навыки во всех
областях разработки игр: от написания игрового движка до проектирования уровней и
механики, программирования физики и игровой логики, создания 2D-графики и, наконец,
написания музыки для неё. Работа над новым, ограниченным оборудованием также
оказалась очень познавательным опытом и улучшила мои способности решать проблемы
как инженера. Этот опыт, а также полная разработка игры и прототипов
задокументированы в данной бакалаврской диссертации, которая послужит справочной
информацией для будущих разработчиков.
7.2. Улучшения
63
64 Заключение
Работа над этой бакалаврской диссертацией, над TinySeconds и над небольшими играми
стала очень познавательным опытом, охватывающим все области разработки игр и
обучение разработке для новой платформы.
Во-первых, многое было изучено об архитектуре и возможностях Playdate. Создание и
разработка игры на языке низкого уровня включало понимание характеристик, сильных и
слабых сторон консоли, а также их анализ для выявления лучших практик. Эти
соображения были учтены при разработке игры для ее оптимизации и получения хорошей
производительности.
Фаза создания прототипов проекта обеспечила понимание различных способов создания
игр для консоли, их преимуществ и недостатков. Программирование на Lua требовало
умеренного обучения благодаря более высокому уровню абстракции, управлению памятью
со сборкой мусора, а также более богатому набору функций Playdate SDK. Язык C,
напротив, оказался менее дружелюбным к новичкам, но преуспел в плане
производительности, позволяя играм Playdate достигать более высокой частоты кадров
благодаря более низкому уровню абстракции, ручному управлению системной памятью и
отказу от запуска. на виртуальной машине. Все эти знания, полученные на этапе создания
прототипа, были затем применены при разработке TinySeconds, что значительно облегчило
решение проблем.
Разработка TinySeconds также оказалась полезной в плане процессов планирования, анали-
7.4. Личные выводы 65
Лично я очень доволен работой, проделанной в рамках этого проекта, поскольку Playdate
была для меня неизведанной территорией, когда я впервые начал писать бакалаврскую
диссертацию, и теперь я могу сказать, что хорошо понимаю консоль и способы разработки
для неё.
Участие в программе Developer Preview было потрясающим опытом: наблюдать за
расцветом сообщества, развитием консоли и ростом ожиданий публики в течение года было
действительно захватывающе. Это также позволило мне взглянуть на процессы компании
Panic, пока они работали над подготовкой консоли к выпуску, и я рад, что внёс свой вклад,
сообщая об ошибках и помогая протестировать SDK и оборудование.
Кроме того, возможность завершить первую версию игры TinySeconds, которой я могу
гордиться, вселила в меня уверенность в будущих разработках и заставила задуматься о
возможности дальнейшей доработки продукта и подготовки его версии к выпуску. это как
название запуска.
Источники
Карр, Р. (2007, март). Тест скорости: Switch против if-else-if. Взят 21/03/2021 г. с http://
www.blackwasp.co.uk/speedtestifelseswitch.aspx
Дюран, FJG (август 2020). Gameengine ecs: Эффект звёздного поля. Взят 11/11/2020 с
https://www.youtube.com/watch?v=ighkMUM9-Ww
67
68 Источники
Panic. (2020b, Август). Inside playdate with [Руководство по SDK на языке C].
Septhon, M. (2021a, June). Daily driver: преобразование RGB в 1 бит. Взят 06/16/2021, с
https://blog.gingerbeardman.com/2021/06/05/channelling-rgb-into-1bit/
69
A. Проведённые эксперименты
A.1. Lua
71
72 Проведённые эксперименты
нится до того, как это произойдет, таймер сбрасывается на ноль, избегая мерцания.
Lua Playdate SDK предоставляет некоторые эффекты обработки изображений, которые
можно использовать во время выполнения. В этой демонстрации эффект
playdate.graphics.image:drawBlurred использовался через случайные промежутки времени,
чтобы придать изюминку изображению инструкции.
Анимация была реализована путём инициализации таблицы playdate.graphics.imagetable из
файла .gif и создания из неё playdate.graphics.animation.loop. Эти классы являются
стандартным решением для анимации серии изображений в SDK, с возможностью указать
задержку между кадрами в конструкторе. Анимация обновляется автоматически при
вызове функции draw().
Наконец, были добавлены обложка, звук запуска и анимация. Эти ресурсы отображаются
в меню Playdate при выборе игры, а анимация воспроизводится в полноэкранном режиме
вместе со звуковым эффектом. Эти элементы устанавливаются путём изменения файла
pdxinfo в корне каждого проекта. В нём в поле imagePath необходимо указать папку внутри
исходного каталога Source, в которой хранятся ресурсы для меню. Внутри этого пути папка
с именем launchImages содержит кадры анимации запуска, названные по номеру кадра,
начиная с «1.png». Другое поле в файле pdxinfo, называемое launchSoundPath, хранит путь
из исходной папки Source к пользовательскому звуковому эффекту запуска.
Скриншоты на рис. А.3
Последним проектом Lua была музыкальная игра, в которой основное внимание уделялось
вводу рукоятки. В этой игре рукоятка управляет углом движения курсора вокруг середины
экрана. Во время воспроизведения песни ноты перемещаются к центру экрана по заданному
шаблону, и цель пользователя — поймать их курсором, когда они попадут в его зону
действия.
В этом проекте представлена наша первая реализация на Lua конечного автомата игры. В
нём состояния игры должны иметь одну функцию для обновления логики, другую для
рендеринга и дополнительную функцию инициализации, которая будет вызываться при
переходе в это состояние. Затем в таблице GameManager сохраняются ссылки на функции
активного состояния, которые затем используются для независимого вызова функций
обновления и рендеринга из основного цикла приложения. См. рисунки A.5в, A.5a и A.5б.
Поскольку этот тип игры требует точных движений и восприятия, целью оптимизации
было достижение скорости 50 кадров в секунду. Учитывая это, подход к рендерингу был
разделен на два этапа: во-первых, отрисовка всех элементов в том виде, в каком они были
на предыдущем кадре, с инвертированными цветами для выборочной очистки экрана, и, во-
вторых, отрисовка текущего кадра. Как упоминалось в главе 3.1.1, этот тип рендеринга на
основе областей рекомендуется для приложений Playdate вместо полноэкранного подхода,
что позволяет нам достичь более высокой частоты кадров и продлить срок службы батареи
консоли.
Чтобы ритм-игра приносила удовольствие, действие должно быть точно синхро-
низировано с сопровождающей её музыкой. Как правило, этого лучше достичь с помощью
контента, созданного вручную, поэтому было важно иметь возможность легко написать
сценарий, о том в какое время и под каким углом «заметки» будут иметь влияние.
Выбранный путь заключался в использовании Audacity*2, бесплатного звукового редактора
с открытым исходным кодом, в качестве графического интерфейса пользователя (GUI).
Таким образом, дорожка меток может использоваться для представления воздействий нот,
определяя угол в качестве текста метки, а также использовать представление формы волны
и инструмент меток регулярных интервалов для синхронизации их с музыкальной
дорожкой*3 (см. рисунок A.5д). Наконец, был написан простой анализатор текста для
преобразования меток, экспортированных из проекта Audacity, в их игровое представление.
Файлы, экспортированные из Audacity, структурированы следующим образом: для
каждого тега указано время их начала, время окончания, их текст и символ новой строки.
Обратите внимание, что теги в Audacity могут иметь длительность, действуя как маркер
региона, хотя для наших целей эта функция не используется. Анализатор открывает файл с
помощью playdate.file.open(), а затем разделяет каждую строку, используя пробел в
качестве разделителя, сохраняя начальное значение как временную метку новой заметки и
текстовое значение как её угол.
Чтобы это приложение выглядело как родное, были использованы настраиваемые поля
системного меню для выхода в главное меню из песни, а также оповещение о включении
по умолчанию, если оно было убрано во время игры. В какой-то момент была добавлена
частичная поддержка перевёрнутой ориентации системы — экспериментальная функция
для игроков-левшей, но в итоге от неё отказались.
A.2. C
которая находилась под текстом в каждом кадре. Я также использовал для текста режим
рисования NXOR, чтобы выделить его на фоне. См. скриншот A.6.
A.3. C++
То же, что и A.2.1, но реализовано на C++. Целью этого эксперимента было запуск кода
C++ на Playdate, поскольку этот язык не является официально поддерживаемым, но мне
нужен был ООП-подход. Используя настройки C и проверив пример проекта C++,
включённый в SDK, я изменил конфигурации CMake, успешно скомпилировал и запустил
демо-версию.
A.4. Pulp
Стремясь открыть разработку игр для новичков, разработчик Panic создал веб-инструмент
под названием Pulp для создания игр для Playdate. Pulp позволяет пользователям любого
уровня навыков быстро создавать простые игры на основе плиток в стиле RPG.
Заинтересовавшись охватить все возможности разработки Playdate в этой бакалаврской
диссертации, я принял участие в предварительном просмотре бета-версии Pulp и в качестве
теста создал небольшую игру. См. рис. А.7.
Playdate C SDK реализует собственный анализатор JSON для чтения и записи файлов,
написанных на этом языке разметки. В документации к этой функции упоминается
возможность пропускать пары ключ-значение JSON по отдельности вместо их анализа.
При написании кода загрузки тайловых карт в главе 6.4 я пытался использовать эту
возможность, чтобы ускорить чтение файлов карт за счёт отказа от обработки ненужных
полей JSON; но затем каждый раз, когда он пытался прочитать файл, программа вылетала
с ожидаемой ошибкой decode_table expected ','.
Из-за отсутствия отладки Playdate игр на C в Windows, источник ошибки было трудно
отследить; файл JSON был правильным, и запятая не пропущена, поэтому сообщение об
ошибке не помогло. Через некоторое время стало очевидно, что парсер JSON
преждевременно прерывает чтение файла и приводит к сбою всего приложения.
Для проверки правильности этого предположения была разработана упрощённая
демонстрация, в которой приложение пыталось открыть файл, прочитать его содержимое и
вывести его на консоль отладки. Дальнейший анализ этой демонстрации доказал, что
теория верна, и поэтому в трекере проблем Playdate GitLab была зарегистрирована ошибка.
Вот полный отчет об ошибке:
B.1.1.1. Настройка
Версия — обнаружена в 0.11.1, присутствует в 0.12.0.
OС — Windows
B.1.1.2. Шаги
*1. GitLab — онлайн-платформа для управления проектами и исходным кодом Git https://about.gitlab.com/
*2. </uploads/1351873859769340764f58bb4a29d5c9/BugJSON.zip>
81
82 Отчёты об ошибках
B.1.1.5. Частота
Постоянная
B.1.1.6. Строгость
Незначительная
B.1.2. Вывод
После публикации отчёта, член команды Playdate подробно остановился на этом вопросе,
предоставив ещё один пример кода, и проблема была решена в выпуске Playdate 1.0.0 SDK.
В то же время был добавлен рабочий пример использования парсера C JSON и
дополнительная документация по этой функции.
На той же неделе произошла предыдущая ошибка, ещё во время главы 6.4, был выпущен
Playdate 12.0.0 SDK и обновление прошивки. Было важно принять эту версию, поскольку
В.2. Ошибка обрезки прямоугольника 83
она изменила способ рисования графики и растровых изображений из C SDK, поэтому пре-
дыдущие функции рисования устарели и не работали в последней версии прошивки (1.0.0).
К сожалению, в этом обновлении появилась ошибка в создании прямоугольных сечений.
Это нарушило отрисовку тайловых карт, что ещё больше замедлило ход выполнения этой
итерации.
Прямоугольные сечения используются в моей игре для выбора части листа плитки, соот-
ветствующей рисуемой плитке. После некоторого тестирования стало ясно, что положение
тайла влияет на размер обрезки. Значение координаты x добавлялось к ширине
прямоугольного обрезка, и то же самое происходило с координатой y и высотой. К счастью,
оказалось, что это можно легко обойти: вычесть положение прямоугольного сечения из его
масштаба.
Для наглядной демонстрации этого эффекта я разработал небольшое тестовое приложение
(рис. В.1), сравнивающее результаты рисования обрезанного изображения с устаревшими
функциями, новыми сломанными функциями и обходным путём. Он состоит из
полноэкранного изображения с прямоугольным обрезком, который перемещается по
экрану, демонстрируя, как его положение влияет на его размеры. Был дополнительный
пошаговый режим, чтобы было легче увидеть эту зависимость.
B.2.1.1. Настройка
Версия — 12.0.0
ОС — Windows
B.2.1.2. Шаги
B.2.1.5. Частота
Повторяющаяся
Постоянная
B.2.1.6. Строгость
Высокая
B.2.2. Вывод
На этот отчёт об ошибке не было сделано ни одной рецензии. Та же ошибка была поднята
в разговоре на официальном сервере Playdate Discord другим разработчиком, и было
выпущено исправление для версии SDK 1.0.0.
C. Программа Tiled
Как следует из описания на веб-сайте, «Tiled — это редактор тайловых карт общего
назначения для всех тайловых игр, таких как ролевые игры, платформеры или клоны
Breakout» (Lindeijer, 2019). Это бесплатная программа с открытым исходным кодом,
позволяющая сохранять и загружать карты тайлов в формате JSON. В этом проекте в
качестве редактора уровней будет использоваться Tiled.
85
D. Простой конечный автомат
87
(гугл перевод от С.Айрата)