使用运动学体2d

介绍

Godot提供了许多碰撞对象来提供碰撞检测和响应。试图决定哪一个用于您的项目可能会令人困惑。如果您了解每个问题是如何工作的,以及它们的优缺点,那么就可以避免问题并简化开发。在本教程中,我们将了解 KinematicBody2D 节点并显示一些如何使用它的示例。

注解

本文档假设您熟悉Godot的各种物理体。请阅读 物理导论 第一。

什么是运动体?

KinematicBody2D 用于实现通过代码控制的机构。它们在运动时检测到与其他物体的碰撞,但不受引擎物理特性(如重力或摩擦)的影响。这意味着您必须编写一些代码来创建它们的行为,但也意味着您可以更精确地控制它们的移动和反应。

小技巧

A KinematicBody2D 可以受重力和其他力的影响,但必须用代码计算运动。物理引擎不会移动 KinematicBody2D .

移动和碰撞

移动时 KinematicBody2D ,不应设置 position 直接属性。相反,您使用 move_and_collide()move_and_slide() 方法。这些方法沿着给定的向量移动物体,如果检测到与另一物体发生碰撞,就会立即停止。在运动学体2d发生碰撞后, 碰撞响应 必须手动编码。

警告

运动体运动只能在 _physics_process() 回调。

这两种移动方法有不同的用途,稍后在本教程中,您将看到它们如何工作的示例。

move_and_collide

此方法采用一个参数:a Vector2 指示身体的相对运动。通常,这是速度向量乘以帧时间步长 (delta )如果引擎检测到沿着这个向量的任何地方发生碰撞,物体将立即停止移动。如果发生这种情况,该方法将返回 KinematicCollision2D 对象。

KinematicCollision2D 是一个包含有关碰撞和碰撞对象的数据的对象。使用这些数据,您可以计算碰撞响应。

move_and_slide

这个 move_and_slide() 方法的目的是在希望一个实体沿着另一个实体滑动的常见情况下简化碰撞响应。例如,这在平台或自顶向下的游戏中特别有用。

小技巧

move_and_slide() 使用自动计算基于帧的移动 delta . 做 not 将速度矢量乘以 delta 在传递给 move_and_slide() .

除了速度矢量外, move_and_slide() 采用许多其他参数,允许您自定义幻灯片行为:

  • floor_normal - default value: Vector2( 0, 0 )

    此参数允许您定义引擎应将哪些表面视为地板。设置此选项可以使用 is_on_floor()is_on_wall()is_on_ceiling() 方法检测身体接触的表面类型。默认值表示所有曲面都被视为墙。

  • slope_stop_min_velocity - default value: 5

    这是站在斜坡上的最小速度。这样可以防止身体静止时从斜坡上滑下。

  • max_bounces - default value: 4

    这是物体停止移动前的最大碰撞次数。设置得太低可能会完全阻止移动。

  • floor_max_angle - 默认值: 0.785398 (以弧度表示,相当于 45 度)

    这是表面不再被视为“地板”之前的最大角度。

move_and_slide_with_snap

此方法将一些附加功能添加到 move_and_slide() 通过添加 snap 参数。只要这个向量与地面接触,物体就仍然附着在表面上。注意,这意味着您必须禁用跳跃时的捕捉,例如。您可以通过设置 snapVector2(0, 0) 或通过使用 move_and_slide() 相反。

使用哪种移动方法?

来自Godot新用户的一个常见问题是:“您如何决定使用哪种移动功能?”通常,响应是使用 move_and_slide() 因为这是“简单的”,但不一定是这样。一种方法是 move_and_slide() 属于特殊情况,并且 move_and_collide() 更一般。例如,以下两个代码段会导致相同的冲突响应:

../../_images/k2d_compare.gif
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
    velocity = velocity.slide(collision.normal)

# using move_and_slide
velocity = move_and_slide(velocity)
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
    velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);

你做什么都行 move_and_slide() 也可以用 move_and_collide() 但可能需要更多的代码。但是,正如我们在下面的示例中看到的,在 move_and_slide() 不提供所需的响应。

实例

要查看这些示例,请下载示例项目: using_kinematic2d.zip .

移动和墙壁

如果您已经下载了示例项目,则此示例位于“basicmovement.tscn”中。

