Моей целью было создать исчерпывающий и понятный путеводитель по реализации 2Д платформеров.
Оговорка: часть информации в этой статье получена путём реверсивного проектирования поведения игры, а не из исходного кода или от программистов. Возможно, что на самом деле игра реализована не так, как описано в статье, а просто ведёт себя схожим образом. Также стоит отметить, что размеры сетки тайлов для игровой логики могут отличаться от размеров графических тайлов.
Четыре решения
Мне приходит на ум четыре основных варианта решений при создании платформера. В рамках этой статьи будут рассмотрены все четыре, но из-за большого объема статья поделена на 2 части (прим. пер).
От простого к наиболее сложному это:
Тип №1: Тайловый (чистый)
В нём позиционирование персонажа ограничено сеткой тайлов, таким образом, он никогда не сможет встать между двумя тайлами. Для создания иллюзии плавного передвижения могут быть использованы различные анимации, однако, согласно игровой логике, персонаж всегда находится прямо на конкретном тайле. Это самый простой способ сделать платформер, однако он накладывает большие ограничения на контроль персонажа, делая его неприемлемым для традиционных экшн-платформеров. Тем не менее, он очень популярен для паззлов и «кинематографичных» платформеров.
Flashback, сетка тайлов
Примеры: Prince of Persia, Toki Tori, Lode Runner, Flashback
Как оно работает
Карта — это сетка тайлов, каждый из которых содержит информацию о свойствах тайла: препятствие это или нет, какое изображение использовать, какие проигрывать звуки шагов и так далее. А игрок и другие персонажи представлены набором из одного или нескольких тайлов, движущихся вместе. Например, в Lode Runner игрок является одним тайлом. В Toki Tori же игрок 2х2 тайла. А в игре Flashback, которая довольно необычна из-за небольшого размера сетки тайлов, игрок имеет габариты в два тайла в ширину и пять в высоту (см. изображение выше) когда стоит, но всего три тайла в высоту, когда пригибается.
В играх такого типа, персонаж обычно не двигается по диагонали, а если и двигается, то, движение можно разложить на два отдельных шага. Движение на несколько тайлов можно сделать множественными сдвигами одиночных тайлов, если нужно (во Flashback игрок всегда движется по два тайла за раз). Складывается следующий алгоритм:
- Создать копию персонажа там, где он должен оказаться (т.е. если нужно сдвинуться на 1 тайл вправо, нужно сделать копию, где каждый тайл персонажа сдвинут на один тайл вправо)
- Проверить эту копию на пересечение с фоном и другими персонажами
- Если найдено пересечение, то передвижение персонажа заблокировано. Нужно отреагировать соответствующим образом.
- В противном случае путь чист. Передвигайте персонажа сюда, воспроизводя анимацию, если необходимо, чтобы перемещение выглядело плавным.
Этот тип движения совершенно непригоден для обычных прыжков «по дуге», поэтому игры такого жанра вовсе лишены прыжков (Toki Tori, Lode Runner) или разрешают только горизонтальные или только вертикальные прыжки фиксированной длины (Prince of Persia, Flashback), которые есть ни что иное как обычное линейное движение. Преимущества этой системы — простота и точность. Такие игры более детерминированы, что ведет к меньшей вероятности появления глюков и более контролируемому геймплею, не требующему слишком часто подстраивать значения в зависимости от обстоятельств. Фиксированные расстояния для взаимодействия дают возможность сделать красивую бесшовную анимацию. Значительно упрощается реализация некоторых игровых механик (таких как ухват за выступ и односторонние платформы) — всё что нужно сделать, это проверить, удовлетворяют ли тайлы фона в нужной позиции необходимым условиям.
Конечно, эта система не даёт делать шаги менее чем на один тайл, но шаги можно уменьшать разными способами. Например, тайлы могут быть чуть меньше, чем игрок (скажем, игрок 2х6 тайлов), или можно разрешить «только визуальное» движение, чтобы перемещаться внутри выбранного тайла без изменения логики (я полагаю, что это решение применено в «Lode Runner – The Legend Returns»)
Тип №2: Тайловый (Плавный)
Столкновения по прежнему определяются сеткой тайлов, но персонажи могут двигаться в мире свободно (обычно с разрешением в 1 пиксель. см. замечание в конце статьи относительно плавности движения). Это наиболее частая форма реализации платформеров на 8-ми и 16-ти битных консолях, остающаяся популярной и сегодня, так как крайне легка в реализации и позволяет редактировать уровень намного проще чем с применением более сложных техник. Также она позволяет делать уклоны и плавные прыжки по дуге.
Если вы не уверены какой именно платформер хотите сделать, но точно экшн, то я предлагаю остановиться на этом типе. Неудивительно, что подавляющее большинство лучших экшн-платформеров всех времён основываются именно на этом методе.
Mega Man X, с сеткой и прямоугольником персонажа.
Примеры: Super Mario World, Sonic the Hedgehog, Mega Man, Super Metroid, Contra, Metal Slug, и практически любой платформер 16-битной эры
Как это работает
Информация о карте хранится также, как и в чисто тайловом методе. Разница только в том, как персонаж взаимодействует с фоном. Теперь у персонажа есть описывающий его прямоугольник для просчета столкновений (AABB, который не может вращаться), и, обычно, по размеру является кратным размеру тайла. Стандартные размеры вроде одного тайла в ширину и один (маленький Mario, пригнувшаяся Samus), два (большой Mario, Mega Man, пригнувшаяся Samus) или три (стоящая Samus) тайла в высоту. Во многих случаях визуально спрайт персонажа больше, чем логический прямоугольник, так как это делает внешний вид игры более приятным и геймплей — более честным (согласитесь, что для игрока лучше избежать попадания, когда он должен его получить, чем получить когда не должен).
На изображении выше, можно заметить, что спрайт с персонажем «X» квадратный (два тайла шириной), однако описывающий его прямоугольник шириной только в один тайл.
При условии, что нет уклонов и односторонних платформ, алгоритм прост:
- Разложить движение на оси X и Y, делать одно перемещение за раз. Если планируется позже добавить уклоны, тогда сначала по X, затем по Y. В противном случае порядок абсолютно не важен. Затем для каждой оси:
Получить координату грани в направлении движения. Например: если двигаться влево, X координата левой грани описывающего прямоугольника. Если вправо, X координата правой стороны. Если верх, Y координата верха и так далее. - Определить какие линии тайлов пересекаются с описывающим прямоугольником — это даст минимальное и максимальное значение тайла на ДРУГОЙ оси. Например, если мы движемся влево, предположим игрок пересекается с горизонтальными линиями 32, 33 и 34 (вот оно, тайлы с Y = 32 * TS, Y = 33 * TS и Y = 34 * TS, где TS = размер тайла).
- Изучите эти линии с тайлами в направлении движения, пока не найдете ближайшее препятствие. Затем в цикле смотрите на каждое движущееся препятствие и определите, какое из всех наиболее близкое на вашем пути.
- Результирующее движение игрока вдоль этого направления это минимум между расстоянием до ближайшего препятствия и дальностью хода игрока.
- Передвинуть игрока на новую позицию. С этой позиции обрабатывайте другую координату, если еще не обработали.
Уклоны
Mega Man X, с комментариями к уклонам
Уклоны (тайлы, на которые указывают зеленые стрелки) слегка мудрёная штука, так как они и препятствия и в то же время позволяют персонажу заходить на их тайл. Также они вызывают изменение Y координаты при простом перемещении вдоль оси Х. Простой способ сделать их — это позволить тайлу хранить информацию о «высоте пола» с каждой стороны. Допустим система координат с нулём в левом верхнем углу, тогда тайл слева от X (героя), первый тайл уклона, будет содержать высоты {0, 3}. Тот, на котором он стоит будет содержать {4, 7}, затем {8, 11}, потом {12, 15}. После чего всё повториться снова с {0, 3} и так далее. После мы видим уклон с большим углом, собранный из двух тайлов {0, 7} и {8, 15}.
Детальный вид тайла {4, 7}
Метод, который я собираюсь описать позволяет делать произвольные уклоны, несмотря на очевидные причины, эти два уклона наиболее распространены и получаются из 12 тайлов (6 описано ранее и их зеркальные копии). Алгоритм коллизий изменяется для горизонтального движения:
- Убедитесь, что передвижение по оси X происходит раньше чем по Y.
- Во время проверки столкновений (4 пункт выше по тексту) уклон считается столкновением только если его ближайшая грань наивысшая (меньше Y координаты). Это предотвратит ситуацию с подёргиванием персонажа при движении с другой стороны.
- Возможно вы захотите запретить персонажу останавливаться на полпути уклона (например на {4, 7} тайле). Эти ограничения приняты в Mega Man X и многих других играх. Если не захотите, то вам придется разбираться с еще более сложным случаем, когда игрок пытается забраться с нижней стороны тайла с уклоном. Один вариант побороть это — обработать уровень и пометить все подобные тайлы. Тогда при обнаружении столкновений нужно также считать это столкновением от нижней части игрока, если наименьшая Y координата игрока ниже выпирающей части тайла (координата тайла * размер тайла + уровень пола y).
- Целый тайл с препятствием, смежный с уклоном, если на нем стоит игрок, не должен считаться будто он прикреплен к уклону. Тоесть если персонаж (его нижний центральный пиксель) на уклоне {0, *}, нужно игнорировать левый тайл, а если на улоне {*, 0} — игнорировать правый. Можно делать так для большего числа тайлов, если персонаж шире, чем два тайла — просто скидывать проверку всего ряда, если игрок двигается навстречу верхней части уклона. Причина, для того чтобы это делать, в том, чтобы предотвратить застревание персонажа в этих тайлах (подсвечены желтым на скриншоте выше), пока он забирается на уклон и его ступни будут ниже «уровня поверхности» до тех пор. пока он не поднимется до уровня прямых тайлов.
И для вертикального передвижения:
- Если позволить гравитации делать свою работу для спуска по склону, убедитесь, что минимальное смещение гравитацией совместимо с уклоном и горизонтальной скоростью. Например, на 4:1 уклоне (на скриншоте {4, 7} выше) гравитационный сдвиг должен быть как минимум 1\4 горизонтальной скорости округленной вверх. На склоне 2:1 (таком как {0, 7}) минимально 1\2. Если это не учесть, игрок будет двигаться горизонтально до конца рампы в течении времени, пока гравитация не поймает и не бросит его вниз, заставляя его скакать на наклонной плоскости вместо плавного спуска по ней.
- Альтернативой использования гравитации можно взять расчет разницы количества пикселей между высотой игрока до начала и после окончания движения (использую формулу ниже) и менять его позицию, чтобы всё совпадало.
- Когда движетесь вниз возьмите для расчета не наивысшую грань уклона для просчета столкновений, а текущую точку на вертикали с позицией игрока. Чтобы это сделать найдите [0, 1] значение, которое определит расположение игрока на тайле (0 = лево, 1 = право) и используйте линейную интерполяцию floorY значений. Код будет выглядеть как-то так:
float t = float(centerX - tileX) / tileSize; float floorY = (1-t) * leftFloorY + t * rightFloorY;
- При движении вниз, если несколько тайлов с препятствиями находится на одной Y координате и одна на X координате центра игрока — тайл уклона, то используйте именно его, игнорируйте остальные, даже если они, технически, ближе. Это обеспечит более верное поведение на краях уклонов и не позволит персонажу «съезжать» на совершенно ровном тайле только потому, что рядом есть уклон.
Односторонние платформы
Super Mario World, где Mario сваливается сквозь (слева) и стоит на (справа) одной и той же односторонней платформе
Односторонние платформы — это обычные платформы на которые можно встать, но при этом можно запрыгнуть сквозь них снизу. Другими словами, они считаются препятствием если вы стоите на них, и не считаются, если вы прыгаете снизу. Это полностью описывает их поведение. Алгоритм немного меняется:
- по X координате этот тайл никогда не бывает препятствием
- По Y координате этот тайл препятствие только при движении сверху вниз и только при координате игрока больше (хотя бы на 1 пиксель, когда он стоит), чем верхняя грань тайла.
Довольно заманчиво завязать поведение на позитивное значение вертикальный скорости игрока (если игрок падает), но это будет не верно: игрок может, при прыжке, пересечь платформу, но затем начать падать вниз снова, не успев поставить ноги на платформу. В этом случае он должен по прежнему проваливаться сквозь неё.
Некоторые игры позволяют игроку спрыгивать вниз с таких платформ. Есть несколько путей решения, но они все относительно простые. Например, можно отключить односторонние платформы на один кадр и убедиться, что вертикальная скорость как минимум в один пиксель (так, что персонаж будет ниже платформы на следующем кадре), или можно проверить стоит ли он на односторонней платформе, и если стоит, то сдвинуть персонажа на один пиксель вниз.
Лестницы
Mega Man 7, с сеткой тайлов, подсвеченными тайлами лестницы, и прямоугольник игрока.
Лестницы могут показаться сложной штукой, но реализуются они довольно просто — когда персонаж на лестнице, он просто игнорирует большинство коллизий и переопределяет их новым набором правил. Лестницы обычно в один тайл шириной.
На лестницу можно попасть двумя путями:
- Описывающий персонажа прямоугольник пересекается с лестницей, неважно в воздухе или на земле и нажатием кнопки «вверх» (некоторые игры позволяют нажимать «вниз»)
- Персонаж стоит на вершине тайла лестницы (который часто является тайлом односторонней платформы, так что по нему можно ходить) и жмет «вниз».
Это немедленно вызывает эффект привязывания координаты Х игрока к тайлам лестницы, и если двигаться сверху лестницы вниз, нужно просто менять У координату, поскольку игрок уже находится внутри реальной лестницы. Начиная с этого момента некоторые игры начинают использовать другой описывающий прямоугольник, чтобы определять находится ли игрок до сих пор на лестнице. Mega Man, например, видимо использует одиночный тайл (эквивалент верхнего тайла обычного персонажа, обведенный красным на картинке выше).
Чтобы покинуть лестницу есть несколько вариантов:
- Достигнуть верха лестницы. Обычно это оканчивает анимацию и перемещает игрока на несколько пикселей вверх по У, чтобы он теперь просто стоял на верху лестницы.
- Достигнуть низа висящей лестницы. Это приведёт к тому, что игрок просто упадёт, хотя некоторые игры просто не дадут покинуть лестницу таким путём.
- Сдвинуться влево или вправо. Если сбоку нет препятствий, игроку может быть позволено сбежать таким путём.
- Спрыгнуть. Некоторые игры позволяют освободить лестницу даже так.
Пока игрок находится на лестнице, поведение персонажа меняется так, что обычно он может двигаться вверх и вниз и иногда атаковать.
Ступеньки
Castlevania: Dracula X, с сеткой тайлов
Ступеньки это разновидность лестниц, как замечено в некоторых играх, но особенно в серии Castlevania. Реализация очень похожа на лестницы с некоторыми оговорками:
- Игрок двигается тайл за тайлом или по половинке тайла (как в Dracula X)
- Каждый «шаг» сдвигает игрока одновременно и по Х и по У координатам на фиксированную величину.
- Определение стартового соприкосновения с лестницей при начале подъема должно смотреть на тайл вперёд вместо того, с которым идёт пересечение сейчас.
Другие игры могут реализовывать лестницы как уклоны. В этом случае лестницы несут больше декоративных характер.
Двигающиеся платформы
Super Mario World
Двигающиеся платформы могут показаться хитрыми, но на самом деле крайне просты. В отличие от нормальных платформ их нельзя представить фиксированными тайлами (по очевидным причинам), а необходимо описать их AABB прямоугольником. Это обычное препятствие для всех видов столкновений. Если останоиться на этом, то это будут очень скользкие платформы (они будут работать, но как и задумано, за исключением того, что персонаж не будет двигаться вместе с ними).
Есть несколько вариантов решения. Разберём один алгоритм:
- До того как в сцене что-то сдвинетcя, надо определить стоит ли персонаж на подвижной платформе. Этого можно добиться проверкой, например, находится ли центральный пиксель игрока на один пиксель выше поверхности платформы. Если да, сохраним указатель на платформу где-нибудь внутри персонажа.
- Сдвигаем все подвижные платформы. Убедимся что сделали это до того как начали двигать персонажей.
- Для каждого персонажа, который стоит на платформе, посчитаем дельту смещения платформы (насколько сдвинулась по каждой оси). Теперь сдвинем персонажа на тоже самое значение.
- Теперь двигаем персонажей, как и обычно.
Другие фичи
Sonic the Hedgehog 2
Есть игры, которые располагают сложными и эксклюзивными фичами. Например серия Sonic the Hedgehog. Эти фичи выходят за рамки этой статьи (и моих знаний, что очень важно!), но могут стать темой будущей статьи.
Продолжение (окончание)
Комментарии (30)
Antares19
02.09.2015 01:40+2Этот пост пронизан духом Старой Школы :)
Побольше бы такого с конкретными хаками и приёмами. Все это может наталкивать на интересные мысли при разработке игр смежных жанров.
Mercury13
02.09.2015 02:02Из собственного опыта. Надо указывать, на чём герой стоит ногами, и если стоит — двигать его по «направляющей».
Если на движущейся платформе — то со сдвигом платформы двигать и героя.
Обычно платформа считается «полупроходимым» элементом, и если герой на ней стоит — то проезжает через другие платформы. Если непроходима — придётся толкать героя.
HunterXXI
02.09.2015 10:07Очень позновательно и грамотно все изложено. Даже учитывая тот факт, что я не разработчик, было крайне интересно читать. Жду продолжения!
Darthman
02.09.2015 10:12+3Спасибо, работаю над продолжением. Очень постараюсь уложиться до среды, возможно даже в выходные доделаю и сверстаю.
Athari
Ну вот, остановились на самом интересном. :) А я ждал разбора Соника с бегом по стенам, петлям и спиралям; современных игр типа Реймана с платформами произвольной формы и произвольной физикой и т. д. и т. п. На векторном способе автор не задержался…
Не уверен насчёт существования «чистого тайлового» типа, практически во всех играх можно же располагать персонажа по оси X произвольно. Принц бегает и шагает рывками, что несколько сбивает с толку и раздражает, но поставить его при желании можно где угодно с помощью прыжков и приседаний. Противники его положение учитывают до пикселя. Насчёт Лодеруннера не уверен, там действительно происходит некоторое округление до клетки при любом действии, особенно перескоки заметны при стрельбе между клеток, но координаты у него с точностью до пикселя, и противники явно не убивают его при соприкосновении ближайших клеток.
Antares19
Я смотрю в оригинале перевода там продолжение есть. Весьма интересное :)
Athari
Продолжение-то есть, но ни Соника, ни детальных векторов…
А, в комментариях запостили: info.sonicretro.org/Sonic_Physics_Guide
Darthman
Продолжение будет, но статья очень обширная. Поэтому я разбил на 2 части. Постараюсь в течение недели выложить продолжение. Его надо хорошенько вычитать. У автора местами язык слишком костный.
Darthman
Принц и флешбек не позволяет. Ты нажал один раз вбок и персонаж пошел на следующий тайл. Мне кажется автор очень даже прав. Хотя плавный тип с произвольным перемещением куда более явно распостранен.
khim
Нужно только учитывать, что тайлы у принце довольно маленькие. Когда вы делате движение с нажатой клавишей Shift от пикслов на пять перемещается. И в игре есть несколько моментов где это важно. Но как только вы попытаетесь куда-нибудь залезть/слезть — он выравняется на границу «большого» тайла.
Athari
С помощью прыжков вверх можно очень точно позиционировать принца. А размер аккуратного шага ещё и уменьшается, если стоять в половине шага от обрыва.
Выравнивание у принца не по тайлу, а по стенке, на которую он лезет. Анимация просто с пиксельной точностью выравнивается, чтобы руки в воздухе не висели. По сути просто округление, чтобы пиксель-хантингом не заниматься для того, чтобы запрыгнуть.
Athari
Вы в Принца играли вообще? Дольше одной минуты?
Попробуйте двигаться с помощью шага, а не бега (Shift). Попробуйте прыгать на месте вверх (Up). Попробуйте опускаться вниз и сразу подниматься (Down). И обратите внимание на координату X.
Darthman
Играл, ещё в незапамятные времена на commodore 64, в конце 80х. И довольно много играл.
Athari
Запустил досовую версию и подвигался туда-сюда.
Сколько здесь «тайл»? Два пикселя?
Darthman
Заинтриговали. Память иначе рисовала мне эту игру. Приду домой, погоняю эмулятор C64.
sourcerer
Это, кажется, VGA-версия.
sourcerer
Пиксельный скроллиг же.
Athari
Не понял. В смысле?
sourcerer
Появление сайд-скроллеров стало возможно благодаря технологии попиксельной прокрутки тайлового экрана — пиксельного скроллинга. Поэтому границы тайлов стало выделить очень и очень сложно. И, конечно, автор статьи не совсем прав — далеко не во всех играх координаты персонажа хранятся в размерах тайловой сетки, а не в абсолютных значениях.
Прошу прощения, не заметил, что в примерах чистых тайлов указан и Prince Persia. Вероятно, это ошибка.
Darthman
опять-таки, чего далеко ходить Loderunner не позволял остановиться между тайлами вообще.
khim
Какая, к терапевту, «граница между тайлами»? Изначально Loderunner был программой для текстового режима! Работал на VAX'е, был написан на смеси Фортрана и Паскаля… да, были времена…
Darthman
Так и речь о том, что чаще всего это крайне старые игры. Или тупо не динамичные вовсе, где динамика, экшн, не в приоритете. Я об этом.
Athari
Если бы в статье в качестве примера был приведён один из ископаемых Лодеруннеров, вообще не было бы вопросов. Но там ведь про Legend Returns — очень даже продвинутого Лодеруннера из середины девяностых. Почему автор решил, что в игре используется только имитация движения между клетками — я не понял.
Darthman
Ну так статью не я писал. По мне, так именно классический куда больше и точнее описывает данный метод. Я и так позволил себе украшать статью и местами перефразировать совершенно иначе. Но вот списков примеров я не трогал.
Автор пишет о реверс-инжиниринге, возможно он всё-таки порылся во внутренностях реализации, отсюда и такой вывод сделал. Сложно сказать.
Eefrit
Потому что так и есть. Анимация персонажа сделана так, чтобы игрок этого практически не замечал, но по факту каждый игровой уровень — это набор квадратов, и любые действия можно совершать только в границах этих квадратов. Например, когда герой совершает обычное для игры действие — копает яму слева или справа — героя двигает немного левее или правее, в зависимости от того, где он стоит, чтобы анимация выкапывания ямки совпала с квадратом, который будет выкопан. То же самое касается и всех используемых предметов — газ распыляется на три квадрата влево или вправо (герой оказывается в центре клетки, с которой он применяет газ), клей — аналогично, бомбы — аналогично. Все действия привязаны именно к этим квадратам, хотя анимация и сделана так, чтобы игрок этого почти не замечал.
Alexsey
Физика в Сонике это ад. Я пытался ее воссоздать, но моих весьма посредственных знаний в математике явно не хватило даже не смотря на помощь в виде Sonic Physics Guide и различных исходников. Так что я бы тоже не отказался от подробного разбора этого дела. :)
Athari
Хм… По Сонику, наверное, фанатские игры в достатке, какие-нибудь опен-сорсные должны быть. Или глухо?
Alexsey
Не, есть то они есть и позволяют примерно понять как оно работает — с десяток различных «сенсоров» для определения положения, наклона и тд + попиксельные коллизии между ними и поверхностями. У меня просто с математикой совсем туго. :) Базовую физику я сделал, но как только дело дошло до создания 360-градусного вращения персонажа (хотя правильнее сказать — сенсоров), я спасовал. :)