Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Platformer

Основы физики 2D платформера, часть 4

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Basic 2D Platformer Physics .
Basic 2D Platformer Physics, Part 3
Basic 2D Platformer Physics, Part 5: Object vs. Object Collision Detection

Russian (Pусский) translation by Alexey Malyutin (you can also view the original English article)

Захват уступа

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

Давайте рассмотрим, как мы определяем, может ли быть захвачен уступ. Чтобы определить, может ли персонаж ухватиться за уступ, мы постоянно проверяем сторону, в которую движется персонаж. Если мы находим пустой тайл  на верхнем крае AABB, а под ним сплошной тайл, тогда верхний край сплошного тайла - это уступ, за который может ухватиться наш персонаж.

Установка переменных

Давайте обратимся к нашему классу Character, в котором мы реализуем захват уступов. Нет смысла делать это в классе MovingObject, так как большинство объектов не имеют возможности захвата уступа, поэтому будет бессмысленно делать какие-либо расчеты в этом направлении в нем.

Сначала нам нужно добавить ряд констант. Давайте начнем с создания констант смещения сенсора.

cGrabLedgeStartY и cGrabLedgeEndY это смещения от верхнего края AABB, первая константа это начальная точка сенсора, а вторая - конечная точка сенсора. Как видите, персонажу нужно будет найти уступ в пределах 2 пикселей.

Нам также нужна дополнительная константа, чтобы выровнять персонажа по тайлу, за который он только что ухватился. Для нашего персонажа установим ее в -4.

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

Реализация

Нам нужно посмотреть, можем ли мы захватить уступ из состояния прыжка, поэтому давайте отправимся туда. Сразу после того, как мы проверили, приземлился ли персонаж на землю, давайте посмотрим, выполняются ли условия захвата уступа. Первичные условия следующие:

  • Вертикальная скорость меньше или равна нулю (персонаж падает)
  • Персонаж не находится на потолке - если вы не можете спрыгнуть с него, бессмысленно захватывать уступ.
  • Персонаж столкнулся со стеной и двигается в ее направлении.

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

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


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

Давайте получим координаты тайлов, которые нам нужно проверить, начнем с объявления переменных. Мы будем проверять тайлы в одном столбце, поэтому все, что нам понадобится - это координата X столбца, и его верхние и нижние координаты Y.

Давайте получим координату X угла AABB.

Мы должны начать искать уступ из позиции предыдущего кадра, только если мы на самом деле уже двигаемся в направлении стены, в которую давим в этот момент - поэтому позиция персонажа по оси X не меняется.

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

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

Теперь давайте проверим, выполняет ли перебираемый нами тайл условия, которые позволяют персонажу схватиться за уступ. Условия, как объяснялось ранее, следующие:

  • Тайл пуст.
  • Тайл под ним является сплошным тайлом (это тайл, за который мы хотим ухватиться).

Следующий шаг - это рассчитать позицию угла тайла, который мы хотим схватить. Это довольно просто - нам просто нужно получить позицию тайла и затем сместить ее на размер тайла.

Теперь, когда мы знаем ее, мы должны проверить находится ли угол между точками нашего сенсора. конечно, мы должны делать, только если мы проверяем тайл относительно позиции текущего кадра, который является тайлом с координатой Y, равной bottomY.  Если это не этот случай, тогда мы можем безболезненно предположить, что прошли мимо уступа между предыдущим и текущим кадрами - и поэтому нам нужно схватить уступ в любом случае.

Теперь мы в домике, мы нашли уступ, за который мы можем схватиться. Сначала давайте сохраним позицию тайла с захватываемым уступом.

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

Помимо этого, нам нужно сделать еще кое-какие дела, такие, как сбросить скорость в ноль и изменить состояние на CharacterState.GrabLedge. После этого мы можем прервать цикл, потому что смысла перебирать оставшиеся тайлы больше нет.

Вот это будет то, что надо! Теперь уступы могут быть обнаружены и захвачены, и теперь нам просто нужно реализовать состояние GrabLedge, которое мы пропустили ранее.

Управление захватом уступа

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

Реализация управления

Первое, что нам здесь нужно сделать это определить, находится ли уступ слева или справа от персонажа. Мы можем это сделать, так как мы сохранили координаты уступа, за который ухватился персонаж.

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

  • нажать клавишу вниз
  • нажать клавишу влево, когда мы держимся за уступ справа, или
  • нажать клавишу вправо, когда мы держимся за уступ слева

Здесь есть маленькая оговорка. Представьте ситуацию, когда мы удерживаете клавиши вниз и вправо, при этом персонаж держится за уступ справа. Это приведет к следующей ситуации:

