2D运动概述

简介

每个初学者都会说:"我如何移动我的游戏角色呢?" 根据你正在制作的游戏的风格,可能有特殊的需求,但一般来说,大多数2D游戏的运动都基于一组不太多的操作之上.

在这些示例中,我们将使用 KinematicBody2D ,但这些原则也适用于其他节点类型(Area2D,RigidBody2D).

场景布置

以下每个示例都使用相同的场景布置. 从带有 SpriteCollisionShape2D 两个子节点的 KinematicBody2D 开始. 你可以使用Godot图标("icon.png")作为Sprite的纹理或使用其他任何2D图像.

打开 Project -> Project Settings 并选择 "Input Map" 选项卡. 添加以下输入操作(相关详细信息,请参阅 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) 处理, 就可以防止这种情况 ,这意味着我们将速度向量的 长度 设置为 1 ,并乘以所期望的速度.

小技巧

如果你之前从未接触过向量数学,或者需要复习,你可以在 向量数学 看到Godot中向量用法的解释.

注解

如果在你按下键时上面的代码不起任何作用,请仔细检查你是否按照本教程的 场景布置 部分所描述的正确设置了输入操作.

旋转+移动

这种类型的运动有时被称为"Asteroids式运动",因为它类似于经典街机游戏Asteroids的工作方式. 按左/右旋转角色,而按上/下使得角色在面向的方向上向前或向后.

../../_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() 是一个很有用的向量函数,你可以在许多情况下使用它,否则就需要用到三角函数.

旋转+移动(鼠标)

这种运动方式是前一种运动方式的变体. 这次,方向由鼠标位置而不是键盘设置. 角色将始终"看向"鼠标指针. 前进/后退输入保持不变.

../../_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() 检查. 如果没有这个检查,物体在到达目标位置时会"抖动",因为它稍微移过该位置时就会试图向后移动,只是每次移动步长都会有点远从而导致来回重复移动.

如果你喜欢, 取消注释的 rotation 代码可以使物体转向其运动方向.

小技巧

该技术也可以用到"跟随"的游戏角色中. 目标 位置可以是任何你想移动到的对象的位置.

总结

你可能觉得这些代码示例可以作为你自己的项目的一个有用的出发点. 请随意使用它们并试验它们,看看你能做些什么.

可以在此处下载此示例项目: 2D_movement_demo.zip