物理导论

在游戏开发中,经常需要知道游戏中的两个对象何时相交或接触。这被称为 碰撞检测 . 当检测到碰撞时,您通常希望发生一些事情。这被称为 碰撞响应 .

Godot提供了许多二维和三维的碰撞对象,以提供碰撞检测和响应。试图决定哪一个用于您的项目可能会令人困惑。如果您了解每种方法的工作原理及其优缺点,就可以避免问题并简化开发。

在本指南中,您将学习:

  • Godot的四种碰撞对象类型

  • 每个碰撞对象的工作方式

  • 何时以及为什么选择一种类型而不是另一种类型

注解

本文档的示例将使用二维对象。每一个二维物理物体和碰撞形状在三维中都有一个直接的等价物,而且在大多数情况下,它们的工作方式基本相同。

碰撞对象

Godot提供四种物理体,扩展 CollisionObject2D

  • Area2D

    Area2D 节点提供 侦查影响 . 它们可以检测对象何时重叠,并可以在实体进入或退出时发出信号。安 Area2D 也可用于覆盖定义区域中的物理属性,如重力或阻尼。

其他三个身体伸展 PhysicsBody2D

  • StaticBody2D

    静止物体是不被物理引擎移动的物体。它参与碰撞检测,但不会响应碰撞而移动。它们通常用于作为环境一部分或不需要任何动态行为的对象。

  • RigidBody2D

    这是实现模拟二维物理的节点。你不能控制 RigidBody2D 直接的,但是你要对它施力(重力,脉冲等),物理引擎计算产生的运动。 Read more about using rigid bodies.

  • KinematicBody2D

    提供碰撞检测但没有物理特性的物体。所有的移动和碰撞响应都必须在代码中实现。

碰撞形状

一个物理体可以容纳任何数量的 Shape2D 对象作为子对象。这些形状用于定义对象的碰撞边界并检测与其他对象的接触。

注解

为了检测碰撞,至少一个 Shape2D 必须分配给对象。

最常见的分配形状的方法是添加 CollisionShape2DCollisionPolygon2D 作为对象的子对象。这些节点允许您直接在编辑器工作区中绘制形状。

重要

注意不要在编辑器中缩放碰撞形状。检查员的“比例”属性应保持不变。 (1, 1) . 更改碰撞形状的大小时,应始终使用大小控制柄, not 这个 Node2D 缩放手柄。缩放形状会导致意外的碰撞行为。

../../_images/player_coll_shape1.png

物理过程回调

物理引擎可以生成多个线程来提高性能,因此它最多可以使用一个完整的帧来处理物理。因此,一个物体的状态变量的值,如 positionlinear velocity 可能对当前帧不准确。

为了避免这种不准确,任何需要访问主体属性的代码都应该在 Node._physics_process() 回调,在每个物理步骤之前以恒定的帧速率(默认为每秒60次)调用。

碰撞层和遮罩

碰撞层系统是最强大但经常被误解的碰撞特征之一。这个系统允许您在各种对象之间建立复杂的交互。关键概念是 口罩 .每个 CollisionObject2D 有20个不同的物理层可以相互作用。

让我们依次查看每个属性:

  • collision_layer

    这描述了对象出现的层 in . 默认情况下,所有实体都在图层上 1 .

  • collision_mask

    这描述了身体的层次 scan 对于碰撞。如果一个对象不在其中一个遮罩层中,主体将忽略它。默认情况下,所有实体扫描层 1 .

这些属性可以通过代码配置,也可以通过在检查器中编辑它们来配置。

跟踪每一层的用途可能很困难,因此您可能会发现将名称分配给正在使用的层很有用。可以在“项目设置”->“图层名称”中指定名称。

../../_images/physics_layer_names.png

例子:

游戏中有四种节点类型:墙、玩家、敌人和硬币。玩家和敌人都应该与墙相撞。玩家节点应该检测到与敌人和硬币的碰撞,但是敌人和硬币应该相互忽略。

首先命名层1-4“墙”、“玩家”、“敌人”和“硬币”,然后使用“层”属性将每个节点类型放置在各自的层中。然后通过选择每个节点应该交互的层来设置每个节点的“mask”属性。例如,玩家的设置如下:

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

区域二维

区域节点提供 侦查影响 . 它们可以检测物体何时重叠,并在物体进出时发出信号。区域还可用于覆盖定义区域中的物理属性,如重力或阻尼。

有三个主要用途 Area2D

  • 覆盖给定区域中的物理参数(如重力)。

  • 检测其他实体何时进入或退出某个区域或当前在某个区域中的实体。

  • 检查其他区域是否重叠。

默认情况下,区域还接收鼠标和触摸屏输入。

静体2d

静止物体是不被物理引擎移动的物体。它参与碰撞检测,但不会响应碰撞而移动。但是,它可以给碰撞的物体提供运动或旋转。 犹如 它在移动,用它 constant_linear_velocityconstant_angular_velocity 性质。

StaticBody2D 节点通常用于作为环境一部分或不需要任何动态行为的对象。

示例用途 StaticBody2D

  • 平台(包括移动平台)

  • 输送带

  • 墙壁和其他障碍物

刚性体2d

这是实现模拟二维物理的节点。你不能控制 RigidBody2D 直接。相反,你对它施力,物理引擎计算产生的运动,包括与其他物体的碰撞,以及碰撞响应,如弹跳、旋转等。

可以通过“质量”、“摩擦”或“反弹”等属性修改刚体的行为,这些属性可以在检查器中设置。

身体的行为也受世界属性的影响,如 Project Settings -> Physics 或通过输入 Area2D 这将覆盖全球物理特性。

