2D movement overview

Introduzione

Every beginner has been there: «How do I move my character?» Depending on the style of game you’re making, you may have special requirements, but in general the movement in most 2D games is based on a small number of designs.

We’ll use KinematicBody2D for these examples, but the principles will apply to other node types (Area2D, RigidBody2D) as well.

Setup

Each example below uses the same scene setup. Start with a KinematicBody2D with two children: Sprite and CollisionShape2D. You can use the Godot icon («icon.png») for the Sprite’s texture or use any other 2D image you have.

Open Project -> Project Settings and select the «Input Map» tab. Add the following input actions (see InputEvent for details):

../../_images/movement_inputs.png

8-way movement

In this scenario, you want the user to press the four directional keys (up/left/down/right or W/A/S/D) and move in the selected direction. The name «8-way movement» comes from the fact that the player can move diagonally by pressing two keys at the same time.

../../_images/movement_8way.gif

Add a script to the kinematic body and add the following code:

extends KinematicBody2D

export (int) var speed = 200

var velocity = Vector2()

func get_input():
    velocity = Vector2()
    if Input.is_action_pressed('right'):
        velocity.x += 1
    if Input.is_action_pressed('left'):
        velocity.x -= 1
    if Input.is_action_pressed('down'):
        velocity.y += 1
    if Input.is_action_pressed('up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int speed = 200;

    public Vector2 velocity = new Vector2();

    public void GetInput()
    {
        velocity = new Vector2();

        if (Input.IsActionPressed("right"))
            velocity.x += 1;

        if (Input.IsActionPressed("left"))
            velocity.x -= 1;

        if (Input.IsActionPressed("down"))
            velocity.y += 1;

        if (Input.IsActionPressed("up"))
            velocity.y -= 1;

        velocity = velocity.Normalized() * speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity = MoveAndSlide(velocity);
    }
}

In the get_input() function we check for the four key events and sum them up to get the velocity vector. This has the benefit of making two opposite keys cancel each other out, but will also result in diagonal movement being faster due to the two directions being added together.

We can prevent that if we normalize the velocity, which means we set its length to 1, and multiply by the desired speed.

Suggerimento

Se non hai esperienza di matematica vettoriale, o necessiti di un ripasso, puoi dare un’occhiata alla spiegazione sull’utilizzo dei vettori in Godot a Vector math.

Rotazione + movimento

Questo tipo di movimento viene chiamato a volte «Stile-Asteroids» perché è riconducibile al classico gioco arcade. Premendo sinistra/destra si ruota il personaggio mentre su/giù si muove in avanti o indietro nella direzione in cui sta puntando.

../../_images/movement_rotate1.gif
extends KinematicBody2D

export (int) var speed = 200
export (float) var rotation_speed = 1.5

var velocity = Vector2()
var rotation_dir = 0

func get_input():
    rotation_dir = 0
    velocity = Vector2()
    if Input.is_action_pressed('right'):
        rotation_dir += 1
    if Input.is_action_pressed('left'):
        rotation_dir -= 1
    if Input.is_action_pressed('down'):
        velocity = Vector2(-speed, 0).rotated(rotation)
    if Input.is_action_pressed('up'):
        velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    get_input()
    rotation += rotation_dir * rotation_speed * delta
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int speed = 200;
    [Export] public float rotationSpeed = 1.5f;

    public Vector2 velocity = new Vector2();
    public int rotationDir = 0;

    public void GetInput()
    {
        rotationDir = 0;
        velocity = new Vector2();

        if (Input.IsActionPressed("right"))
            rotationDir += 1;

        if (Input.IsActionPressed("left"))
            rotationDir -= 1;

        if (Input.IsActionPressed("down"))
            velocity = new Vector2(-speed, 0).Rotated(Rotation);

        if (Input.IsActionPressed("up"))
            velocity = new Vector2(speed, 0).Rotated(Rotation);

        velocity = velocity.Normalized() * speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        Rotation += rotationDir * rotationSpeed * delta;
        velocity = MoveAndSlide(velocity);
    }
}