Проблема здесь в том, что персонаж хватается за уступ сразу, как только он его отпускает.

Простое решение этой проблемы - это заблокировать движение в направлении уступа на несколько кадров после того, как мы спрыгнули с него. Для этого нам нужно добавить две новые переменные; давайте назовем их mCannotGoLeftFrames и mCannotGoRightFrames.

Когда персонаж падает с уступа, нам нужно установить эти переменные и изменить состояние на прыжок.

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

Как видите, таким образом мы не выполняем условия, необходимые для захвата уступа так долго, пока блокируемое направление совпадает с направлением на уступ, который персонаж может попытаться захватить. Каждый раз, когда мы отклоняем конкретные вводимые данные, мы отнимаем единицу от оставшихся кадров блокировки, и в конце концов мы станем способны двигаться снова - в нашем случае, после 3-х кадров.

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

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

Вот, в общем- то и все! Теперь захват уступов должен работать правильно в любых ситуациях.

Позволим персонажу прыгать сразу после покидания платформы

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

Теперь давайте воплотим такую механику. Прежде всего нам нужно добавить константу - сколько кадров может пройти с момента, когда персонаж сошел с платформы и все еще может выполнить прыжок.

Нам также понадобится счетчик кадров в классе Character, тогда мы будем знать, сколько кадров персонаж уже находится в воздухе.

Теперь давайте устанавливать в 0 mFramesFromJumpStart каждый раз, когда мы оторвались от земли. Давайте сделаем это сразу после вызова UpdatePhysics.

И давайте будем увеличивать его на единицу каждый кадр, пока мы находимся в состоянии прыжка.

Если мы в состоянии прыжка, мы не можем позволить прыжок в воздухе, если мы возле потолка, или же у нас положительная вертикальная скорость. Положительная вертикальная скорость означает, что персонаж не пропустил прыжок.

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

Вот и все! Мы можем установить cJumpFramesThreshold в большое значение, например 10 кадров, чтобы убедиться, что это работает.

Эффект здесь достаточно преувеличен. Это не очень заметно, если мы позволяем персонажу прыгать спустя всего 1-4 кадра после того, когда он фактически больше не находится на твердой поверхности, но в целом это позволяет нам менять, насколько мягкими мы хотим сделать наши прыжки.

Масштабирование объектов

Давайте добавим возможность масштабирования объектов. У нас уже есть mScale в классе MovingObject, поэтому все, что нам нужно сделать, это убедиться, что эта переменная корректно влияет на AABB и его смещение.

Сначала давайте отредактируем наш класс AABB, так, чтобы он получил компонент масштаба.

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

Мы также хотим иметь возможность получать или устанавливать только X или Y значение половинного размера, поэтому нам нужно сделать отдельные функции получения и установки также и для них.

Кроме масштабирования самого AABB, нам также нужно масштабировать mAABBOffset, так, чтобы после того, как мы отмасштабировали объект, его спрайт все еще совпадал бы с AABB таким же образом, как он совпадал, когда объект был неотмасштабированным. Давайте вернемся к классу MovingObject, чтобы отредактировать его.

Также, как и в предыдущем случае, нам понадобится получать доступ к компонентам X и Y отдельно.

Наконец, нам также нужно убедиться, что когда масштаб изменен в MovingObject, она также изменен и в AABB. Масштаб объекта может быть отрицательным, но сам AABB не должен иметь отрицательный масштаб, потому что мы полагаемся на то, что половинный размер всегда будет положительным. Вот почему вместо простой передачи масштаба в AABB, мы будем передавать масштаб, все компоненты которого положительны.

Все, что нам осталось сделать, это убедиться всегда, когда мы напрямую используем переменные, мы работаем с ними через процедуры их получения и установки. Всегда, когда мы использовали halfSize.x, мы будем использовать HalfSizeX, всегда, когда мы использовали halfSize.y, мы будем использовать HalfSizeY, и т.д. Несколько вызовов функции поиска и замены должны справиться с этим хорошо.

Проверим результаты

Масштабирование должно теперь работать хорошо, и благодаря способу, которым мы построили наши функции обнаружения столкновения, неважно, гигантский у нас персонаж или маленький - он должен хорошо взаимодействовать с картой.

Заключение

Этот урок завершает нашу работу с тайловой картой. В следующих уроках мы будем ставить вопросы обнаружения столкновения между объектами.

Это займет некоторое время и усилия, но система в целом должна получиться очень крепкая. Одна вещь, которой возможно не хватает прямо сейчас, это поддержка склонов. Многие игры обходятся без них, но многие их используют, поэтому это величайшая задача для улучшения этой системы. Спасибо за усидчивое чтение, встретимся на следующем уроке!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.