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

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

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Basic 2D Platformer Physics .
Basic 2D Platformer Physics, Part 2

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

Столкновения персонажа

Хорошо, задача описывается так: мы хотим создать 2D платформер c простой, устойчивой, отзывчивой, точной и предсказуемой физикой. Мы не хотим использовать тяжеловесный 2D физический движок в этой задаче, по нескольким причинам:

  • непредсказыемые последствия столкновений
  • трудно установить точное и устойчивое перемещение персонажа
  • более сложная работа с ним
  • потребление большей вычислительной мощности по сравнению с упрощенной физикой

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

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

Ограничения персонажа

Начнем с определения типа форм которые будут использоваться в нашей физике. Одна из наиболее базовых форм которую мы можем использовать для представления физического объекта в игре это Axis Aligned Bounding Box (AABB). По сути AABB это не повернутый прямоугольник.

Example of an AABB

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

Без дополнительных объяснений, создадим структуру нашего AABB.

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

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

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

На картинке показана эта проверка на оси x; ось y проверяется аналогично

Demonstrating a check on the X-Axis

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

Перемещение объекта

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

Теперь заполним этот класс данными. Нам потребуется достаточно много информации для данного объекта:

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

Положение, скорость и масштаб двумерных векторов.

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

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

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

Первое что мы хотим сделать это сохранить данные предыдущего кадра в соответствующие переменные

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

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

После этого, нам также нужно обновить положение центра AABB, как это соответствует новому положению.

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

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

Управление персонажем

Данные

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

Во-первых, создадим класс Character, и отделим его от класса MovingObject.

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

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

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

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

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

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

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

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

Цикл обновления

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

Персонаж стоит на месте

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

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

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

Если клавиши GoLeft или GoRight нажаты, тогда нам нужно изменить наше состояние на ходьбу.

В случае нажатия клавиши Jump, мы хотим установить вертикальную скорость на скорость прыжка и изменить состояние на прыжок.

Это происходит именно для этого состояния, по крайней мере сейчас.

Состояние ходьбы

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

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

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

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

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

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

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

Это для ходьбы. Перейдем к состоянию прыжка.

Состояние прыжка

Начнем с настройки соответствующей анимации для спрайта.

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

Но будет разумным установить предел, так персонаж не сможет падать слишком быстро.

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

В конце, мы собираемся сделать прыжок выше, если кнопка прыжка нажата долшье. Сделав это, мы сделаем прыжок ниже, если кнопка прыжка не зажата.

Как вы можете видеть, если кнопка прыжка не зажата и вертикальная скорость положительна, тогда мы можем установить скорость на максимальное значение cMinJumpSpeed (200 пикселей  в секунду). Это значит что если мы выполним кратковременное нажатие кнопки прыжка, вместо принятой скорости mJumpSpeed (410 по умолчанию), мы получим сниженную до 200, и следовательно прыжок будет короче.

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

Обновление предыдущих нажатий клавиш.

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

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

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

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

В конце, обновим предыдущие данные нажатий клавиш.

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

Инициализация персонажа

Напишем функцию инициализации для персонажа. Эта функция будет принимать массивы данных ввода как параметры. Мы будем предоставлять их из управляющего класса позже. Кроме этого, нам нужно сделать:

  • задать масштаб
  • задать скорость прыжка
  • задать скорость ходьбы
  • задать начальное положение
  • установить AABB

Тут мы будем использовать несколько из заданных констант.

Для демонстрации, мы можем установить начальное положение в редакторе.

Для  AABB нам нужно установить смещение и половинный размер. Для демонстрации смещение спрайта должно быть просто половинным размером.

Сейчас мы можем заняться остальными переменными.

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

Дополнительно, при обновлении менеджера, нам нужно обновить игрока, и его вводимые данные.

Заметьте, что мы обновляем физику персонажа в функции FixedUpdate(). Это дает нам возможность убедиться что прыжки всегда будут на одинаковую высоту, независимо от того, с какой частотой кадров игра работает. Тут отличная статья Гленна Фидлера о том как исправить шаг времени, в случае если вы не используете Unity.

Тестирование управления персонажем

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

An animation of the character controller

Выводы

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

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

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.