Qui abbiamo aggiunto due nuove variabili per controllare la rotazione e la velocità. Come previsto, premendo entrambi i tasti contemporaneamente l’effetto sarà nullo in quanto non avverrà alcuna rotazione. Questa verrà applicata direttamente alla proprietà rotation del corpo.

Per impostare la velocità, utilizzeremo il metodo Vector2.rotated(), in modo che punti nella stessa direzione del corpo. rotated() è una funzione vettoriale che si puo” utliizzare in molti casi in sostituzione di funzioni trigonometriche.

Rotazione + movimento (mouse)

Questo metodo di spostamento è molto simile al precedente. Questa volta, la direzione è determinata dalla posizione del mouse anzichè dalla tastiera. Il personaggio guarderà («look_at») sempre al puntatore del mouse. Comunque gli input avanti/indietro non varieranno.

../../_images/movement_rotate2.gif
extends KinematicBody2D

export (int) var speed = 200

var velocity = Vector2()

func get_input():
    look_at(get_global_mouse_position())
    velocity = Vector2()
    if Input.is_action_pressed('down'):
        velocity = Vector2(-speed, 0).rotated(rotation)
    if Input.is_action_pressed('up'):
        velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    get_input()
    velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int speed = 200;

    public Vector2 velocity = new Vector2();

    public void GetInput()
    {
        LookAt(GetGlobalMousePosition());
        velocity = new Vector2();

        if (Input.IsActionPressed("down"))
            velocity = new Vector2(-speed, 0).Rotated(Rotation);

        if (Input.IsActionPressed("up"))
            velocity = new Vector2(speed, 0).Rotated(Rotation);

        velocity = velocity.Normalized() * speed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity = MoveAndSlide(velocity);
    }
}

Qui utilizziamo il metodo Node2D look_at() per puntare il personaggio verso una posizione prestabilita. Se non si vuole utilizzare questa funzione, si puo” ottenere lo stesso risultato impostando l’angolo come segue:

rotation = get_global_mouse_position().angle_to_point(position)
var rotation = GetGlobalMousePosition().AngleToPoint(Position);

Clicca e muovi (Click-and-move)

Questo ultimo esempio utilizza solo il mouse per controllare il personaggio. Cliccando sullo schermo provocherà lo spostamento del giocatore verso la posizione designata.

../../_images/movement_click.gif
extends KinematicBody2D

export (int) var speed = 200

var target = Vector2()
var velocity = Vector2()

func _input(event):
    if event.is_action_pressed('click'):
        target = get_global_mouse_position()

func _physics_process(delta):
    velocity = position.direction_to(target) * speed
    # look_at(target)
    if position.distance_to(target) > 5:
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class Movement : KinematicBody2D
{
    [Export] public int speed = 200;

    public Vector2 target = new Vector2();
    public Vector2 velocity = new Vector2();

    public override void _Input(InputEvent @event)
    {
        if (@event.IsActionPressed("click"))
        {
            target = GetGlobalMousePosition();
        }
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity = Position.DirectionTo(target) * speed;
        // LookAt(target);
        if (Position.DistanceTo(target) > 5)
        {
            velocity = MoveAndSlide(velocity);
        }
    }
}

Note the distance_to() check we make prior to movement. Without this test, the body would «jitter» upon reaching the target position, as it moves slightly past the position and tries to move back, only to move too far and repeat.

Uncommenting the look_at() line will also turn the body to point in its direction of motion if you prefer.

Suggerimento

Questa tecnica puo” anche essere utilizzata come base per un personaggio «che segue». La posizione target puo” essere quella di qualsiasi oggetto si desideri raggiungere.

Summary

Troverai questi esempi di codice utili come punto di partenza per i tuoi progetti personali. Sentiti libero di utilizzarli e sperimentare con questi per scoprire cosa puoi fare.

Puoi scaricare questi progetti di esempio qui: 2D_movement_demo.zip