物理介绍
In game development, you often need to know when two objects in the game intersect or come into contact. This is known as collision detection. When a collision is detected, you typically want something to happen. This is known as collision response.
Godot在2D和3D中提供了许多碰撞对象,以提供碰撞检测和响应。 试图决定哪一个用于您的项目可能会令人困惑。 如果您了解每种方法的工作原理以及它们的优缺点,您可以避免出现问题并简化开发。
在本指南中,您将学会:
- Godot的四种碰撞对象类型
- 每个碰撞对象的工作原理
- 何时以及为何选择这种类型而不是另一种类型
注解
本文档的示例将使用2D对象。 每个2D物理对象和碰撞形状在3D中具有直接等价物,并且在大多数情况下它们以相同的方式工作。
Collision objects
Godot提供了四种扩展自 CollisionObject2D 的物体:
-
Area2D
nodes provide detection and influence. They can detect when objects overlap and can emit signals when bodies enter or exit. AnArea2D
can also be used to override physics properties, such as gravity or damping, in a defined area.
其他三个物体扩展自 PhysicsBody2D :
-
静态主体是物理引擎不移动的主体。 它参与碰撞检测,但不会响应碰撞而移动。 它们通常用于属于环境的对象或不需要任何动态行为的对象。
-
这是实现模拟2D物理的节点。 您不直接控制
RigidBody2D
,而是您对它施加力(重力,冲动等),物理引擎计算得到的运动。 阅读更多关于使用刚体的信息。 -
提供碰撞检测的物体,但没有物理特性。 所有移动和碰撞响应必须在代码中实现。
碰撞形状
物理体可以包含任意数量的 Shape2D 对象作为子对象。 这些形状用于定义对象的碰撞边界并检测与其他对象的接触。
注解
为了检测碰撞,必须至少为对象分配一个 Shape2D
。
分配形状的最常用方法是添加 CollisionShape2D 或 CollisionPolygon2D 作为对象的子项。 这些节点允许您直接在编辑器工作区中绘制形状。
重要
Be careful to never scale your collision shapes in the editor. The “Scale” property in the Inspector should remain (1, 1)
. When changing the size of the collision shape, you should always use the size handles, not the Node2D
scale handles. Scaling a shape can result in unexpected collision behavior.
物理过程回调
物理引擎可能会产生多个线程以提高性能,所以它能使用最多一个帧来处理物理。 因此,物体状态的变量的值,如 position
或 linear velocity
,可能在当前帧不完全准确。
In order to avoid this inaccuracy, any code that needs to access a body’s properties should be run in the Node._physics_process() callback, which is called before each physics step at a constant frame rate (60 times per second by default). This method will be passed a delta
parameter, which is a floating-point number equal to the time passed in seconds since the last step. When using the default 60 Hz physics update rate, it will typically be equal to 0.01666...
(but not always, see below).
注解
It’s recommended to always use the delta
parameter when relevant in your physics calculations, so that the game behaves correctly if you change the physics update rate or if the player’s device can’t keep up.
Collision layers and masks
碰撞层系统是最强大但经常被误解的碰撞特征之一。 该系统允许您在各种对象之间建立复杂的交互。 关键概念是 层(layers) 和 遮罩(masks) 。 每个 CollisionObject2D
都有20个不同的物理层可以相互作用。
让我们依次看看每个属性:
collision_layer
这描述了对象在 中出现的图层 。 默认情况下,所有实体都在图层``1``上。
collision_mask
这描述了物体将 扫描 以进行碰撞的层。 如果对象不在其中一个遮罩层中,则物体将忽略它。 默认情况下,所有实体都扫描图层是
1
。
可以通过代码配置这些属性,也可以在Inspector中对其进行编辑。
跟踪您正在使用每个图层的内容可能很困难,因此您可能会发现为您正在使用的图层指定名称很有用。 可以在 项目设置
-> 图层名称
中指定名称。
GUI example
游戏中有四种节点类型:Walls,Player,Enemy和Coin。 游戏角色和敌人都应该与沃尔斯碰撞。 游戏角色节点应该检测与敌人和硬币的碰撞,但敌人和硬币应该互相忽略。
首先命名 1-4 层 墙
, 游戏角色
, 敌人
和 硬币
,然后使用 图层
属性将每个节点类型放在其各自的图层中。 然后通过选择应与之交互的图层来设置每个节点的 Mask
属性。 例如,游戏角色的设置如下所示:
Code example
In function calls, layers are specified as a bitmask. Where a function enables all layers by default, the layer mask will be given as 0x7fffffff
. Your code can use binary, hexadecimal, or decimal notation for layer masks, depending on your preference.
The code equivalent of the above example where layers 1, 3 and 4 were enabled would be as follows:
# Example: Setting mask value for enabling layers 1, 3 and 4
# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 20 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore
0b00000000000000001101
# (This can be shortened to 0b1101)
# Hexadecimal equivalent (1101 binary converted to hexadecimal)
0x000d
# (This value can be shortened to 0xd)
# Decimal - Add the results of 2 to the power of (layer be enabled-1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
pow(2, 1) + pow(2, 3) + pow(2, 4)
Area2D
Area nodes provide detection and influence. They can detect when objects overlap and emit signals when bodies enter or exit. Areas can also be used to override physics properties, such as gravity or damping, in a defined area.
有三个主要用途 Area2D:
- Overriding physics parameters (such as gravity) in a given region.
- 检测其他实体何时进入或退出某个区域或当前哪个实体位于某个区域。
- 检查其他区域是否重叠。
默认情况下,区域还会接收鼠标和触摸屏输入。
StaticBody2D
静态主体是物理引擎不移动的主体。 它参与碰撞检测,但不会响应碰撞而移动。 然而,它可以使用它的 constant_linear_velocity
和 constant_angular_velocity
属性将运动或旋转传递给碰撞体,好像 它正在移动一样。
StaticBody2D
节点最常用于属于环境的对象或不需要任何动态行为的对象。
StaticBody2D
的示例用法:
- 平台(包括移动平台)
- 输送带
- 墙壁和其他障碍
RigidBody2D
This is the node that implements simulated 2D physics. You do not control a RigidBody2D directly. Instead, you apply forces to it and the physics engine calculates the resulting movement, including collisions with other bodies, and collision responses, such as bouncing, rotating, etc.
您可以通过 质量
,摩擦力
或 弹跳
等属性修改刚体的行为,这些属性可以在属性面板中设置。
物体的行为也受到 项目设置 ->物理学
中设置的世界属性的影响,或者通过输入覆盖全局物理属性的 Area2D 。
When a rigid body is at rest and hasn’t moved for a while, it goes to sleep. A sleeping body acts like a static body, and its forces are not calculated by the physics engine. The body will wake up when forces are applied, either by a collision or via code.
刚体模式
刚体可以设置为以下四种模式之一:
- Rigid - 物体表现为具有物理属性的对象。 它与其他物体发生碰撞,并对施加于其上的力作出反应。 这是默认模式。
- Static - 物体表现得像 StaticBody2D 并且不会移动。
- Character - Similar to “Rigid” mode, but the body cannot rotate.
- Kinematic - 物体的行为类似于 KinematicBody2D 并且必须通过代码移动。
使用RigidBody2D
使用刚体的一个好处是,可以 免费
获得许多行为而无需编写任何代码。 例如,如果您正在制作一个带有下降块的 愤怒的小鸟
式游戏,您只需要创建RigidBody2D并调整它们的属性。 堆叠,下降和弹跳将由物理引擎自动计算。
However, if you do wish to have some control over the body, you should take care - altering the position
, linear_velocity
, or other physics properties of a rigid body can result in unexpected behavior. If you need to alter any of the physics-related properties, you should use the _integrate_forces() callback instead of _physics_process()
. In this callback, you have access to the body’s Physics2DDirectBodyState, which allows for safely changing properties and synchronizing them with the physics engine.
例如,以下是``小行星``式宇宙飞船的代码:
GDScript
C#
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_velocity
或 angular_velocity
属性,而是将力( thrust
和 torque
)施加到物体上并让物理引擎计算出最终的运动。
注解
When a rigid body goes to sleep, the _integrate_forces()
function will not be called. To override this behavior, you will need to keep the body awake by creating a collision, applying a force to it, or by disabling the can_sleep property. Be aware that this can have a negative effect on performance.
接触报告
By default, rigid bodies do not keep track of contacts, because this can require a huge amount of memory if many bodies are in the scene. To enable contact reporting, set the contacts_reported property to a non-zero value. The contacts can then be obtained via Physics2DDirectBodyState.get_contact_count() and related functions.
Contact monitoring via signals can be enabled via the contact_monitor property. See RigidBody2D for the list of available signals.
KinematicBody2D
KinematicBody2D 物体检测与其他物体的碰撞,但不受重力或摩擦等物理属性的影响。 相反,它们必须由用户通过代码控制。 物理引擎不会移动运动体。
移动运动体时,不应直接设置其 position
。 相反,您使用 move_and_collide()
或 move_and_slide()
方法。 这些方法沿着给定的向量移动物体,如果与另一个物体检测到碰撞,它将立即停止。 在物体发生碰撞后,必须手动编码任何碰撞响应。
运动碰撞响应
碰撞后,您可能希望物体反弹,沿着墙壁滑动,或者改变它所击中的物体的属性。 处理碰撞响应的方式取决于您用于移动KinematicBody2D的方法。
move_and_collide
当使用 move_and_collide()
时,该函数返回一个 KinematicCollision2D 对象,其中包含有关碰撞和碰撞体的信息。 您可以使用此信息来确定响应。
例如,如果要查找发生碰撞的空间点:
GDScript
C#
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();
}
}
}
或者从碰撞物体反弹:
GDScript
C#
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()
在计算中自动包含时间步长,因此您 不 应将速度向量乘以 delta
。
例如,使用以下代码制作一个可以沿着地面(包括斜坡)行走的角色,并在站在地面时跳跃:
GDScript
C#
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;
GetInput();
_velocity = MoveAndSlide(velocity, new Vector2(0,-1));
}
}
有关使用 move_and_slide()
的更多详细信息,请参阅 动力学角色(二维) ,包括带有详细代码的演示项目。