Как написать игру для sega

Время на прочтение
12 мин

Количество просмотров 40K

image

Даже сегодня люди по-прежнему продолжают создавать новые игры для старых консолей. Мы называем их «homebrew». Иногда это способ реализовать детскую мечту о создании игры для консоли, на которой играл ребёнком. Но кроме того это ещё и интересная задача для любого дизайнера или разработчика игр: ретро-железо обладало множеством ограничений, бросающих вызов креативности авторов. В 90-х для профессиональных разработчиков эти ограничения были привычными. Сегодня, когда у нас есть более совершенные инструменты, создание игр для подобных машин стало гораздо доступнее.

В прошлом году я написал статью о создании игры для Game Boy. Сегодня я поделюсь своим опытом создания трёх игр для домашней консоли SEGA Mega Drive/Genesis. Вероятно, благодаря современным мощным инструментам, это самая простая машина для разработки homebrew-игры. Например, мне даже удалось создать (очень простую) игру всего за 60 минут, и она на самом деле запускается на консоли!

Игры

В прошлом году, чтобы отметить 30-летнюю годовщину выпуска Genesis / Mega Drive, я создал игру под названием «30 Years of Nintendon’t». Как понятно из названия, это дань памяти лучшим играм консоли и агрессивному маркетингу, который SEGA использовала в то время (например, слоган «Genesis Does what Nintendon’t»).


В этой игре мы управляем евангелистом SEGA, который должен убедить игроков забыть о своих NES и SNES, потому что на Genesis игры лучше! Игра была бесплатно выпущена онлайн на 30-й день рождения консоли. В неё можно сыграть здесь: https://drludos.itch.io/30-years-of-nintendont

С тех пор я также создал для консоли две (небольшие) аркадные игры:

«Break An Egg»

«MeteoRain»

Все эти 3 игры продаются на картридже, изданном Cote Gamers:

http://cotegamers.com/shop/en/genesis-mega-drive/43-test.html

Он поставляется в красивой пластиковой коробке, как настоящие винтажные игры, и с набором почтовых открыток.

Можно ли использовать Unity, Unreal Engine или Godot?

Сожалею, но эти движки не способны экспортировать игры на SEGA Mega Drive/Genesis (может быть, пока?). Но не волнуйтесь, существует ещё один инструмент, который столь же полезен, как и эти популярные современные движки, и в то же время разработан специально для Mega Drive/Genesis: SGDK. Это фреймворк, позволяющий писать код для этой машины на языке C. Это намного проще и быстрее, чем изучать язык ассемблера, который был обязателен в 90-х. Но и это ещё не всё. SGDK также имеет инструменты, упрощающие создание графики и звуков. Вы можете создавать графику в любимом редакторе 2D-графики (от Photoshop до GIMP или Asesprite), и она будет преобразована автоматически, если вы соблюдаете аппаратные ограничения (подробнее об этом ниже). То же относится и к звуку: вы можете создавать звуковые эффекты в файлах .wav, а для музыки использовать различные трекеры. И последнее: SGDK имеет мощный «Sprite Engine», упрощающий большинство технических задач по отображению подвижных изображений на экране. Но насколько «просто» это в реальности? В статье я расскажу о собственном опыте работы.

На сцене появляется Blast Processing

Как вам скажет любой программист старых систем, если писать программу на C вместо ассемблера, то это снизит производительность игры. И это правда. Но затраты в разных ретро-консолях сильно различаются. Они зависят от используемых ЦП, а также от эффективности современных компиляторов, создающих код для таких винтажных процессоров. К счастью, в Mega Drive/Genesis используется процессор Motorola 68000, который достаточно хорошо подходит для работы с особенностями языка C. Особенно справедливо это по сравнению с другими ЦП, например с 65816 консоли SNES. Хотя сам по себе он и мощен, ЦП SNES сложно заставить выполнять написанные на C программы с достаточной скоростью. То есть когда дело доходит до homebrew-разработки на языке C, «Blast Processing» оказывается не маркетинговым мифом, а реальностью!

Насколько быстро может выполняться код на C?

Вот пример из личного опыта — анимированный GIF моей игры MeteoRain, в котором спрайты 45 метеоритов без задержек движутся с частотой 60fps:

А вот ещё один пример взрыва из Break An Egg, состоящего из 40 частиц, движущихся с частотой 60fps:

Если взять для примера старые игры, то хорошим примером мощи ЦП Mega Drive/Genesis является Contra Hard Corps. Хотя игра разрабатывалась на ассемблере, а не на C (ведь это было в 90-х), разработчики могли использовать огромную вычислительную мощь для перемещения множества спрайтов по экрану и создать запоминающихся боссов с кучей подвижных частей и масштабными взрывами.

Для сравнения, Contra III на SNES была ограничена мощью ЦП и вместо перемещения кучи спрайтов Konami использовала уникальные графические эффекты, на которые был способен только видеочип SNES, например, эффект прозрачности.

Несмотря на то, что обе игры одинаково интересны, они отлично подчёркивают технические различия между SNES и Mega Drive/Genesis. Подробнее о том, как Konami создавала каждую из игр под соответствующую консоль, можно прочитать в статье «Making of Contra III and Hard Corps» из Retro Gamer №176.

Графические ограничения

Поскольку вычислительная мощь не является проблемой, основным «бутылочным горлышком» консоли Genesis / Mega Drive для homebrew-разработчиков стали её графические ограничения. А если конкретнее, то ограниченное количество отображаемых ею цветов. Хотя в общем консоль может генерировать 512 уникальных цветов, одновременно на экране может быть не более 64 цветов. Ой-ёй. Для сравнения: SNES могла отображать одновременно 256 цветов из палитры 32768 уникальных цветов. То есть художникам графики нужен был большой талант, чтобы игры на SEGA выглядели так же хорошо, как на консолях конкурентов. И во многих случаях им это удавалось. Несмотря на цветовые ограничения, многие старые игры для Mega Drive/Genesis выглядят красивыми, например, Comix Zone, the Story of Thor, Streets of Rage II, Ranger-X, Gunstar Heroes, Sonic 1-2-3, и т.д.

Comix Zone (1995 год)

Но в других случаях игры Mega Drive/Genesis выглядели довольно блекло по сравнению с версиями для SNES. Один из самых известных примеров — это Street Fighter II, где цветовые ограничения консоли очень заметны:

Оптимальное использование ограниченных палитр (4 палитры по 16 цветов у Genesis против 16 палитр по 16 цветов у SNES) было большой сложностью для разработчиков, для завершения проектов у которых обычно было очень мало времени. Сегодня талантливые участники SEGA-сообщества способны модифицировать некоторые игры для оптимизации использования цветов. Например, Габриель Пирон творит чудеса с играми наподобие Castlevania Blood Lines, Golden Axe, Outrun или с вышеупомянутой Street Fighter II:

(Сверху — цветовая палитра оригинала, внизу — модифицированная Габриелем)

С учётом этого, вы можете использовать для создания изображений любимый графический редактор, но быть очень аккуратным при выборе цветов! Если вам не удастся соответствовать 512-цветовой палитре, прошитой в оборудовании, то это не проблема: SGDK автоматически преобразует изображение в палитру Mega Drive/Genesis. Но когда дело касается общего количества используемых и отображаемых на экране цветов, то вам придётся заниматься этим самостоятельно!

Заставляем объекты двигаться

Чтобы отобразить что-нибудь на экране, нужно скопировать графические данные из ПЗУ в видео-ОЗУ. Графический процессор не имеет прямого доступа к данным в ROM, но способен считывать данные из RAM. Поэтому чтобы фон или анимации спрайтов менялись, вам нужно регулярно копировать новые данные из ROM в RAM. А общее количество видео-RAM (VRAM) ограничено: всего 64 КБ. По сравнению с размерами игровых ROM (от 512 КБ до 8 МБ) это довольно мало.

Узкое место №1: передача во VRAM

Если вы создаёте очень маленькую игру с небольшим количеством графических элементов, то можно просто раз и навсегда загрузить их в память при запуске игры. Но большинству игр для демонстрации различных анимаций требуется регулярно обновлять графические данные во VRAM. И обычно это вызывает множество проблем!

Количество графических данных, которое можно передать из ROM во VRAM в каждом кадре (т.е. 60 раз в секунду), ограничено. Если конкретнее, то это 7524 байт на кадр, то есть около 7 КБ. Когда необходимо отображать огромные анимированные спрайты плюс анимированные фоны, как в Street Fighter II, то это ограничение постоянно мешает. И ведь это только первая проблема.

Узкое место №2: ограничения спрайтов

У 8/16-битных консолей ещё одна сложность заключается в общем количестве спрайтов, которое может отображать оборудование. Обычно ограничений два: одно для общего количества спрайтов на экране, другое для общего количества спрайтов в одной горизонтальной строке. У 8-битных консолей, таких как NES и Master System, эти ограничения очень суровы: не более 8 спрайтов на горизонтальную строку, а общее количество не должно превышать 64 спрайта. Значит ли это, что мы можем отобразить на экране 64 раза Соника или Марио? Нет, имеются в виду «аппаратные спрайты», которые меньше того, что обычно называется спрайтом. Вот пример из Super Mario Bros для NES, где для отображения одного спрайта Марио требуется несколько аппаратных спрайтов размером 8×8 пикселей.

