Перемещение в 2D пространстве

Введение

Каждый новичок задаётся вопросом: "Как я могу перемещать свой персонаж?". В зависимости от стиля игры, у тебя могут быть специальные требования, но в основном перемещения в большинстве 2D игр основаны на небольшом числе различных способов.

Для наших примеров мы будем использовать KinematicBody2D, но эти принципы так же применимы и для других типов узлов (Area2D, RigidBody2D).

Настройка

Каждый из примеров показанных ниже использует одинаковые настройки сцены. Начинайте с добавления KinematicBody2D с двумя дочерними узлами: Sprite и CollisionShape2D. Вы можете использовать иконку Godot ("icon.png") в качестве текстуры спрайта или воспользоваться любой другой 2D картинкой которая имеется.

Откройте Проект -> Настройки проекта и выберите вкладку "Список действий". Добавьте следующие действия (для справки InputEvent):

../../_images/movement_inputs.png

8-стороннее движение

В этом примере мы хотим, чтобы игрок использовал четыре клавиши для выбора движения в заданном направлении (клавиши - стрелки: вверх/влево/вниз/вправо или W/A/S/D). Термин 8 - позиционное перемещение исходит из факта, что игрок может перемещаться по диагонали удерживая одновременно две клавиши.

../../_images/movement_8way.gif

Добавьте скрипт к "KinematicBody2D", а затем добавьте следующий код:

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

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

Мы можем предотвратить это применив нормализацию (normalize) к вектору направления, таким образом длинна вектора (length) станет равной 1, и затем умножим его на желаемую скорость.

Совет

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

Примечание

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

Вращение + перемещение

Этот стиль движения иногда называют "Астероид-стиль" потому что он похож на классическую аркадную игру. Нажатие влево/вправо вращает персонаж, вверх/вниз перемещает его вперёд или назад по направления персонажа.

../../_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);
    }
}

Здесь мы добавили две новых переменных для отслеживания направления вращения и скорости вращения. Как и ранее, нажатие двух противоположных клавиш одновременно, отменяет вращение. Вращение осуществляется изменением свойства rotation.

Для задания скорости, мы используем метод``Vector2.rotated()`` чтобы скорость была направлена вдоль направления тела. rotated() полезная векторная функция, которую можно использовать во многих обстоятельствах, в противном случае пришлось бы применять тригонометрические функции.

Вращение + перемещение с помощью мыши

Этот стиль перемещения - вариация предыдущего. Сейчас направление устанавливается перемещением мыши а не клавиатурой. Благодаря функции "look at", персонаж будет всегда смотреть на курсор мыши. Перемещения вперёд/назад остаются прежними.

../../_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);
    }
}

Мы используем Node2D ``look_at()``метод для указания направления игрока. Без этой функции мы можем достичь того же эффекта используя установку угла как описано ниже:

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

Нажми и двигайся

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

../../_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);
        }
    }
}

Заметьте проверку``distance_to()``, которую мы выполняем перед перемещением. Без этого теста, тела может начать дрожать пытаясь достичь заданной позиции, так как оно может её проскакивать при перемещении и пытаться вернуться назад, снова её проскакивая.

Раскомментируя строку ``look_at()``заставит тело поворачиваться в направлении своего перемещения.

Совет

Этот приём ещё может использоваться как основа для "следящего" персонажа. Позицией цели может быть любой объект в направлении которого вы хотите перемещаться.

Подведение итогов

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

Вы можете загрузить эти примеры: 2D_movement_demo.zip