Kinematic character (2D)

Wprowadzenie

Yes, the name sounds strange. „Kinematic Character”. What is that? The reason for the name is that, when physics engines came out, they were called „Dynamics” engines (because they dealt mainly with collision responses). Many attempts were made to create a character controller using the dynamics engines, but it wasn’t as easy as it seemed. Godot has one of the best implementations of dynamic character controller you can find (as it can be seen in the 2d/platformer demo), but using it requires a considerable level of skill and understanding of physics engines (or a lot of patience with trial and error).

Some physics engines, such as Havok seem to swear by dynamic character controllers as the best option, while others (PhysX) would rather promote the kinematic one.

Jaka jest więc różnica?:

  • A dynamic character controller uses a rigid body with an infinite inertia tensor. Basically, it’s a rigid body that can’t rotate. Physics engines always let objects collide, then solve their collisions all together. This makes dynamic character controllers able to interact with other physics objects seamlessly (as seen in the platformer demo), however these interactions are not always predictable. Collisions can also take more than one frame to be solved, so a few collisions may seem to displace a tiny bit. Those problems can be fixed, but require a certain amount of skill.
  • A kinematic character controller is assumed to always begin in a non-colliding state, and will always move to a non colliding state. If it starts in a colliding state, it will try to free itself (like rigid bodies do), but this is the exception, not the rule. This makes their control and motion a lot more predictable and easier to program. However, as a downside, they can’t directly interact with other physics objects (unless done by hand in code).

This short tutorial will focus on the kinematic character controller. Basically, the old-school way of handling collisions (which is not necessarily simpler under the hood, but well hidden and presented as a nice and simple API).

Proces fizyki

To manage the logic of a kinematic body or character, it is always advised to use physics process, because it’s called before physics step and its execution is in sync with physics server, also it is called the same amount of times per second, always. This makes physics and motion calculation work in a more predictable way than using regular process, which might have spikes or lose precision if the frame rate is too high or too low.

extends KinematicBody2D

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

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

Konfiguracja sceny

Oto scena (z samouczka tilemap) : kbscene.zip. Stworzymy nową scenę dla bohatera. Użyj robota sprite i stwórz taką scenę:

../../_images/kbscene.png

You’ll notice that there’s a warning icon next to our CollisionShape2D node; that’s because we haven’t defined a shape for it. Create a new CircleShape2D in the shape property of CollisionShape2D. Click on <CircleShape2D> to go to the options for it, and set the radius to 30:

../../_images/kbradius.png

Note: As mentioned before in the physics tutorial, the physics engine can’t handle scale on most types of shapes (only collision polygons, planes and segments work), so always change the parameters (such as radius) of the shape instead of scaling it. The same is also true for the kinematic/rigid/static bodies themselves, as their scale affects the shape scale.

Now, create a script for the character, the one used as an example above should work as a base.

Na koniec dodaj scenę postaci do tilemapie, i zrób mapę jako główną scenę, która zostanie uruchomiona natychmiast po naciśnięciu przycisku odtwarzania.

../../_images/kbinstance.png

Moving the kinematic character

Go back to the character scene, and open the script, the magic begins now! Kinematic body will do nothing by default, but it has a useful function called KinematicBody2D.move_and_collide(). This function takes a Vector2 as an argument, and tries to apply that motion to the kinematic body. If a collision happens, it stops right at the moment of the collision.

Przesuńmy więc nasz sprite w dół, aż uderzy o podłogę:

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));
    }
}

W rezultacie postać będzie się poruszać, ale zatrzyma się bezpośrednio po uderzeniu w podłogę. Nieźle, co?

The next step will be adding gravity to the mix, this way it behaves a little more like a regular game character:

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);
    }
}

Now the character falls smoothly. Let’s make it walk to the sides, left and right when touching the directional keys. Remember that the values being used (for speed at least) are pixels/second.

Dodaje to proste poruszanie się poprzez naciskania lewego i prawego klawisza:

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));
    }
}

Spróbuj.

Jest to dobry początek dla platformówki. Bardziej dokończone demo można znaleźć w https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic_character.