(Источник: https://nesdoug.com/2018/09/05/06-sprites/)

У 16-битных консолей ограничения спрайтов немного снижены. На Mega Drive/Genesis можно отображать до 80 аппаратных спрайтов. Но отдельный аппаратный спрайт может иметь размер до 32×32 (ранее — всего 8×8 пикселей). Вот несколько примеров разрезания «аппаратных спрайтов» на Mega Drive/Genesis. Вы заметили, что Соник отрисовывается всего из двух аппаратных спрайтов, а для Марио на NES требуется 8 спрайтов?

(Источник: https://www.patreon.com/posts/new-resource-and-26847248)

Узкое место №3: приоритеты спрайтов

Как будто это было недостаточно сложно, у Mega Drive/Genesis есть ещё одна специфическая черта: цепочка спрайтов. При наложении двух спрайтов консоль должна узнать, какой из них нужно отображать поверх другого. В Master System и в большинстве консолей Nintendo (NES, Game Boy, SNES) спрайты задавались в большой таблице и их порядок в таблице использовался для задания приоритета отображения. Не нужно говорить, что это довольно сложный в применении инструмент, потому что нужно постоянно переопределять эту большую таблицу для изменения приоритета спрайтов.

На Mega Drive/Genesis компания SEGA использовала более гибкую и простую систему. Как и в других консолях, все спрайты имеют номер индекса в большой таблице, но он не используется для задания приоритета спрайта. Вместо этого каждый спрайт хранит ссылку на следующий спрайт, который будет отображаться. В конечном итоге консоль создаёт цепочку спрайтов, начиная со спрайта 0. Каждый спрайт знает только то, что идёт после него. Благодаря этому гораздо проще изменять приоритеты спрайтов без упорядочивания всех спрайтов в большой «таблице ссылок на спрайты». Но честно говоря, хотя такая система проще в использовании, она всё равно раздражает при создании игры с часто меняющимся приоритетом спрайтов, например в beat’em up.

«Простой» способ: спрайтовый движок SGDK

Отображение подвижных спрайтов и прокручивающихся фонов с учётом всех этих узких мест — довольно сложная задача. С ней боролись все разработчики 90-х, и многие homebrew-разработчики продолжают это делать. Например, Мэтт Филлипс выпустил в 2018 году великолепную игру Tanglewood, но создал её при помощи инструментов 90-х. Он программировал игру на языке ассемблера и использовал официальный аппаратный комплект разработки, спроектированный примерно 30 лет назад. С его стороны это был осознанный выбор, он хотел полностью ощутить на себе методику разработки винтажной игры.

(Источник: https://www.gamasutra.com/view/news/325040/New_game_classic_hardware_Developing_Tanglewood_on_a_Sega_devkit.php)

Но для людей не столь опытных и терпеливых, как Мэтт, современные инструменты могут снизить количество проблем, создаваемых техническими аппаратными ограничениями. Мы уже упоминали, что язык C обычно проще, чем ассемблер. Также стоит добавить, что использование высокоточного эмулятора Mega Drive/Genesis наподобие Blast’em в сочетании с Everdrive Flash Cart для тестирования на реальном оборудовании делает разработку игры практически безболезненной по сравнению с разработкой при помощи старых аппаратных эмуляторов в сочетании с набором DOS-инструментов. Но повторюсь, главный подарок современности — это SGDK.

Запишите игру на SD-карту, вставьте её в Mega Everdrive, и тестируйте игру на реальном оборудовании!

Среди прочего, SGDK также имеет мощный «Sprite Engine», сильно облегчающий жизнь разработчику. Это набор функций для обработки отображения спрайтов аналогично тому, как это делается в современных фреймворках. Во-первых, вам больше не нужно волноваться о количестве аппаратных спрайтов, требуемых для каждого кадра анимации: SGDK автоматически займётся этим. Кроме того, при работе с приоритетами спрайтов вам не придётся вручную задавать цепочку спрайтов, потому что SGDK делает это при помощи простого значения «приоритет спрайта», которое можно задавать каждому спрайту, как в любом современном фреймворке. И последнее: SGDK также обрабатывает все передачи во VRAM автоматически! Да, вы всё правильно поняли, благодаря Sprite Engine вам больше вообще не понадобится заботиться о VRAM! Можно просто приказать SGDK отобразить «анимацию X спрайта Y», и он выполнит всю технически сложную работу за вас. Если вам когда-нибудь приходилось работать с этими вещами вручную, то это покажется магией — всё просто и понятно!

Один только Sprite Engine фреймворка SGDK превращает Mega Drive/Genesis в самую простую для разработки игр ретро-консоль, особенно если у вас есть опыт работы с игровыми фреймворками на современных платформах. SGDK бесплатен и имеет открытые исходники, но если вы хотите помочь его автору Стефану Даллонгевилю в дальнейшей разработке ретро-магии, то это можно сделать на Patreon: https://www.patreon.com/SGDK/

Звук

Звук на ретро-железе тоже сам по себе является настоящим испытанием, и каждая консоль в этом отношении уникальна. В Mega Drive/Genesis для этой задачи есть отдельный процессор Z80, который напрямую подключен к звуковому оборудованию. То есть для создания звука и музыки для консоли сначала нужно написать «драйвер аудио»: программу для процессора Z80. В 90-х каждая игровая студия разрабатывала собственный драйвер аудио и подстраивала его под каждую игру. Ближе к концу жизни консоли появились стандартизированные драйверы, например GEMS компании Sega of America, использованный примерно в сотне игр западных разработчиков.

Создание хорошего звука для 8/16-битных консолей — это вопрос не только музыкальных, но и программистских навыков! Некоторые игры с хорошей музыкой или звуковыми эффектами были испорчены плохим драйвером аудио. Самым известным примером опять же является Street Fighter II. Драйвер аудио в этой игре страдает от багов, искажающих все голосовые сэмплы. Много лет спустя Стефан Даллонгевиль (автор SGDK и признанный волшебник) выполнил реверс-инжиниринг этого драйвера аудио и исправил его баги. В результате вы теперь можете играть в Street Fighter II с кристально чистым криком «Hadoken!». Вот видео, в котором можно услышать разницу:

Но давайте вернёмся к homebrew-разработке и любимому SGDK. Он имеет возможность выбора драйверов аудио, которые можно подбирать под требования своей игры. Например, есть PCM-драйвер, позволяющий воспроизводить по одному звуковому эффекту за раз в очень высоком качестве (например, голосовые сэмплы). Но также существует XGM-драйвер, способный одновременно проигрывать музыкальный трек + 4 звуковых эффекта. Лучше всего то, что вы можете переключаться между драйверами аудио во время игры. Например, в 30 Years of Nintendon’t голосовые сэмплы на экране заставки и конца игры воспроизводятся PCM-драйвером для высокого качества. Но во время игры мне нужно было воспроизводить одновременно несколько звуковых эффектов, поэтому я использовал XGM-драйвер.

Что касается самих ресурсов, то для их создания можно использовать современные инструменты. Как я говорил выше, для звуковых эффектов можно просто взять файлы .wav, которые автоматически преобразуются в нужный формат. Для музыки стандартным инструментом является Deflemask — трекер, способный создавать музыку для звукового оборудования различных 8/16-битных консолей. У меня нет большого опыта в создании музыки, потому что использованные в моих играх композиции написаны талантливыми чиптюн-исполнителями: Minerscale (Break An Egg) и Warlord (MeteoRain). Оба они пользовались Deflemask и экспортировали свои творения в формат VGM, который SGDK может преобразовывать для использования с собственными драйверами аудио.

Создать игру для Mega Drive/Genesis за 60 минут? Вызов принят!

Гейм-джемы — превосходный способ применения своих навыков гейм-дизайна и разработки игр: насколько хорошую игру вы сможете создать с нуля за ограниченное время (обычно 48 или 72 часа)?

С точки зрения времени, самым жёстким джемом является 0h Game Jam. Как понятно из названия, он проводится каждый год во время перехода на зимнее время. Вы начинаете создавать игру в 02:00, в ночь перевода часов. Проработав 60 минут, вы выпускаете игру онлайн в тот самый момент, когда часы возвращаются с 03:00 на 02:00. В прошлом году я решил поучаствовать в этом джеме. Только что завершив «30 Years of Nintendon’t», я накопил определённый опыт в создании игры для Mega Drive/Genesis и был вдохновлён простотой SGDK. Но сможет ли SGDK быть достаточно простым, чтобы я смог создать 16-битную homebrew всего за 60 минут? Вот ответ:

60 минут работы

Давайте будем честными, результат совершенно не впечатляет. Баланс игры ужасен, а в генераторе случайных чисел есть огромный баг, из-за чего игра становитя непроходимой после пятого яйца. Кроме того, в ней нет фонового изображения, звука и т.д. Но всё-таки это новая игра для Mega Drive/Genesis, созданная с нуля за 60 минут, и запускаемая на настоящей консоли. Не знаю, как для вас, но для меня это уже своего рода достижение!

Более того, поскольку идея базового геймплея имела определённый потенциал, я продолжил работать над игрой в течение ещё множества часов. В результате у меня получилась довольно интересная аркадная игра, в которую можно сыграть здесь. Также она доступна на картридже 30 Years of Nintendon’t вместе с третьей игрой, уникальной для этого выпуска на физическом носителе — MeteoRain.

Много часов дополнительной работы!

От homebrew к indie

Хотя homebrew обычно является нишей для любителей (вроде меня), некоторые консоли и сегодня являются вполне коммерческой платформой для инди-студий. Прекрасным примером этого стали две недавно выпущенные игры. С одной стороны, Tanglewood была полностью разработана на ассемблере, как в 90-х, что сделало конечный результат ещё более впечатляющим. С другой стороны, столь же потрясающая Xeno Crisis была разработана с помощью SGDK и полностью воспользовалась преимуществом комфорта и простоты современных инструментов.

Tanglewood (2018 год)

Xeno Crisis (2019 год)

Итак, если вам нравятся ретро-игры, почему бы не создать сегодня свою первую игру для Mega Drive/Genesis?

В конце концов, чтобы получить SGDK, достаточно перейти по ссылке: https://github.com/Stephane-D/SGDK

В заключение

Надеюсь, вам понравилась эта статья о создании трёх игр для SEGA Mega Drive/Genesis при помощи современных технологий. Если вы хотите сыграть в мои игры на собственной консоли или просто поддержать мою работу, то можете купить картридж в красивой коробке с релизом моих игр у издателя Cote Gamers: http://cotegamers.com/shop/en/genesis-mega-drive/43-test.html

Если вас интересует только цифровой релиз (т.е. ROM-ы), то все мои ретро-игры можно найти здесь: http://drludos.itch.io/

Если вам понравилась моя работа и вы можете себе это позволить, то поддержите меня финансово на Patreon. Мои игры обычно выпускаются онлайн как freeware, а некоторые из них также продаются на физических картриджах. Поддержав меня на Patreon, вы также получите доступ к бета-версиям моих игр, а также к различным создаваемым мной прототипам (в том числе заброшенным проектам).

Предисловие.

Скачайте готовый файл проекта с MEGA.

В этом уроке мы создадим платформер на SGDK. Да, так быстро, и сразу в пекло, приступим.

Что потребуется для платформера.

А потребуется следующее:

  • Поместить спрайт игрока в центр экрана.
  • Скроллить карту мира в зависимости от координат игрока.
  • Остановить эту карту, если игрок столкнулся с тайловой сеткой (добавить коллизию).
  • Дать игроку гравитацию (просто, прибавлять +1 к y, каждый кадр).
  • Позволить игроку прыгать (если на земле, отнять 10 по y).

Пусть вас не пугает обширный список пунктов, единственный значимый пункт это:

  • Остановить эту карту, если игрок столкнулся с тайловой сеткой (добавить коллизию).

Остальные, мы либо изучали, либо до них не трудно догадаться. А теперь вопрос, каким образом, мы поймем тайл на карте является твердым?

Разбираемся в массиве коллизий.

На помощь приходит массив коллизий.

Массив коллизий — это 2-х мерный массив хранящий информацию о твердости тайла. Например:

  • 0твердый тайл (нельзя пройти)
  • 1прозрачный тайл (можно пройти)

Используя координаты игрока, можно узнать тайл, на котором стоит игрок. Делается это так.

u16 x_tile = x_pos / 8; //так
u16 x_tile = x_pos >> 3; //или так

1-я и вторая строка идентичны, но 2-я предпочтительней, т.к. работает быстрее. Позиция игрока делится на 8, потому-что тайл 8×8 px.

Зная координаты тайла, можно подставить их в массив коллизий (level), и узнать, твердый ли, тайл на котором стоит игрок, или нет. Делается это по такому шаблону.

level[y_tile][x_tile]

x_tile, y_tile — тайл на котором стоит игрок.

Т.е. если level[y_tile][x_tile] вернет 0, то нельзя пройти, если 1, то можно.

Далее, нам понадобится Python скрипт, который будет генерировать массив коллизий из картинки. Скрипт писал я сам, специально для этой задачи.

Генерируем массив коллизий.

Сперва, скачайте скрипт с MEGA.

И перетащите картинку с картой на col_generator.exe, скрипт сам сгенерирует нужные файлы.

Альтернативный способ. Запустите col_generator.exe через консоль, со следующими аргументами.

col_generator.exe изображение [цвет_фона_hex]
  • изображение — имя изображения, с расширением.
  • цвет_фона_hex — цвет фона в hex формате. Его можно не указывать, тогда будет использоваться первый цвет в палитре. Если тайл содержит ничего, кроме цвет_фона, тогда скрипт понимает, что тайл прозрачный. В противном случае — твердый.

Поместите скрипт в папку с изображением (за пределами проекта), затем, запустите скрипт со следующими аргументами.

col_generator.exe bg.png #005500

Изображение bg.png, находится в папке res проекта.

После запуска, скрипт сгенерирует 2 картинки и текстовый файл.

На картинке bg_WithCollision.png, твердые тайлы отмечены красным.

В файле bg_array.txt хранится массив коллизий, и размер карты в пикселях, который, в будущем, понадобится для ограничения камеры.

Массив коллизий получили, теперь разберем код платформера.

Ресурсы платформера.

В папке res, находится спрайт игрока, и карта мира.

Спрайт игрока(кружки) я сделал максимально квадратным, что-бы проще было увидеть область столкновений.

  • resources.res — мы разбирали в предыдущих статьях.
  • resources.h — создается автоматически, во время компиляции.

Код платформера.

Откройте main.c

Нас приветствует огромный массив коллизий, сгенерированный ранее.

Далее, идут структуры, в которых хранятся переменные игрока.

typedef struct {
	f32 x;
	f32 y;
} Point;

typedef struct {
	bool moving;
	bool is_on_floor;
	Point pos;
	Point spd;
} Player;

Т.е хранится:

  • двигается ли игрок
  • на земле ли игрок
  • позиция игрока
  • его скорость

Структуры разбирались в SGDK. Двигаем много спрайтов.

В функции main, заполняем переменные игрока — значениями.

plr.moving = FALSE;
plr.is_on_floor = FALSE;
plr.pos.x = FIX32(160);
plr.pos.y = FIX32(950);
plr.spd.x = 0;
plr.spd.y = 0;

Далее

SPR_init();
VDP_setPalette(PAL3, spr_cup.palette->data);
setCameraPosition(fix32ToInt(plr.pos.x),fix32ToInt(plr.pos.y));
setCameraPosition(fix32ToInt(plr.pos.x)-1,fix32ToInt(plr.pos.y)-1);

cup_obj = SPR_addSprite(&spr_cup, fix32ToInt(plr.pos.x), fix32ToInt(plr.pos.y), TILE_ATTR(PAL3, 0, FALSE, FALSE));

SPR_update();
  • Иницализируем спрайтовый движок SPR_init
  • Дважды вызываем setCameraPosition, что-бы получить картинку в первый кадр.
  • Добавляем спрайт на экран SPR_addSprite.
  • Делаем его видимым SPR_update

Установка позиции спрайта и скролл карты, осуществляется в setCameraPosition.

void setCameraPosition(s16 x, s16 y)
{
    if ((x-160 != camPosX) || (y-120 != camPosY))
    {
        camPosX = x-160;
        camPosY = y-120;
		
	if (camPosX < 0){
		camPosX = 0;
	} else if (camPosX > MAX_X-320) {
		camPosX = MAX_X-320;
	}		
	if (camPosY < 0){
		camPosY = 0;
	} else if (camPosY > MAX_Y-120) {
		camPosY = MAX_Y-120;
	}	
	SPR_setPosition(cup_obj, x-camPosX, y-camPosY);
        MAP_scrollTo(bga, camPosX, camPosY);
    }
}

Разберем данную функцию

camPosX = x-160;
camPosY = y-120;

Позиция камеры смещена в минус относительно координат игрока, таким образом, игрок находится в центре.

if (camPosX < 0){
	camPosX = 0;
} else if (camPosX > MAX_X-320) {
	camPosX = MAX_X-320;
}		
if (camPosY < 0){
	camPosY = 0;
} else if (camPosY > MAX_Y-120) {
	camPosY = MAX_Y-120;
}	

Ограничиваем камеру в рамках карты.

SPR_setPosition(cup_obj, x-camPosX, y-camPosY);
MAP_scrollTo(bga, camPosX, camPosY);

Устанавливаем позицию спрайта относительно координат камеры. Данная конструкция, обеспечивает перемещение спрайта из центральной позиции, при приближении к границам карты.

Перейдем к проверке коллизии.

Разбираем код коллизии.

bool checkCollision(s16 x, s16 y)
{
	plr.is_on_floor = FALSE;
	
	s16 y_tile = y >> 3;
	s16 x_tile = x >> 3;
	
	u8 player_width = 4;
	u8 player_height = 4;
	
	s16 leftTile = x_tile;
	s16 rightTile = x_tile+player_width;
	s16 topTile = y_tile;
	s16 bottomTile = y_tile+player_height;
	
	for(s16 i=leftTile; i<=rightTile; i++)
	{
		for(s16 j=topTile; j<=bottomTile; j++)
		{
			if(level[j][i] == 0) {
				if(j == bottomTile){
					plr.is_on_floor = TRUE;
				}
				return FALSE;
			}
		}
	}
	return TRUE;
}
  • Здесь, мы соотносим тайл, на котором стоит игрок (y_tile, x_tile) и т.к. игрок занимает не и 1 тайл, а 4.
  • Мы проверяем каждый тайл, который занимает игрок, в цикле, и если тайл твердый (значение level[y][x] == 0), то возвращаем FALSE (пройти нельзя).
  • Если тайл, с которым сталкиваемся (level[j][i] == 0), находится под игроком (j == bottomTile), значит, игрок находится на земле (plr.is_on_floor = TRUE).
  • В остальный случаях, игрок может пройти.

Теперь, разберем moveEntity, именно эта функция отвечает за движение игрока.

Если бы, игрок перемещался со скростью, кратной размерам тайла (1, 2, 4, 8). И находился бы, строго на координатах тайла. Тогда, можно было-бы сократить код moveEntity, до:

void moveEntity(){
	s16 posX = fix32ToInt(plr.pos.x);
	s16 posY = fix32ToInt(plr.pos.y);

	s16 spdX = fix32ToInt(plr.spd.x);
	s16 spdY = fix32ToInt(plr.spd.y)+2; //Ранее здесь не было +2, и игрок мог застрять на уступе, как ни странно, костыль подобранный на угад, спас ситуацию.

	if(checkCollision(posX+spdX, posY+spdY)) {
		posX += spdX;
		posY += spdY;
	}
	plr.pos.x = FIX32(posX);
	plr.pos.y = FIX32(posY);
}

Т.е. если на будующей позиции игрока (posX+spdX) нет коллизии, тогда перемещаемся на будующую позицию, в противном случае — стоим на месте.

Но, такой вариант, не подходит для платформеров.

На помощь приходит огроменный блок else. Который, в основном, состоит из копипасты.

Здесь, я буду использовать жутко не оптимизированный алгоритм, т.к. если буду работать над правильным вариантом, выпуск статьи затянется до второго пришествия Христа. Так что, приступим.

Проверяем путь игрока на столкновение.

Разберем код else.

if (spdX) {
  testPosX = posX;
  if (spdX > 0) {
    for(u8 i=1;i<spdX;i++){
      testPosX++;
      if(checkCollision(testPosX, posY)) {
      posX = testPosX;
          } else {
            break;
          }  
        }
      } else {
        for(u8 i=spdX;i>0;i++){
          testPosX--;
          if(checkCollision(testPosX, posY)) {
            posX = testPosX;
          } else {
            break;
          }  
        }
      }
    }

Если игрок двигается влево или вправо if (spdX), то проверяем позиции игрока, начиная от posX, заканчивая spdX.

for(u8 i=1;i<spdX;i++){
      testPosX++;
      if(checkCollision(testPosX, posY)) {
      posX = testPosX;
          } else {
            break;
          }  
        }

Ровно до того момента, пока координата игрока не столкнется с коллизией. Финальная координата игрока, хранится в testPosX, testPosY. Она приравнивается к позиции игрока.

plr.pos.x = FIX32(posX);
plr.pos.y = FIX32(posY);

В конце, меняем координаты игрока.

Разбираем управление.

Управление задается в handleInput:

void handleInput()
{
    u16 value = JOY_readJoypad(JOY_1);
    if (!paused && !plr.moving)
    {
		
		plr.spd.x = 0;
		
		if(!plr.is_on_floor){
			plr.spd.y += FIX32(1);
		} else {
			plr.spd.y = 0;
		}
		
        if (value & BUTTON_RIGHT)
        {
			plr.spd.x = FIX32(5);
		}
        else if (value & BUTTON_LEFT)
        {
			plr.spd.x = FIX32(-5);
		}
        if (value & BUTTON_UP)
        {
			if(plr.is_on_floor){
				plr.spd.y = FIX32(-10);
			}
        }
    }
}
  • Если игрок не на полу, то применяем гравитацию, иначе — отключаем.
  • Меняем скорость игрока, при нажатии на кнопок вправо/влево
  • Прыгаем, если нажата кнопка вверх и игрок находится на земле.

Последнее что осталось, это вызвать все эти фукнции в главном цикле.

while(1)
{
	SPR_update();
	handleInput();
	moveEntity();
		
	VDP_showFPS(FALSE);
	setCameraPosition(fix32ToInt(plr.pos.x),fix32ToInt(plr.pos.y));
        
	SYS_doVBlankProcess();
}

Вроде обо всем рассказал.

Заключение.

Вот и создали мы базовый платформер, без монстров и декораций, с одним единственным уровнем и без звука. Но ничего, в следующих статьях мы это исправим.

(Бонус) Правильный код коллизии.

Вместо того, чтобы проверять весь путь от posX до posX+spdX. Намного проще, узнать позицию тайла (в пикселях) с которым ты сталкиваешься, и вычесть из неё координату игрока.

pos_x += (coll_tile_x*8) - posX; //прожорливый вариант
pos_x += (coll_tile_x<<3) - posX; //эффективный вариант

Пока, мне не удлось реализовать такую коллизию. Если разберусь, обновлю статью.

Отдельная благодарность.

Передаю благодарность:

  • Sharpnull — из форума emu-land, за объясение работы коллизии.
  • Diamond Softhouse — тоже помог, в изучении данного вопроса.

Итоговый результат.

Делаю игру на Sega Genesis / Mega Drive

  1. Сейчас занят тем, что в свободное время, которого не так уж и много, делаю игру на 16-битную игровую приставку Sega Genesis / Mega Drive. Названия пока нет, но по жанру это будет платформенный шутер, т.н. run ‘n gun.

    [​IMG] [​IMG]

    Кодер я никакой, художник тоже так себе, посему делаю игру несмотря на ряд нареканий. Зато сам. И без нервотрёпки с художниками, которым нужны деньги, у которых пропадает желание и вообще времени нет. Проект делается туго, не в пример сложнее чем если бы делался он на современный PC Windows.

    [​IMG] [​IMG]

    Сейчас, вроде почти полностью движок готов, и я пилю уровни по мере того, как удаётся что-то нарисовать, что-то накодить. Надеюсь этот проект уже сделать со всеми имеющимися компромиссами, а затем уже все недочёты учесть при создании следующего движка. Например, избавлюсь от HUD в пользу значков чтобы экран был больше, графику буду рисовать без обводки и пр.

    Я знаю, что графика не айс. Но мне не удалось найти чуткого художника. который бы делал то, что я прошу (а платформа задаёт свои условия). Зато с музыкантом нашёл общий язык — нанял человека за деньги. И он тоже не первый, с кем сотрудничал. Посему убегаю от команды — просто болит голова уже объяснять впустую.

    Проект хочу выпустить на картриджах небольшим тиражом, а затем уже он появится в виде ROM-файла. Лог веду ещё здесь.


Форум Old-Games.RU. Всё о старых играх

В предыдущем уроке говорилось о настройке среды SGDK, в которой мы разработаем нашу первую программу Sega MD: Hello World.

Я использовал редактор vscode, эта статья в основном основана на vscode.

1. Создайте новый проект. Фактически, нет нужды говорить, что он настолько формален. Это означает, что вы можете создать новую папку в любом месте, которое вам нравится. Папка называется helloworld. Затем войдите в каталог helloworld и создайте три новые папки, а именно src. Res, Inc

Во-вторых, щелкните правой кнопкой мыши корневой каталог проекта (здесь находится каталог helloworld), откройте с помощью кода, чтобы открыть (чтобы установить расширение C / C ++), создайте новый файл helloworld.c в каталоге src (базовая операция vscode не будет говорить об этом, self Baidu)

3. Нажмите F1, чтобы открыть командную панель, выберите C / C ++: Изменить конфигурации (JSON), как показано ниже:

Корневой каталог проекта сгенерирует каталог .vscode, в нем будет дополнительный файл c_cpp_properties.json, откройте его, выберите и удалите содержимое внутри, скопируйте в него следующий код, сохраните его, чтобы вы могли правильно запросить завершение кода.

{
    "configurations": [
        {
            "name": "Genesis",
            "includePath": [
                "${GDK_WIN}/inc"
            ]
        }
    ],
    "version": 4
}

4. Откройте helloworld.c и начните писать код следующим образом:

#include <genesis.h>
#include <vdp.h>

int main()
{
    VDP_drawText("hello world", 0, 0);
    while (1)
    {
        VDP_waitVSync();
    }
    return 0;
}

5. Скомпилируйте, Ctrl + J откроет панель терминала vscode, введите следующую команду и нажмите Enter:

%GDK_WIN%binmake -f %GDK_WIN%makefile.gen

Если все пойдет хорошо, вы увидите выходной каталог в корневом каталоге проекта, который содержит сгенерированную игру, как показано ниже:

Откройте rom.bin с помощью симулятора Sega MD, и вы увидите на экране привет мир, как показано ниже:

6. Действительно сложно писать такой длинный список команд каждый раз, когда вы компилируете, тогда давайте настроим компиляцию в один клик:

Нажмите клавишу F1, чтобы открыть панель команд, введите задачу, выберите «Задачи»: настройте задачу по умолчанию, добавьте файл tasks.json в каталог .vscode, откройте его, удалите все содержимое и скопируйте следующий код для сохранения:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "MegaDrive",
            "type": "shell",
            "command": "%GDK_WIN%/bin/make",
            "args": [
                "-f",
                "%GDK_WIN%/makefile.gen"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

Таким образом, вы можете скомпилировать игру непосредственно, нажав Ctrl + Shift + B, что очень удобно.

Продолжение следует. , ,

Группа разработчиков ретро игр: 879063892

Тема: Программа или язык для создания игр для денди, сеги или снес  (Прочитано 14308 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Привет вопрос который поднимался 100500 раз — но ответ на который я не нашел. Хотел бы поэкспериментировать и попробовать создать простецкую игрушку на одной и выше указанных платформ, но вот к сожалению не знаю следующего.

Есть ли какие нибудь языки и компиляторы для написания игрушек? 
Так же интересуют муз редакторы и конвертеры?

В чем раньше писали игры для дендика, сеги и снес — не уж то в чистом ассемблере?

Так же естественно интересуют редакторы карт.
Буду благодарен за любую полезную инфу. Если разберусь, то сделаю какую нить игруху. Хаки и просто переделки уровней не интересны — так как хочу по своему сценарию.


ruslansh, хак тоже может быть тотальной конверсией)) Ты можешь переделать абсолютно все, оставив только движок: персонажа, сценарий, уровни, анимацию и т.д. Даже многие официальные разработчики никогда не гнушались использовать готовые движки, например, куча игр было сделано на движке мегамэна, зельды, драгон ворриора… Ну для этого ассемблер все равно придется учить)
вот это отличный сайт по написанию собственной игры на нес, на инглише
http://nintendoage.com/forum/categories.cfm?catid=22


В чем раньше писали игры для дендика, сеги и снес — не уж то в чистом ассемблере?

На нём самом.

Хотя для сеги энтузиасты написали фреймворк на Си. Так что на неё игрушку легче всего написать.
http://code.google.com/p/sgdk/



GINCS — конструктор текстовых квестов и новелл для MD/Genesis


Basiegaxorz — типа васик для мегадрайва


В 1980-х годах, когда приставки только появлялись, вышла NES — Nintendo Entertainment System. В Россию она попала в виде китайского клона «Денди», «Кенги» и прочих, поэтому если у вас была восьмибитная приставка, то это была NES.

У NES было очень мало памяти и очень медленный по нынешним меркам процессор. Эта статья о том, как сделать крутую игру в очень ограниченных условиях.

Та самая приставка, справа пока ещё две кнопки вместо четырёх.

Для разбора мы взяли видео из канала Morphcat Games — How we fit an NES game into 40 Kilobytes. Там разработчики повторяют опыт геймдизайнеров прошлого и пишут игру для старого железа. Как обычно, если знаете английский, то лучше посмотрите видео целиком, а если нет — держите наш текстовый вариант.

Почему именно 40 килобайт

В 1980-х объём памяти на цифровых устройствах измеряли в килобайтах, потому что ещё не было таких продвинутых её технологий. В большинстве картриджей для восьмибитных приставок было по 40 килобайт памяти. Для сравнения, это в сто тысяч раз меньше, чем на флешке в 4 гигабайта. Даже эта статья весит больше, чем 40 килобайт, так что по современным меркам этого действительно мало.

Два блока памяти в картриджах, 8 и 32 килобайта, в сумме — 40 килобайт.

Чтобы использовать больше памяти, нужно было идти на всякие ухищрения — ставить расширители памяти или отдельные блоки для работы с несколькими картриджами одновременно. Так как почти ни у кого из геймеров такой роскоши не было, то разработчики использовали только 40 доступных килобайт.

Когда у тебя мало памяти, у тебя мало возможностей: уровни однообразные, враги однообразные, геймплей одинаковый. Но иногда разработчики шли на безумные ухищрения, и в игру получалось запихнуть много «миров», секретов и вариантов геймплея.

Одна из игр, которая взорвала мозг всем в своё время, была та самая «Супер Марио»: в ней было огромное количество разнообразных уровней разной сложности, боссы, секретные уровни и непростой, очень насыщенный геймплей. Были уровни на земле, под землёй, под водой и даже на небе; у героя было несколько режимов — низкий, высокий, в белом комбинезоне. А как вам идея разрушаемого мира? А как вам атаки с воздуха? Короче, «Марио» была безумной, невероятной игрой для своего времени, а всё благодаря оптимизациям.

В видеоролике разработчики поставили себе похожую цель: сделать насыщенную, разнообразную игру с большим количеством уровней, миров и настроений. И они показали, как этого добиться с помощью жёстких оптимизаций.

«Супер Марио» — игра, в которую играл каждый, у кого была приставка.

Логика игры

Чтобы в игру было интереснее играть дольше пяти минут, разработчики поставили такие требования:

  1. Это будет платформер — игра, где главному герою нужно бегать и прыгать по платформам, залезать наверх и скакать через препятствия.
  2. Герой сможет ловко двигаться и стрелять по врагам.
  3. Чтобы можно было играть компанией, делают мультиплеер на четырёх человек.

Так как у нас ограничения по памяти, всю игру пишут на Ассемблере — это язык, который работает напрямую с процессором. С одной стороны, код Ассемблера исполняется очень быстро; с другой — в нём работа идёт тупо с перекладыванием данных из одной ячейки процессора в другую. Это примерно как готовить суши, работая с индивидуальными рисинками.

Память распределили так:

  • 8 килобайт на графику,
  • 32 килобайта на сам код игры и хранение данных.

Персонажи

В игре есть два вида графики: статичный фон и движущиеся предметы — игроки, противники, боссы и выстрелы. Всё, что движется, называется спрайтами. Разработчики делят всю графическую память на две части — одну под спрайты, вторую под фон:

Каждая клеточка — это мини-квадратик 8 на 8 пикселей.
В каждом таком квадратике можно что-то нарисовать, но использовать при этом только три цвета.
Если объединить несколько квадратиков в один, получится метаспрайт. В нашем случае — персонаж.
Приставка может использовать одновременно только 4 вида палитры, поэтому у нас получается 4 цветных главных героя и нераскрашенный злодей.
Новое ограничение: на экране одновременно может быть только 8 спрайтов — на большее не хватает памяти. Поэтому для злодея места не остаётся. Можно пойти на хитрость и показывать их быстро-быстро по очереди, но тогда картинка будет мерцать и выглядеть хуже.
Разработчики радикально уменьшили размеры героев и злодея до одного спрайта. Теперь они выглядят более условно, зато помещаются на экран.
Меньше размер героя — больше свободного места для дизайна злодеев, боссов и спецэффектов. Сейчас в табличке собраны все варианты того, как может выглядеть персонаж в игре — и в прыжках, и на бегу.

Большой босс и оптимизация памяти

Если с персонажем всё стало проще, когда его уменьшили, то с боссом всё немного сложнее. Он большой, занимает много места и у него много анимации. Задача — сделать так, чтобы боссы занимали как можно меньше места в памяти.

Большой босс и все его варианты анимации.
Если мы распределим все спрайты по таблице один в один, то у нас быстро закончится место и один кусочек не поместится. Запомните эту картинку как пример неоптимизированной работы с памятью.
Для начала разработчики разбили босса горизонтально на три части, и каждая анимируется отдельно. Видно, что анимация причёски состоит из трёх картинок, каждая из которых немного отличается от остальных.
Если разбить картинки с причёской на отдельные квадратики, то мы заметим, что у них есть повторяющиеся части. Поэтому достаточно нарисовать одну деталь, а потом использовать её во всех трёх вариантах причёски.
Находим оставшиеся одинаковые части и тоже оставляем только одну из них.
А вот тут видно, что это один и тот же спрайт, только в зеркальном виде. Компьютеру несложно нарисовать его отражённым, поэтому тоже можно смело оставить только один из них. С последними треугольничками в каждой картинке — то же самое: это отзеркаленные первые спрайты.
В итоге вся верхняя часть босса вместе с анимацией поместилась в четырёх спрайтах. Это и есть оптимизация: было 16 спрайтов, стало 4.
То же самое делают для средней части. Сейчас она занимает 3 × 8 = 24 спрайта.
А сейчас — 7.
После полной оптимизации босс занимает всего 21 спрайт. Из этих кусочков собирается итоговый вид босса.
Сравните с первоначальным вариантом до оптимизации 🙂

Карта

Для карт у нас столько же памяти, сколько и на спрайты (то есть мало), поэтому разработчики будут действовать так же:

  • разбивать фон на отдельные ячейки;
  • смотреть, как можно оптимизировать эти ячейки для хранения в памяти;
  • смотреть, можно ли что-то использовать повторно, для экономии памяти.

Главная задача на этом этапе — максимальная экономия видеопамяти. Для этого каждый экран с уровнем игры разбивается не на метаплитки 2 × 2, как в примере выше, с персонажем, а на метаметаплитки или суперплитки — 4 × 4 ячейки. Вот для чего это нужно:

Если разбить просто на квадратики 8 × 8, как в памяти, то вся видимая на экране часть уровня займёт 960 байт. Это почти килобайт, и это очень много.
Разбивают уровень на метаплитки 16 × 16. Теперь на одну карту нужно 240 байт, чтобы пометить каждую такую метаплитку, но это всё равно много. Уменьшаем дальше.
Теперь уровень делится на супербольшие плитки по 16 ячеек в каждой. В итоге для того, чтобы пронумеровать каждую такую суперплитку, нужно всего 60 байт. Уже можно работать.
Вот так собираются метаплитки — из четырёх ячеек в памяти.
Теперь можно собирать такие метаплитки в виртуальные наборы и каждой присвоить какой-то код. Но и это ещё не всё.
Вот теперь получилась суперплитка. Это готовый блок для уровня, и чтобы собрать такое, нужно совсем немного памяти.
Коллекция виртуальных суперплиток. С ними можно сделать любые уровни и фоны.

Рисуем карты (и оптимизируем их)

Даже 60 байт на экран, которые у нас получились, — это всё равно очень много, ведь нужно сделать много разных карт, написать логику поведения персонажей и сделать меню, заставки и титры. Каждый байт на счету.

Первый вариант — уменьшить количество памяти для отрисовки карты: сделать их симметричными, что даст нам 30 байт вместо 60. Мы рисуем одну половинку карты, а потом просто отзеркаливаем её. Сравним с картой, которую мы бы хотели получить:

Вроде всё на месте, а выглядит плохо — сразу видна симметрия и доступ наверх закрыт блоками.

И вот тут разработчики делают очередной хитрый ход, который даст им немного дополнительной памяти для графики. Смотрите:

  1. Они дают для хранения одной суперплитки один байт.
  2. Считают по картинке, сколько получилось суперплиток в прошлом разделе — 96.
  3. Так как программисты начинают считать с нуля, то самое большое число, которое получится, — 95, а это 1011111 в двоичной системе счисления.
  4. В этом длинном числе всего 7 цифр, а в байте их 8, поэтому остаётся один лишний бит из каждого числа.
  5. 4 суперплитки дадут 4 бита.
  6. Эти 4 бита можно использовать, чтобы сдвинуть по кругу ряд с зеркальным отражением и получить как бы новый ряд, уже без видимой симметрии.

Если вы не знаете, что такое двоичная система счисления, — почитайте нашу статью об этом, а потом вернитесь сюда.

4 суперплитки дают 4 бита. Посмотрим, что можно с ними сделать.
Сначала делают симметричный уровень…
А затем сдвигают верхнюю полосу вправо по кругу. 1100 — это 12 в десятичной системе счисления, именно столько сдвигов вправо нужно сделать, чтобы получилось как на картинке.
То же самое делают с третьей строкой и получают уже приемлемое начало уровня.

Действуя таким образом, разработчики могут менять уровни до неузнаваемости, не затрачивая при этом вообще лишней памяти. Помним, что наш экран — это ещё не весь уровень, сверху нужно нарисовать ещё много раз по столько же.

Добавляем в игру сложный режим

Когда игрок прошёл все уровни, ему можно дать возможность поиграть на повышенной сложности: он уже знает всю игру и может пройти более сложные ловушки и боссов. Например, сложный режим может отличаться дизайном уровней и поведением противников.

Чтобы и этот режим поместился в оставшуюся память, снова используют трюки с памятью и графикой.

Чтобы игрок понял, что начались трудности, просто меняют палитру. Это почти столько же по памяти, но выглядит сложнее.
Уровень можно поменять так: берут исходную картинку, накладывают сверху новые детали и получают сложную локацию. В среднем на это уходит по 7 байт на каждый экран.

В чем оптимизация, брат

  1. Уменьшили персонажей: маленькие спрайты — меньше памяти.
  2. Оптимизировали графику: вместо больших повторяющихся картинок — много маленьких повторяющихся картинок.
  3. Оптимизировали архитектуру уровней: сделали их симметричными, но сдвинули ряды по кругу влево-вправо, чтобы добавить разнообразия.
  4. Для дополнительного разнообразия ввели новые цветовые палитры.
  5. Более сложные уровни не хранили в памяти целиком. Для них хранились лишь дополнительные ловушки и враги. А на фоне лежали те же старые уровни.
  6. И всё это на чистом Ассемблере.

Понравилась статья? Поделить с друзьями:
  • Как написать игровой сценарий
  • Как написать игровой искусственный интеллект unity 2d
  • Как написать игрового бота на python для web
  • Как написать игривое сообщение мужчине
  • Как написать игрек на клавиатуре