[Перевод] Путеводитель по реализации 2Д платформеров (окончание)

Окончание перевода статьи «путеводитель по реализации 2Д платформеров».
Начало

Тип №3: Битовые маски


Он подобен тайловому (плавному) методу, но вместо использования больших тайлов используется картинка для проверки столкновений для каждого пиксела. Это позволяет лучше проработать игру, но и значительно увеличивает сложность, использует больше памяти и требует что-то схожее с графическим редактором для создания уровней. Такая маска обычно не используется непосредственно для визуализации, поэтому нужны дополнительные средства — например, большое графическое изображение (подложка), индивидуально для каждого уровня. Из-за подобных проблем эта техника довольно редка в использовании, но позволяет добиться более качественных результатов, чем варианты, основанные на тайлах. Этот метод удобен для создания динамического окружения — разрушения можно просто «рисовать» в битовую маску для изменения уровня. Хороший пример — игры серии Worms.
c8decf5098ca41d0ab29bd3c988bc948.png
Worms World Party с разрушаемой топографией
Примеры: Worms, Talbot«s Odyssey

Как это работает
Основная идея очень близка к тайловому (плавному) алгоритму — мы просто решаем, что каждый пиксель это тайл, и реализуем совершенно тот же алгоритм. Все будет работать за одним небольшим исключением — уклоны. Так как уклоны теперь полностью определяются относительностью позиции двух ближайших тайлов, предыдущая техника не будет работать и придется использовать гораздо более сложный алгоритм вместо неё. Другие элеметны, вроде лестниц, также становятся хитрее.

Уклоны


da31e99fc6d94cc893919959b9947ae9.png
Talbot«s Odyssey, с маской столкновений, наложенной поверх игры.

Уклоны — это основная причина, по которой эту реализацю крайне сложно сделать верно. К несчастью, они обычно обязательны к исполнению, так как нет смысла использовать эту реализацию без уклонов. Плавное изменение геометрии уровня — это основная причина по которой вы вынуждены использовать эту систему.
Вот грубый алгоритм, использованный в Talbot«s Odyssey:

  • Совмещаем ускорение и направление движения, чтобы высчитать вектор изменения позиции (насколько двигаться по каждой оси)
  • Обрабатываем каждую ось отдельно, начиная с той, у которой абсолютная разница больше.
  • Для горизонтального движения сдвигаем AABB прямоугольник игрока на 3 пикселя вверх, чтобы он мог забираться на уклоны.
  • Сканируйте дальше, проверяя все преграды и саму битовую маску, чтобы обределить на сколько пикселей можно сдвинуться до столкновения с препятствием. Дигаем на новую позицию.
  • Если это было горизонтальное движение, двигаем на столько пикселей вверх, насколько необходимо (вообще оно должно быть не больше трёх) чтобы забраться на уклон.
  • Если в конце передвижения любой пиксель персонажа пересекается с любым препятствием, убираем передвижение по этой оси.
  • Согласно результату последнего условия, делаем тоже самое для другой оси.

Так как эта система не имеет различий в движении — двигается персонаж по уклону вниз или падает, то скорее всего придется сделать подсчет количества кадров, которые персонаж не касается пола, чтобы определить может ли он прыгать и менять анимацию. У игры Talbot это значение равно 10 кадрам.
Другая хитрость здесь это эффективный подсчет того, на сколько пикселей можно сдвигаться до столкновения с чем-либо. Возникают другие возможные усложняющие факторы, такие как односторонние платформы (работающие точно тем же образом, что и в тайловом подходе) и крутые уклоны, по которым игрок скользит вниз, но не может забраться на них (которые довольно сложны и выходят за рамки этой статьи). В целом эта техника требует большого количества точной подстройки значений и в действительности менее стабильна, нежели тайловые подходы с сеткой. Я бы рекомендовал её только если нужно иметь очень детализированную местность.

Тип №4: Векторный

Эта техника использует векторные данные (линии или многоугольники) для определения граней зоны столкновений. Несмотря на очень большую сложность в корректной разработке, она становится все более распространенной благодаря обилию физических движков, таких как Box2D, которые подходят для реализации этой техники. Она дает все прелести техники битовых масок, но без огромной перегрузки памяти и использует совершенно иной метод редактирования уровней.