对于此示例,添加 KinematicBody2D 有两个孩子:A Sprite 和A CollisionShape2D . 使用godot“icon.png”作为sprite的纹理(将其从文件系统Dock拖到 纹理 性质 Sprite )在 CollisionShape2D形状 属性,选择“New RectangleShape2d”并调整矩形大小以适合精灵图像。

注解

二维运动概述 用于实施二维移动方案的示例。

将脚本附加到运动学体2d并添加以下代码:

extends KinematicBody2D

var speed = 250
var velocity = Vector2()

func get_input():
    # Detect up/down/left/right keystate and only move when pressed.
    velocity = Vector2()
    if Input.is_action_pressed('ui_right'):
        velocity.x += 1
    if Input.is_action_pressed('ui_left'):
        velocity.x -= 1
    if Input.is_action_pressed('ui_down'):
        velocity.y += 1
    if Input.is_action_pressed('ui_up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    move_and_collide(velocity * delta)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    public int Speed = 250;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // Detect up/down/left/right keystate and only move when pressed
        _velocity = new Vector2();

        if (Input.IsActionPressed("ui_right"))
            _velocity.x += 1;

        if (Input.IsActionPressed("ui_left"))
            _velocity.x -= 1;

        if (Input.IsActionPressed("ui_down"))
            _velocity.y += 1;

        if (Input.IsActionPressed("ui_up"))
            _velocity.y -= 1;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        MoveAndCollide(velocity * delta);
    }
}

运行这个场景,你会看到 move_and_collide() 按预期工作,沿着速度矢量移动物体。现在让我们看看当你添加一些障碍时会发生什么。添加一个 StaticBody2D 具有矩形碰撞形状。对于可见性,可以使用sprite、多边形2d或从“调试”菜单中打开“可见碰撞形状”。

再次运行场景并尝试进入障碍物。你会看到 KinematicBody2D 不能穿透障碍物。但是,试着以一定的角度进入障碍物,你会发现障碍物的作用就像胶水一样——感觉就像身体被卡住了。

这是因为没有 碰撞响应 . move_and_collide() 当碰撞发生时停止身体的运动。我们需要对碰撞的响应进行编码。

尝试将函数更改为 move_and_slide(velocity) 再运行一次。注意,我们删除了 delta 从速度计算。

move_and_slide() 提供沿碰撞对象滑动实体的默认碰撞响应。这对很多类型的游戏都很有用,而且可能是你获得你想要的行为所需要的全部。

跳动/反射

如果你不想要滑动碰撞响应怎么办?在这个例子中(“bounceandcollide.tscn”在示例项目中),我们有一个角色在射击子弹,我们希望子弹从墙上反弹。

此示例使用三个场景。主场景包含播放器和墙。子弹和墙是分开的场景,因此可以实例化它们。

玩家由 ws 前进和后退键。瞄准使用鼠标指针。这是玩家的代码,使用 move_and_slide()

extends KinematicBody2D

var Bullet = preload("res://Bullet.tscn")
var speed = 200
var velocity = Vector2()

func get_input():
    # Add these actions in Project Settings -> Input Map.
    velocity = Vector2()
    if Input.is_action_pressed('backward'):
        velocity = Vector2(-speed/3, 0).rotated(rotation)
    if Input.is_action_pressed('forward'):
        velocity = Vector2(speed, 0).rotated(rotation)
    if Input.is_action_just_pressed('mouse_click'):
        shoot()

func shoot():
    # "Muzzle" is a Position2D placed at the barrel of the gun.
    var b = Bullet.instance()
    b.start($Muzzle.global_position, rotation)
    get_parent().add_child(b)

func _physics_process(delta):
    get_input()
    var dir = get_global_mouse_position() - global_position
    # Don't move if too close to the mouse pointer.
    if dir.length() > 5:
        rotation = dir.angle()
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
    public int Speed = 200;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // add these actions in Project Settings -> Input Map
        _velocity = new Vector2();
        if (Input.IsActionPressed("backward"))
        {
            _velocity = new Vector2(-speed/3, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("forward"))
        {
            _velocity = new Vector2(speed, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("mouse_click"))
        {
            Shoot();
        }
    }

    public void Shoot()
    {
        // "Muzzle" is a Position2D placed at the barrel of the gun
        var b = (Bullet)_bullet.Instance();
        b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
        GetParent().AddChild(b);
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        var dir = GetGlobalMousePosition() - GlobalPosition;
        // Don't move if too close to the mouse pointer
        if (dir.Length() > 5)
        {
            Rotation = dir.Angle();
            _velocity = MoveAndSlide(_velocity);
        }
    }
}