当一个僵硬的身体处于静止状态并且有一段时间没有移动时,它就会进入睡眠状态。一个熟睡的身体就像一个静止的身体,它的力量不是由物理引擎计算出来的。当外力作用时,物体会被唤醒,不管是通过碰撞还是通过代码。

刚体模式

刚体可以设置为四种模式之一:

  • 刚性的 -身体表现为一个物理对象。它与其他物体碰撞,并对施加在它上面的力作出反应。这是默认模式。

  • 静态的 -身体的行为就像 StaticBody2D 不会移动。

  • 性格 -类似于“刚性”模式,但身体不能旋转。

  • 运动学 -身体的行为就像 KinematicBody2D 必须按代码移动。

使用刚性体2d

使用刚性体的好处之一是可以“免费”拥有很多行为,而不需要编写任何代码。例如,如果你正在制作一个“愤怒的小鸟”风格的游戏,你只需要创建一个僵化的body2ds并调整它们的属性。物理引擎将自动计算叠加、下降和反弹。

但是,如果你真的想控制身体,你应该小心-改变 positionlinear_velocity 或刚性物体的其他物理特性会导致意外行为。如果需要更改任何与物理相关的属性,则应使用 _integrate_forces() 回调而不是 _physics_process() . 在这个回调中,您可以访问主体的 Physics2DDirectBodyState 允许安全地更改属性并将其与物理引擎同步。

例如,下面是“小行星”式宇宙飞船的代码:

extends RigidBody2D

var thrust = Vector2(0, 250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        applied_force = thrust.rotated(rotation)
    else:
        applied_force = Vector2()
    var rotation_dir = 0
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_left"):
        rotation_dir -= 1
    applied_torque = rotation_dir * torque
class Spaceship : RigidBody2D
{
    private Vector2 thrust = new Vector2(0, 250);
    private float torque = 20000;

    public override void _IntegrateForces(Physics2DDirectBodyState state)
    {
        if (Input.IsActionPressed("ui_up"))
            SetAppliedForce(thrust.Rotated(Rotation));
        else
            SetAppliedForce(new Vector2());

        var rotationDir = 0;
        if (Input.IsActionPressed("ui_right"))
            rotationDir += 1;
        if (Input.IsActionPressed("ui_left"))
            rotationDir -= 1;
        SetAppliedTorque(rotationDir * torque);
    }
}

注意,我们没有设置 linear_velocityangular_velocity 直接属性,而不是施加力 (thrusttorque )对身体和让物理引擎计算产生的运动。

注解

当一个僵硬的身体进入睡眠状态时, _integrate_forces() 将不调用函数。要覆盖此行为,您需要通过创建碰撞、对其施加力或禁用 can_sleep 属性。请注意,这可能会对性能产生负面影响。

联系人报告

默认情况下,刚体不跟踪接触,因为如果场景中有许多实体,这可能需要大量的内存。要启用联系人报告,请设置 contacts_reported 属性设置为非零值。然后可通过以下方式获得触点: Physics2DDirectBodyState.get_contact_count() 以及相关功能。

可通过以下方式启用通过信号进行的接触监测: contact_monitor 属性。见 RigidBody2D 获取可用信号列表。

运动学体2d

KinematicBody2D 物体探测到与其他物体的碰撞,但不受重力或摩擦等物理性质的影响。相反,它们必须由用户通过代码来控制。物理引擎不会移动运动物体。

移动运动体时,不应设置其 position 直接。相反,您使用 move_and_collide()move_and_slide() 方法。这些方法沿着给定的向量移动物体,如果检测到与另一物体发生碰撞,它将立即停止。人体碰撞后,任何碰撞响应都必须手动编码。

运动碰撞响应

碰撞后,您可能希望主体反弹、沿墙滑动或更改其撞击的对象的属性。处理碰撞响应的方式取决于用于移动运动学体二维的方法。

move_and_collide

使用时 move_and_collide() ,函数返回 KinematicCollision2D 对象,其中包含有关碰撞和碰撞体的信息。您可以使用此信息来确定响应。

例如,如果要在空间中查找发生碰撞的点:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.position
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
        {
            var collisionPoint = collisionInfo.GetPosition();
        }
    }
}

或从碰撞物体上弹回:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.normal)
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
            velocity = velocity.Bounce(collisionInfo.Normal);
    }
}

move_and_slide

滑动是一种常见的碰撞反应;想象一个玩家在一个自上而下的游戏中沿着墙壁移动,或者在平台上跑上下斜坡。虽然可以在使用后自己编写此响应的代码 move_and_collide()move_and_slide() 提供了一种在不编写太多代码的情况下实现滑动移动的方便方法。

警告

move_and_slide() 在计算中自动包含timestep,因此应该 not 速度矢量乘以 delta .

例如,使用以下代码生成一个可以在地面(包括斜坡)上行走并在地面上跳跃的字符:

extends KinematicBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

var velocity = Vector2()

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 is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    velocity = move_and_slide(velocity, Vector2(0, -1))
class Body : KinematicBody2D
{
    private float runSpeed = 350;
    private float jumpSpeed = -1000;
    private float gravity = 2500;

    private Vector2 velocity = new Vector2();

    private void getInput()
    {
        velocity.x = 0;

        var right = Input.IsActionPressed("ui_right");
        var left = Input.IsActionPressed("ui_left");
        var jump = Input.IsActionPressed("ui_select");

        if (IsOnFloor() && jump)
            velocity.y = jumpSpeed;
        if (right)
            velocity.x += runSpeed;
        if (left)
            velocity.x -= runSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += gravity * delta;
    }
}

运动特征(2d) 有关使用的详细信息 move_and_slide() 包括一个带有详细代码的演示项目。