Кинематический персонаж (2D)

Введение

Да, название звучит странно. «Кинематический персонаж». Что это? Причина такого названия в том, что когда появились физические движки, они назывались движками динамики (потому что они работали в основном с реакцией на столкновения). Было предпринято много попыток создать контроллер персонажа, используя движки динамики, но это оказалось не так просто, как казалось. Godot имеет одну из лучших реализаций динамического контроллера персонажа, которую вы можете найти (это можно увидеть в демонстрации 2d/платформера), но ее использование требует значительного уровня мастерства и понимания физических движков (или большого терпения на пробы и ошибки).

Некоторые физические движки, такие, как Havok, преподносят динамические контроллеры персонажей как лучший вариант, в то же время как другие (PhysX), напротив, продвигают только кинематику.

Так, а в чём разница?:

  • Динамический контроллер персонажа использует твёрдое тело с бесконечным тензором инерции. Это твёрдое тело, которое не может вращаться. Физические движки всегда позволяют объектам двигаться и сталкиваться, а затем решают их столкновения разом. Это делает динамические контроллеры персонажей способными бесшовно взаимодействовать с другими объектами физики, как показано в демо-версии платформера. Однако, эти взаимодействия не всегда предсказуемы. Столкновения могут длиться более одного кадра для завершения расчётов, и несколько столкновений могут оказаться смещёнными на несколько бит. Эти проблемы могут быть устранены, но требуют определенной квалификации.

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

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

Обработка физики

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

extends KinematicBody2D

func _physics_process(delta):
    pass
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
    }
}

Настройка сцены

Чтобы проверить что-то, вот сцена (из учебного пособия tilemap): kbscene.zip. Мы создадим новую сцену для персонажа. Используйте спрайт робота и создайте сцену следующим образом:

../../_images/kbscene.png

Вы заметите, что рядом с нашим узлом CollisionShape2D есть значок предупреждения; это потому, что мы не определили форму для него. Создайте новый CircleShape2D в свойстве формы CollisionShape2D. Нажмите кнопку < CircleShape2D >, чтобы перейти к его параметрам, и установите радиус, равный 30:

../../_images/kbradius.png

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

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

Наконец, инстанцируйте эту сцену персонажа в tilemap и сделайте сцену карты главной, чтобы она запускалась при нажатии на воспроизведение.

../../_images/kbinstance.png

Перемещение кинематического персонажа

Вернитесь к сцене персонажа и откройте скрипт, магия начинается сейчас! Кинематическое тело по умолчанию ничего не делает, но имеет полезную функцию KinematicBody2D.move_and_collide(). Эта функция принимает Vector2 в качестве аргумента и пытается применить это движение к кинематическому телу. Если происходит столкновение, она останавливается прямо в момент столкновения.

Итак, давайте сдвинем наш спрайт вниз, пока он не ударится об пол:

extends KinematicBody2D

func _physics_process(delta):
    move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    public override void _PhysicsProcess(float delta)
    {
        // Move down 1 pixel per physics frame
        MoveAndCollide(new Vector2(0, 1));
    }
}

В результате персонаж двигается, но останавливается прямо при ударе о пол. Довольно круто, да?

Следующим шагом будет добавление гравитации к миксу, путь он ведет себя немного ближе к обычному игровому персонажу:

extends KinematicBody2D

const GRAVITY = 200.0
var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    var motion = velocity * delta
    move_and_collide(motion)
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        var motion = velocity * delta;
        MoveAndCollide(motion);
    }
}

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

Это добавляет простую поддержку ходьбы при нажатии влево и вправо:

extends KinematicBody2D

const GRAVITY = 200.0
const WALK_SPEED = 200

var velocity = Vector2()

func _physics_process(delta):
    velocity.y += delta * GRAVITY

    if Input.is_action_pressed("ui_left"):
        velocity.x = -WALK_SPEED
    elif Input.is_action_pressed("ui_right"):
        velocity.x =  WALK_SPEED
    else:
        velocity.x = 0

    # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account.

    # The second parameter of "move_and_slide" is the normal pointing up.
    # In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
    move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class PhysicsScript : KinematicBody2D
{
    const float gravity = 200.0f;
    const int walkSpeed = 200;

    Vector2 velocity;

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += delta * gravity;

        if (Input.IsActionPressed("ui_left"))
        {
            velocity.x = -walkSpeed;
        }
        else if (Input.IsActionPressed("ui_right"))
        {
            velocity.x = walkSpeed;
        }
        else
        {
            velocity.x = 0;
        }

        // We don't need to multiply velocity by delta because "MoveAndSlide" already takes delta time into account.

        // The second parameter of "MoveAndSlide" is the normal pointing up.
        // In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
        MoveAndSlide(velocity, new Vector2(0, -1));
    }
}

И попробуем.

Это хорошая отправная точка для платформера. Более полную демонстрацию можно найти в demo zip, распространяемом вместе с движком, или в https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character.