0dbb72b7fe9348a99f976c24ade15b73.jpg
ef722400575f4269b3c57e6587a46f09.png
Braid (редактор уровней), с видимыми слоями (верх) и многоугольниками столкновений (низ)
Примеры: Braid, Limbo

Как это работает
Вот два основных подхода к реализации:

  • Обрабатывать движение и столкновения самостоятельно, схоже с битовыми масками, но используя многоугольники для просчета пересечений объектов и движения.
  • Использовать физический движок (например Box2D)


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

Сложные объекты

Этот подход имеет также и свои уникальные проблемы. Например иногда может быть сложно сказать, стоит ли игрок на полу (из-за ошибок округления), упёрся в стену или скользит по уклону вниз. При использовании физического движка трение может стать большой проблемой, потому что вы хотите чтобы трение было большим на полу, но маленьким по сторонам тайла.
Решают это по разному, но самым популярным решением является разделение персонажа на несколько разных многоугольников, каждый с разной ролью: так получается (опционально) основное тело, затем тоненький прямоугольник для ног и два тонких прямоугольника для сторон, а также еще один для головы. Иногда они сужены, чтобы не застревать в преградах. Они могут быть с разными настройками физики, а ответные реакции (коллбэки) на столкновение могут послужить для определения состояния персонажа. Для большей информации используют сенсоры (несталкивающиеся объекты, которые используют чтобы проверить пересечение). Базовые случаи включают в себя определение достаточно ли близко персонаж к полу, чтобы совершить прыжок или он толкает стену и т.д.

Основные соображения

В зависимости от типа движения, который вы выбрали (исключая, пожалуй, тип номер 1) можно вынести несколько соображений.

Ускорение


b06a8ce76a394ddbbca2a19aa50fcc1f.png
Super Mario World (низкое ускорение), Super Metroid (среднее ускорение), Mega Man 7 (высокое ускорение)

Один из факторов, который влияет на ощущение от платформера это ускорение персонажа. Ускорение это мера изменения скорости. Когда оно низкое, персонажу нужно много времени чтобы достигнуть максимальной скорости, или остановиться, если игрок отпустил контроллер. При неправильной реализации это вызывает ощущение что персонаж «скользкий» и не дает хорошего контроля. Такое движение часто ассоциируется с играми серии Super Mario. Когда ускорение высокое, персонажу нужно очень мало (или вовсе не нужно) времени чтобы разогнаться от нуля до максимальной скорости или наоборот, что вызывает очень быстрой отклик, «дёрганое» управление, как можно наблюдать в серии Mega Man (я верю, что Mega Man на самом деле применяет бесконечное ускорение, так как он даже останавливается на полной скорости).
Даже если игра не имеет понятия ускорения в горизонтальной плоскости, она скорее всего использует его для прыжков по дуге. Иначе форма прыжка была бы треугольной.

Как это работает
Реализовать ускорение на самом деле очень просто, но есть несколько подводных камней.

  • Определите xTargetSpeed. Он должен быть 0, если игрок не трогает управление, -maxSpeed, если нажимает влево или +maxSpeed, если нажимет вправо.
  • Определите yTargetSpeed. Он должен быть 0, если игрок стоит на платформе или +terminalSpeed в ином случае.
  • Для каждой оси увеличивайте текущую скорость в направлении целевой скорости используя средневзвешенное или добавочное ускорение.


Два метода ускорения:

  • Средневзвешенное: ускорение это число («а») от 0 (без изменений) до 1(мгновенное ускорение). Используйте это значение для линейной интерполяции между целевой и текущей скоростью и устанавливайте результат как текущую скорость.
    vector2f curSpeed = a * targetSpeed + (1-a) * curSpeed;
    if (fabs(curSpeed.x) < threshold) curSpeed.x = 0;
    if (fabs(curSpeed.y) < threshold) curSpeed.y = 0;
    
    
  • Добавочное ускорение: мы определяем направление для добавления ускорения (используя функцию, которая возвращает 1 для чисел больше 0 и -1 для чисел меньше нуля), затем проверяем не промахнулись ли.
    vector2f direction = vector2f(sign(targetSpeed.x - curSpeed.x),
                                  sign(targetSpeed.y - curSpeed.y));
    curSpeed += acceleration * direction;
    if (sign(targetSpeed.x - curSpeed.x) != direction.x)
        curSpeed.x = targetSpeed.x;
    if (sign(targetSpeed.y - curSpeed.y) != direction.y)
        curSpeed.y = targetSpeed.y;
    
    