子弹的代码是:

extends KinematicBody2D

var speed = 750
var velocity = Vector2()

func start(pos, dir):
    rotation = dir
    position = pos
    velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        velocity = velocity.bounce(collision.normal)
        if collision.collider.has_method("hit"):
            collision.collider.hit()

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()
using Godot;
using System;

public class Bullet : KinematicBody2D
{
    public int Speed = 750;
    private Vector2 _velocity = new Vector2();

    public void Start(Vector2 pos, float dir)
    {
        Rotation = dir;
        Position = pos;
        _velocity = new Vector2(speed, 0).Rotated(Rotation);
    }

    public override void _PhysicsProcess(float delta)
    {
        var collsion = MoveAndCollide(_velocity * delta);
        if (collsion != null)
        {
            _velocity = _velocity.Bounce(collsion.Normal);
            if (collsion.Collider.HasMethod("Hit"))
            {
                collsion.Collider.Hit();
            }
        }
    }

    public void OnVisibilityNotifier2DScreenExited()
    {
        QueueFree();
    }
}

行动发生在 _physics_process() . 使用后 move_and_collide() ,如果发生碰撞, KinematicCollision2D 返回对象(否则返回 Nil

如果有返回的冲突,我们使用 normal 以反映子弹的 velocityVector2.bounce() 方法。

如果碰撞物体 (collider )有一个 hit 方法,我们也叫它。在这个示例项目中,我们为墙添加了一个闪光颜色效果来演示这一点。

../../_images/k2d_bullet_bounce.gif

平台运动

让我们尝试一个更流行的例子:2d平台。 move_and_slide() 是快速启动和运行功能字符控制器的理想选择。如果您已经下载了这个示例项目,您可以在“platformer.tscn”中找到它。

对于这个例子,我们假设您有一个由 StaticBody2D 物体。它们可以是任何形状和大小。在示例项目中,我们使用 Polygon2D 创建平台形状。

这是玩家身体的代码:

extends KinematicBody2D

export (int) var run_speed = 100
export (int) var jump_speed = -400
export (int) var gravity = 1200

var velocity = Vector2()
var jumping = false

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if jump and is_on_floor():
        jumping = true
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    get_input()
    velocity.y += gravity * delta
    if jumping and is_on_floor():
        jumping = false
    velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    [Export] public int RunSpeed = 100;
    [Export] public int JumpSpeed = -400;
    [Export] public int Gravity = 1200;

    Vector2 velocity = new Vector2();
    bool jumping = false;

    public void GetInput()
    {
        velocity.x = 0;
        bool right = Input.IsActionPressed("ui_right");
        bool left = Input.IsActionPressed("ui_left");
        bool jump = Input.IsActionPressed("ui_select");

        if (jump && IsOnFloor())
        {
            jumping = true;
            velocity.y = JumpSpeed;
        }

        if (right)
            velocity.x += RunSpeed;
        if (left)
            velocity.x -= RunSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity.y += Gravity * delta;
        if (jumping && IsOnFloor())
            jumping = false;
        velocity = MoveAndSlide(velocity, new Vector2(0, -1));
    }
}
../../_images/k2d_platform.gif

使用时 move_and_slide() ,函数返回一个向量,该向量表示发生滑动碰撞后的移动。将该值设置回角色的 velocity 使我们能够平稳地上下斜坡。尝试删除 velocity = 看看如果你不这样做会发生什么。

还要注意,我们已经添加了 Vector2(0, -1) 地板正常。这是一个直接向上的向量。这意味着,如果角色与具有此法线的对象碰撞,它将被视为地板。

使用地板正常允许我们进行跳跃工作,使用 is_on_floor() . 此函数只返回 true 后一 move_and_slide() 碰撞,碰撞体的法向在给定地板矢量的45度范围内(可通过设置进行调整 floor_max_angle

这还允许您使用 is_on_wall() 例如。