код платформера на c
SGDK. Создание платформера для Sega Genesis.
Предисловие.
Скачайте готовый файл проекта с MEGA.
В этом уроке мы создадим платформер на SGDK. Да, так быстро, и сразу в пекло, приступим.
Что потребуется для платформера.
А потребуется следующее:
Пусть вас не пугает обширный список пунктов, единственный значимый пункт это:
Остальные, мы либо изучали, либо до них не трудно догадаться. А теперь вопрос, каким образом, мы поймем тайл на карте является твердым?
Разбираемся в массиве коллизий.
На помощь приходит массив коллизий.
Массив коллизий — это 2-х мерный массив хранящий информацию о твердости тайла. Например:
Используя координаты игрока, можно узнать тайл, на котором стоит игрок. Делается это так.
1-я и вторая строка идентичны, но 2-я предпочтительней, т.к. работает быстрее. Позиция игрока делится на 8, потому-что тайл 8×8 px.
Зная координаты тайла, можно подставить их в массив коллизий (level), и узнать, твердый ли, тайл на котором стоит игрок, или нет. Делается это по такому шаблону.
x_tile, y_tile — тайл на котором стоит игрок.
Т.е. если level[y_tile][x_tile] вернет 0, то нельзя пройти, если 1, то можно.
Далее, нам понадобится Python скрипт, который будет генерировать массив коллизий из картинки. Скрипт писал я сам, специально для этой задачи.
Генерируем массив коллизий.
Сперва, скачайте скрипт с MEGA.
И перетащите картинку с картой на col_generator.exe, скрипт сам сгенерирует нужные файлы.
Альтернативный способ. Запустите col_generator.exe через консоль, со следующими аргументами.
Поместите скрипт в папку с изображением (за пределами проекта), затем, запустите скрипт со следующими аргументами.
Изображение bg.png, находится в папке res проекта.
После запуска, скрипт сгенерирует 2 картинки и текстовый файл.
На картинке bg_WithCollision.png, твердые тайлы отмечены красным.
В файле bg_array.txt хранится массив коллизий, и размер карты в пикселях, который, в будущем, понадобится для ограничения камеры.
Массив коллизий получили, теперь разберем код платформера.
Ресурсы платформера.
В папке res, находится спрайт игрока, и карта мира.
Спрайт игрока(кружки) я сделал максимально квадратным, что-бы проще было увидеть область столкновений.
Код платформера.
Откройте main.c
Нас приветствует огромный массив коллизий, сгенерированный ранее.
Далее, идут структуры, в которых хранятся переменные игрока.
В функции main, заполняем переменные игрока — значениями.
Установка позиции спрайта и скролл карты, осуществляется в setCameraPosition.
Разберем данную функцию
Позиция камеры смещена в минус относительно координат игрока, таким образом, игрок находится в центре.
Ограничиваем камеру в рамках карты.
Устанавливаем позицию спрайта относительно координат камеры. Данная конструкция, обеспечивает перемещение спрайта из центральной позиции, при приближении к границам карты.
Перейдем к проверке коллизии.
Разбираем код коллизии.
Теперь, разберем moveEntity, именно эта функция отвечает за движение игрока.
Если бы, игрок перемещался со скростью, кратной размерам тайла (1, 2, 4, 8). И находился бы, строго на координатах тайла. Тогда, можно было-бы сократить код moveEntity, до:
Т.е. если на будующей позиции игрока (posX+spdX) нет коллизии, тогда перемещаемся на будующую позицию, в противном случае — стоим на месте.
Но, такой вариант, не подходит для платформеров.
На помощь приходит огроменный блок else. Который, в основном, состоит из копипасты.
Здесь, я буду использовать жутко не оптимизированный алгоритм, т.к. если буду работать над правильным вариантом, выпуск статьи затянется до второго пришествия Христа. Так что, приступим.
Проверяем путь игрока на столкновение.
Разберем код else.
Если игрок двигается влево или вправо if (spdX), то проверяем позиции игрока, начиная от posX, заканчивая spdX.
Ровно до того момента, пока координата игрока не столкнется с коллизией. Финальная координата игрока, хранится в testPosX, testPosY. Она приравнивается к позиции игрока.
В конце, меняем координаты игрока.
Разбираем управление.
Управление задается в handleInput:
Последнее что осталось, это вызвать все эти фукнции в главном цикле.
Вроде обо всем рассказал.
Заключение.
Вот и создали мы базовый платформер, без монстров и декораций, с одним единственным уровнем и без звука. Но ничего, в следующих статьях мы это исправим.
(Бонус) Правильный код коллизии.
Вместо того, чтобы проверять весь путь от posX до posX+spdX. Намного проще, узнать позицию тайла (в пикселях) с которым ты сталкиваешься, и вычесть из неё координату игрока.
Пока, мне не удлось реализовать такую коллизию. Если разберусь, обновлю статью.
Отдельная благодарность.
Создаем платформер за 30 минут
Здравствуйте! Сегодня мы будем писать платформер, используя C++, Box2D и SFML, а также редактор 2D карт для игр Tiled Map Editor.
Вот результат (карта создавалась 5 минут + во время сьемки игра тормозила + экран не так растянут — дефект Bandicam):
Исходники и exe — внизу статьи.
Что, где, когда?
Box2D
Эту библиотеку мы будем использовать для симуляции физики в платформере (столкновение с блоками, гравитация). Возможно, не стоило для одних только блоков юзать эту библиотеку, но красиво жить не запретишь 😉
Почему именно Box2D? Потому что это самая распространенная и бесплатная физическая библиотека
Почему SFML? Вначале я хотел использовать библиотеку SDL, но она сильно ограничена в возможностях по сравнению с SFML, многое пришлось бы дописывать самому. Спасибо автору SFML за сэкономленное время!
Ее используем для отрисовки графики
Tiled Map Editor
Что делает тут Tiled Map Editor?
Вы когда-нибудь пробовали создавать карты для игр? Спорим, что вашей первой картой было что-то наподобие такого.
Это довольно неэффективное решение! Гораздо лучше написать что-то вроде редактора карт, но задним числом мы понимаем, что это не делается за 5 минут, а приведенная выше «карта» — вполне.
Создание карты
Скачиваем TME с официального сайта
Создаем новую карту «Файл->Создать. »
Ориентация должна быть ортогональной (если вы не делаете изометрический платформер), а формат слоя XML, мы будем считывать именно этот формат
Кстати, ни формат слоя, ни размер тайлов нельзя будет поменять в созданной карте.
Тайлы
Затем идем в «Карта->Новый набор тайлов. », загружаем наш тайлсет:
В итоге у вас получится что-то вроде этого:
В чем смысл слоев тайлов?
Почти в каждой игре есть многослойные карты. Первый слой — земля (лед, чернозем, etc), второй слой — здания (казармы, форт, etc, причем фон прозрачен), третий — деревья (ель, пихта, etc, фон тоже прозрачен). То есть рисуется сначала первый слой, поверх него накладывается второй слой, а потом уже третий.
Процесс создания слоев запечатлен на следующих 4 скриншотах:
Объекты
Что такое объект в TME?
Объект имеет свое имя, тип, а также параметры со значениями.
За объекты отвечает эта панель
Вы вполне можете узнать, что делает каждая из кнопок, сами.
Теперь попробуем создать объект.
Удаляем слой «Колобоша», вместо него создаем слой объектов, допустим, с тем же названием «Колобоша». Выбираем «Вставить тайл-объект» из панели для объектов (или можете выбрать любую фигуру — Shape), нажимаем на тайл Колобоши и просто ставим объект в какое-нибудь место.
После чего нажимаем правой кнопкой мыши на объект и нажимаем на «Свойства объекта. ». Измените имя объекта на Kolobosha.
После чего сохраните карту.
В общем, ничего архисложного в редакторах карт нету. Пора переходить к считыванию карты.
Считывание карты
Для считывания XML файлов создана отличная библиотека TinyXML, скачайте ее исходники.
Создайте проект Visual Studio. Подключите файлы TinyXML (или просто запихайте все эти файлы в проект, за исключением xmltest.cpp 🙂 )
Теперь подключаем includ’ы и lib’ы SFML в «Проект->Свойства». Если не знаете, как это делать — добро пожаловать в Гугл
Создаем Level.h для карт
Дальше идет структура объекта
Разберем её.
Как уже говорилось, в TME каждый объект может иметь параметры. Параметры берутся из XML файла, записываются в properties, и потом их можно получить любой из первых трех функций. name — имя объекта, type — его тип, rect — прямоугольник, описывающий объект. И наконец, sprite — спрайт (изображение) — часть тайлсета, взятая для объекта. Спрайта может и не быть.
Теперь идет структура слоя — она очень проста
В слое есть прозрачность (да, да, мы можем делать полупрозрачные слои!) и список из тайлов.
Дальше идет класс Level:
LoadFromFile загружает карту из указанного файла. Это сердце класса Level.
GetObject возвращает первый объект с указанным именем, GetObjects возвращает список объектов с указанным именем. Вообще-то, по-хорошему, следовало использовать тип (type) объекта, но мне было удобнее вылавливать блоки и игрока через имя, так как в редакторе имя показывается сверху объекта, а тип — нет.
Draw рисует все тайлы (не объекты!), беря себе экземпляр RenderWindow.
Теперь создаем Level.cpp:
Первым мы обрабатываем структуру объектов
Для Layer реализация не нужна, переходим к Level:
Остальные функции Level:
Протестируем его.
Создаем main.cpp и пишем:
Карта может выглядеть как угодно!
Можете поиграться с объектами:
Когда вы наиграетесь с объектами, наступит пора Box2D:
Коробки-коробочки
Мы хотим создать 3D-экшон платформер, суть такова…
На карте расположены объекты — с названиями player — игрок, enemy — враг, block — блок, money — монетки.
Мы загружаем игрока, заставляем его повиноваться нажатиям клавиши и силе Ньютона.
Враги ходят туда-сюда, отталкивают слишком близко находящегося игрока и погибают, если игрок прыгает на них
Блоки закрепляются «в воздухе» как статичные объекты, на них игрок может прыгать
Монеты ничего не дают, просто исчезают при столкновении с игроком
Открываем main.h, стираем то, что там было написано, и пишем:
Здесь у нас подключены level.h и Box2D.h. iostream нужен для вывода в консоль, random — для генерации направления движения врага.
Далее идут игрок и векторы, каждому врагу, монетке, игроку полагается свой Object и b2Body (тело в Box2D).
Внимание — блокам этого не полагается, так как они взаимодействуют с игроком только на уровне физики Box2D, а не в игровой логике.
srand(time(NULL)) нужен для рандома.
Загружаем карту, создаем b2World, передавая ей гравитацию. Кстати, гравитация может исходить из какого угодно направления, и гравитация из (0,10) действует сильнее (0,1). Потом мы берем нужный нам размер тайлов
Далее создаем тела блоков:
Блоки — статические тела, они не имеют массы и висят в воздухе:
Тут мы устанавливаем позицию блоков. Дело в том, что если просто указать позицию такую же, как у объекта, нас будет ждать коварная ошибка.
Создаем тело блока в world. Далее мы с телом не работаем (в смысле, нигде не храним):
Каждому телу принадлежит несколько shape — фигур. Я не буду подробно разбирать эту тему, так как блокам (и остальным телам) хватает всего-то одного прямоугольника.
Связываем фигуру с телом.
Затем мы делаем то же самое с врагами, монетами и игроком, за небольшими различиями:
Означает, что тело не может вращаться.
Все тела созданы, осталось инициализировать графику!
Хорошо понятный код, создает окно с указанным размером и заголовком:
Тут мы создаем вид (View) для окна.
Зачем это надо? Для того, чтобы придать игре пиксельный стиль, мы умножаем размер экрана на 2 с использованием sf::View и все картинки рисуются в 2 раза выше и шире.
Окно закрывается по нажатию на красный крестик. Такой код был ранее:
Тут уже интереснее! Мы добавляем скорость игроку по нажатию клавиш WAD:
Тут мы обновляем физический мир Box2D. Первый аргумент принимает частоту обновления мира (раз в 1/60 секунд), а также количество velocityIterations и positionIterations. Чем выше значение последних двух аргументов, тем реальнее получается физика игры. Так как у нас нету никаких сложных фигур, как в AngryBirds, а только прямоугольники, то нам достаточно по разу.
Здесь мы обрабатываем столкновение игрока с другими телами:
Обработка столкновения с монетами.
Если какая монета столкнулась с игроком, она просто уничтожается и стирается из векторов:
Если враг сталкивается с игроком, проверяется, выше игрок врага или нет. Если игрок выше врага, то он стирается, а игрок подскакивает вверх.
Если иначе, то игрок отскакивает от врага:
Игрок движется направо или налево в соотвествии с его текущим положением относительно врага.
Если скорость врага равна 0, то ему скорость придается вновь — он движется либо направо, либо налево. Визуально это выглядит как движение рывками.
Работа с графикой. Берем позицию игрока, изменяем центр вида и используем наш вид
Устанавливаем спрайтам игрока, монет и врагов позиции, полученные из b2Body:
Очищаем окна, рисуем тайлы карты, потом игрока, монеты и врагов, после чего представляем окно.
Исходники
https://github.com/Izaron/Platformer
Введение в программирование: заготовка игры-платформера на SDL в 300 строк C++
Этот текст предназначен для тех, кто только осваивает программирование. Я читаю лекции по C++ на первом курсе местного университета, и в качестве практикума предлагаю запрограммировать любую игру (не выношу проектов типа «софт бронирования книг в местной библиотеке»). Соответственно, чтобы помочь начинающим, я сделал некоторое количество заготовок, с которых можно стартовать свой проект. Например, заготовку олдскульного 3д шутера в 486 строк C++ я уже описывал, а вот тут можно посмотреть, что из неё сделали первокурсники.
В этот раз всё будет ещё проще, я хочу сделать заготовку под простейший платформер, вот так выглядит результат:
На данный момент проект содержит менее трёхсот строчек цпп:
Мой опыт показывает, что просто выложить код заготовки недостаточно. Нужно детально описать, как именно я пришёл к такому коду, ведь самый главный навык программиста — это суметь разбить сложную задачу на некоторое количество более простых подзадач, каждая из которых решается легко.
Шаг первый: компилируем проект и открываем окно
Я ничего не знаю про оконные библиотеки, поэтому выбрал первую попавшуюся, а именно SDL. Первый шаг — самый сложный. Нужно суметь скомпилировать пустой проект, и слинковаться с выбранной библиотекой. Самая первая задача — открыть пустое окно, и отработать событие его закрытия. Вот тут можно найти соответствующий коммит:
Изначально я хотел сделать черновой репозиторий, и потом почистить историю, убрать детские баги, сделать красивый «один коммит на одну фичу», но тут случился анекдот: только я создал репозиторий, как он был немедленно форкнут парой человек, один из которых мне к тому же прислал пулл реквест . Соответственно, чистить историю, не сломав их репы, я уже не могу. Издержки двух с половиной тысяч фолловеров на гитхабе
. Таким образом, у меня честный репозиторий без прикрас.
Вот так выглядит код, открывающий пустое окно:
Собственно, там ничего сверхъестественного: мы инициализируем SDL, создаём окно с зоной рендера и запускаем основной цикл игры. По событию закрытия окна или по нажатию эскейпа выходим из цикла и чистим память. Piece of cake.
Шаг второй: счётчик fps
На втором этапе я решил отобразить количество перерисовок экрана в секунду, хочу увидеть вот такой результат (не пугайтесь сорока тысяч fps, всё же мы ничего не делаем в основном цикле!):
Для этого нам нужно две вещи:
Всё едино мне нужно будет уметь работать со спрайтами, так почему бы не использовать эту же технику для отрисовки счётчика? Итак, вот структура для работы со спрайтами:
Вот тут можно посмотреть коммит с рабочим кодом.
Шаг третий: сорок тысяч fps это многовато, давайте поменьше
На данный момент у меня на ноуте вентиляторы крутятся так, что он порывается улететь. Давайте-ка снизим нагрузку на проц. Как заставить основной цикл исполняться не больше 50 раз в секунду? Самый наивный вариант — это что-то вроде такого кода:
Мы можем тупо вставить задержку на 20 миллисекунд в тело цикла, получив максимум 50 fps. Такой подход имеет право на жизнь, но он предполагает, что время работы do_nothing() пренебрежимо. А если вдруг оно будет исполняться, скажем, за 12мс? Тогда нам задержку нужно не 20, а 8, иначе сильно проседает FSP. А ведь это ещё зависит от компа… Поэтому я предлагаю следующий подход:
Итак, вот результат:
Шаг четвёртый: отрисовываем уровень
Теперь давайте отрисуем карту уровня, вот тут соответствующий коммит. Я хочу видеть вот такой результат:
Для этого я сначала нарисовал текстуру 768 x 128 пикслей, в которую у меня упаковано шесть спрайтов каменюк размером 128×128:
Мой экран разбит на 192 клетки (16 по горизонтали и 12 по вертикали), и каждой клетке соответствует какая-то текстура.
Сама структура определена следующим образом:
Шаг пятый: персонаж и его анимация
Осталось совсем немного: непосредственно персонаж. Давайте для начала научимся показывать анимации. Я взял карандаш и нарисовал последовательность кадров, которая показывает идущего человечка:
Я хочу получить вот такой результат (коммит брать тут):
Не бейте меня больно за кривые рисунки, я программист, а не художник! Как же нам их показать на экране? Для начала давайте опишем структуру, которая будет ответственна за анимации:
Теперь осталось описать персонажа:
Использование этой структуры пока что идентично использованию карты и счётчика fps:
Шаг шестой: опрос клавиатуры и обработка столкновений
А теперь давайте научимся опрашивать клавиатуру и обрабатывать столкновения с картой (вот коммит). Я хочу получить вот такой результат:
Шаг седьмой: сила тяжести!
Обработку столкновения по вертикали даже описывать не буду, она ничем не отличается от горизонтали, коммит брать тут, ну а вот и результат:
Последние штрихи
Единственное, что осталось добавить нашу заготовку — это обработку прыжков. Это делается заданием начальных скоростей vx и vy в зависимости от комбинации курсорных клавиш. Чисто по приколу у меня есть прыжки в высоту и прыжки в длину:
Заключение
Ну вот, собственно, и всё. Как я и обещал, игры как таковой у меня нет, но есть играбельная демка всего из 296 строк кода.
Моей задачей было лишь объяснить основные принципы построения примитивного платформера, и мне очень любопытно будет посмотреть, что выдадут мои студенты в этом году. Любые комментарии и идеи приветствуются, как по улучшению этого кода, так и по поводу того, что можно сделать ещё.
Создаем платформер за четыре вечера
Сразу оговорюсь: речь пойдет о гейм-девелопменте для души. Чтобы не стыдно было показать игру миру — придется попотеть подольше.
Как и многие, я стал программистом из-за детской мечты написать собственную игру. Со временем я посвятил себя другой области, но однажды решил во что бы то ни стало воплотить мечту в жизнь. Здесь я хочу поделиться своим опытом, чтобы, возможно, вдохновить вас.
1. Цель
Моей целью было создание классического платформера, но с такими сюжетом, персонажами и уровнями, которые интересны мне. Я хотел сделать игру своими силами и специально для себя; при этом сделать ее с минимальными затратами сил и своего свободного времени.
2. Поиск инструментов
Конечно, создавать геймплей с нуля неразумно. Я начал с поиска конструкторов и сред разработки игр и нашел достаточно большой список на сайте [http://gcup.ru/load/konstruktory_igr/12]. Отдельного внимания хочу удостоить конструкторы, позволяющие быстро создать тематические игры: например, Open Zelda [http://openzelda.net/] или Mario Worker [http://www.softendo.com/game/Mario%20Worker]. Настоящий клад для фанатов.
Не редкость и конструкторы, которые позволяют создавать игры классических жанров (в том числе и платформеры), и при этом вообще не требуют навыков программирования. Например, 001 Game Creator [http://www.engine001.com/] или Rune Sword [http://www.runesword.com/].
При всей своей функциональности, подобные конструкторы в некоторой мере ограничивают творца в возможностях. Кроме того, большинство фич этих инструментов, таких как портирование игр на Андроид, платные.
Среди прочих средств проектирования игр, мое внимание привлек Tululoo Game maker [http://tululoo.com/].
Преимущества налицо: полностью бесплатный инструмент, объединяющий в себе удобство готового движка с вариативностью программирования специфических аспектов игры. К тому же, готовая игра представляет собой веб-страницу на HTML5 с javascript, что позволяет выложить игру в интернет. Самым большим неудобством можно считать отсутствие отладчика. И все же, это вариант отлично решает мою задачу, поэтому на нем я и остановился.
3. Быстрый старт в Tululoo Game maker
Для тех, кто заинтересовался созданием игры с помощью Tululoo Game Maker, я рассмотрю несколько этапов работы для более быстрого и приятного старта.
4. Редактирование персонажа
Следует отметить, что в коде объекта obj_player сосредоточена основная механика игры — через взаимодействие игрока с остальными объектами игрового мира. Событие Step выполняется по таймеру каждый кадр; количество кадров в секунду настраивается при редактировании уровней (я принял значение по умолчанию — 60 кадров в секунду). Сейчас в событии Step обрабатывается нажатие клавиш и столкновение игрока с объектами ландшафта. Первым делом заменим изображение игрока на нашего героя. Нового персонажа я нарисовал в пэинте в двух вариантах: смотрящим влево и вправо, и добавил получившиеся рисунки в спрайты на вкладке Sprites. После загрузки каждой картинки по кнопке Edit можно перейти к редактированию изображения, где есть полезная опция Erase color, — с помощью нее можно стереть фон, добившись тем самым правильного контура.
Теперь необходимо вернуться к редактированию объекта obj_player и указать для него один из новых спрайтов.
Поработаем с кодом: научим объект игрока поворачиваться влево и вправо. Для этого будем менять картинку персонажа между левосторонней и правосторонней, используя свойство sprite_index (более полное описание API можно увидеть в хелпе).
obj_player событие Step:
В самом начале кода редактируем строки:
Запускаем и радуемся. Анимацию шагов делать сложнее, поэтому не буду рассматривать здесь эту процедуру.
5. Добавление объектов
Код игрока придется подредактировать: при контакте со снарядом противника obj_player будет уничтожаться. Также вынесем объект игрока в глобальную переменную.
obj_player событие Creation:
obj_player событие Collision with obj_enemy_bullet:
obj_enemy_1 событие Creation:
obj_enemy_1 событие Step:
6. Редактирование уровней
Последний шаг, важный для создания полноценного платформера — создание уровней. В Tululoo для этого весьма простой интуитивно понятный интерфейс: в конструкторе на вкладке Scenes набрасываем нужные объекты в игровую зону — и готово. Конечно, дизайн уровней — важный и самый трудоемкий процесс. Здесь настоящий простор для воображения.
Отдельно опишу часть кода, отвечающую за переключение между уровнями. Для это используется функция room_goto_next (более полное описание API можно увидеть в хелпе).
obj_player событие Step:
Заменим последнюю строку кода на:
Результирующий код этого события будет выглядеть так:
obj_player событие Step:
7. Резюме
Существует множество способов простого создания игры своей мечты. Я разобрал лишь один из них; возможно, кто-то сможет найти более удобные инструменты, — прошу оставлять ваши отзывы о них в комментариях. Желаю Вам творческих успехов!