Важно добавить ускорение к скорости до того как сдвигать персонажа, иначе вы создадите задержку в один кадр в управлении (лаг).

Когда персонаж врезается в препятствие, хорошей идеей будет обнулить его скорость по этой оси, но этого может быть недостаточно. Скорость может быть больше расстояния до объекта. В некоторых реализациях в результате столкновения на большой скорости персонаж может проникнуть в препятствие. В таком случае необходимо найти величину коррекции (глубину пересечения) и отодвинуть персонажа на эту величину, либо за кадр до пересечения найти другую скорость. (прим. пер.)

Управление в прыжке


7ec6b0cd26dd46bfaaaecfbc6a54363c.png
Super Metroid, Samus исполняет «Space Jump» (с бонусом «Screw Attack»)

Прыжки в игре также просты как проверить на земле ли игрок (или чаще был ли он на земле в последние n кадров) и если да, придать ему стартовую негативную скорость по y оси (в физических терминах импульс). И дать гравитации сделать остальное.
Вот четыре варианта, которые позволяют игроку контролировать прыжок:

  • Импульс: можно увидеть в таких играх как Super Mario World и Sonic the Hedgehog. Прыжок сохраняет инерцию (в терминах реализации скорость), которуа была у персонажа до прыжка. В некоторых играх это является единственным вариантом влиять на дугу прыжка, как и в реальной жизни. Тут делать вообщем-то нечего, так оно и будет, пока вы что-то не сделаете чтобы остановить это.
  • Ускорение в воздухе: именно так, получение управления горизонтальным движением, когда находишься в воздухе. Несмотря на физическую невозможность, это очень популярная фича, так как она делает персонажа более управляемым. Почти каждый платформер имеет такую фичу, исключая игры схожие с Prince of Persia. В основном ускорение, полученное в воздухе, сильно уменьшено, так что импульс по прежнему важен. Однако некоторые игры (вроде Mega Man) дают полный контроль в воздухе.
  • Контроль подъема: другое физически невозможное действие, но тоже популярное, так как оно даёт еще больший контроль над персонажем. Чем дольше вы жмете кнопку прыжка, тем выше персонаж подлетает. Обычно это делают, подавляя гравитацию или продолжая добавлять импульс персонажу (но уменьшая при этом добавляемый импульс), пока нажата кнопка. На действие накладывается ограничение по времени, если вы не хотите, чтобы персонаж мог прыгать бесконечно. Можно представить эту реализацию, как очень короткую работу реактивного ранца — дольще задержал, выше подлетел (прим. пер.).
  • Множественные прыжки: уже находясь в воздухе, некоторые игры позволяют игроку прыгать снова, иногда бесконечное количество раз (как Space Jump в Super Metroid или полёт в Talbot«s Odyssey), или ограниченное количество прыжков до касания земли («двойной прыжок» самый частый выбор). Этого можно достичь сохраняя счетчик, который увеличивается с каждым прыжком и уменьшается, когда на земле (осторожнее обновляйте это значение, иначе можете сбросить его сразу после первого прыжка) и позволять дальнейшие прыжки если счетчик набрал малое значение. Иногда второй прыжок короче, чем первый, или работает только при подъеме — если вы начали падать, второй рывок совершить нельзя. Можно включать другие ограничения — Space Jump срабатывает только если вы уже делается крутящийся прыжок и только начали падать.

Анимации


3d7b9020d1d94b04ac9d0890fd75a952.png
Black Thorne, персонаж выполяет длинную анимацию до того как выстрелить назад

Во многих играх ваш персонаж будет проигрывать анимацию перед тем, как реально выполнит дествие. Тем не менее, в дерганных экшн играх это будет расстраивать игрока — «НЕ ДЕЛАЙ ЭТОГО!» Для плавности по прежнему необходимо иметь упреждающие анимации для прыжков и бега, но необходимо позаботиться о том, как игра отзывается. Можно делать такие анимации чисто косметическими, а само действие отрабатывать сразу.

© Habrahabr.ru