游戏开发中的物理介绍
🌊 作者主页:海拥
🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十
🌊 粉丝福利:粉丝群 每周送六本书,不定期送各种小礼品,往期获奖公布
在游戏开发中,您通常需要知道游戏中的两个对象何时相交或接触。这就是所谓的碰撞检测。当检测到碰撞时,您通常希望发生某些事情。这就是所谓的碰撞响应。
Godot在2D和3D中提供了许多碰撞对象,以提供碰撞检测和响应。试图确定要为您的项目使用哪个选项可能会造成混淆。如果您了解每个工作原理以及各自的优缺点,则可以避免问题并简化开发。
在本指南中,您将学习:
- 戈多的四种碰撞对象类型
- 每个碰撞对象如何工作
- 什么时候以及为什么要选择一种而不是另一种
==注意==
本文档的示例将使用2D对象。每个2D物理对象和碰撞形状在3D中具有直接等效的功能,并且在大多数情况下,它们的工作方式几乎相同。
碰撞对象
Godot提供了四种物理体,扩展了CollisionObject2D:
- Area2D
Area2D节点提供检测和影响。它们可以检测物体何时重叠,并可以在物体进入或离开时发出信号。Area2D 还可以使用an来覆盖定义区域中的物理特性,例如重力或阻尼。
其他三个主体扩展了PhysicsBody2D:
- StaticBody2D
静态物体是物理引擎不会移动的物体。它参与碰撞检测,但不会响应碰撞而移动。它们最常用于环境中的对象或不需要任何动态行为的对象。
- RigidBody2D
这是实现模拟2D物理的节点。您无需RigidBody2D直接控制a ,而是要对其施加力(重力,脉冲等),然后物理引擎将计算最终的运动。阅读更多有关使用刚体的信息。
- KinematicBody2D
提供碰撞检测但没有物理学的物体。所有运动和碰撞响应都必须用代码实现。
碰撞形状
物理物体可以将任意数量的Shape2D对象作为子对象。这些形状用于定义对象的碰撞范围并检测与其他对象的接触。
==注意==
为了检测碰撞,Shape2D必须至少分配一个对象。
分配形状的最常见方法是添加CollisionShape2D 或CollisionPolygon2D作为对象的子级。这些节点允许您直接在编辑器工作区中绘制形状。
重要
注意不要在编辑器中缩放碰撞形状。 检查器中的“比例”属性应保留为(1,1)。
更改碰撞形状的大小时,应始终使用大小控制柄,而不是Node2D比例控制柄。 缩放形状会导致意外的碰撞行为。
物理过程回调
物理引擎可以产生多个线程以提高性能,因此它最多可以使用一个完整的帧来处理物理。 因此,对于当前帧,身体的状态变量(例如位置或线速度)的值可能不准确。
为了避免这种不准确性,任何需要访问人体属性的代码都应在Node._physics_process() 回调中运行,该回调在每个物理步骤之前以恒定帧速率(默认为每秒60次)被调用。该方法将被传递一个delta 参数,该参数是一个浮点数,它等于自上一步以来经过的时间(以 秒为单位)。当使用默认的60 Hz物理更新速率时,通常等于0.01666…(但不总是如此,请参见下文)。
==注意==
建议始终delta在物理计算中使用相关参数,以便在您更改物理更新率或玩家的设备无法跟上时,游戏能够正确运行。
碰撞层和蒙版
碰撞层系统是最强大但经常被误解的碰撞特征之一。该系统使您可以在各种对象之间建立复杂的交互。关键概念是图层 和蒙版。每个CollisionObject2D都有可与之交互的20个不同的物理层。
让我们依次查看每个属性:
- 碰撞层
这描述了对象出现在的层。默认情况下,所有实体都在layer上1。
- 碰撞面罩
这描述了身体将扫描碰撞的层。如果对象不在遮罩层之一中,则主体将忽略它。默认情况下,所有实体都扫描layer 1。
这些属性可以通过代码或在检查器中编辑来配置。
跟踪每个图层的用途可能很困难,因此您可能会发现为使用的图层分配名称很有用。可以在项目设置->图层名称中分配名称。
GUI示例
游戏中有四种节点类型:墙,玩家,敌人和硬币。玩家和敌人都应与墙碰撞。播放器节点应同时检测到与敌人和硬币的碰撞,但敌人和硬币应互相忽略。
首先命名第1-4层“墙”,“玩家”,“敌人”和“硬币”,然后使用“层”属性将每个节点类型放置在其相应的层中。然后通过选择每个节点应与之交互的层来设置每个节点的“蒙版”属性。例如,播放器的设置如下所示:
代码示例
在函数调用中,将图层指定为位掩码。如果功能默认启用所有图层,则图层蒙版将指定为0x7fffffff。您的代码可以对图层蒙版使用二进制,十六进制或十进制表示法,具体取决于您的偏好。
上面启用了第1、3和4层的示例的等效代码如下:
# 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
区域节点提供检测和影响。它们可以检测物体何时重叠并在物体进入或离开时发出信号。区域还可以用于覆盖定义区域中的物理属性,例如重力或阻尼。
Area2D有三个主要用途:
- 给定区域中的替代物理参数(例如重力)。
- 检测其他物体何时进入或离开区域或当前区域中有哪些物体。
- 检查其他区域是否重叠。
默认情况下,区域还接收鼠标和触摸屏输入。
StaticBody2D
静态物体是物理引擎不会移动的物体。它参与碰撞检测,但不会响应碰撞而移动。但是,它可以利用其和属性为碰撞的物体提供运动或旋转,就好像它在运动一样。constant_linear_velocityconstant_angular_velocity
StaticBody2D 节点最常用于环境中的对象或不需要任何动态行为的对象。
示例用于StaticBody2D:
- 平台(包括移动平台)
- 输送带
- 墙壁和其他障碍
RigidBody2D
这是实现模拟2D物理的节点。您不能直接控制 RigidBody2D。取而代之的是,您对其施加力,然后物理引擎会计算出最终的运动,包括与其他物体的碰撞以及碰撞响应(如弹跳,旋转等)。
您可以通过“质量”,“摩擦”或“弹跳”之类的属性来修改刚体的行为,这些属性可以在检查器中设置。
人体的行为也会受到世界属性(如在“ 项目设置”->“物理”中设置的)的影响,或者受输入 覆盖全球物理属性的Area2D的影响。
当刚体处于静止状态并且一段时间未移动时,它将进入睡眠状态。睡眠物体的作用类似于静态物体,其力不是由物理引擎计算的。当通过碰撞或通过代码施加力时,身体将醒来。
刚体模式
刚体可以设置为以下四种模式之一:
- 刚性-身体表现为物理对象。它会与其他物体碰撞,并对其施加的力作出反应。这是默认模式。
- 静态-主体的行为类似于StaticBody2D,并且不会移动。
- 角色-与“刚性”模式相似,但身体无法旋转。
- 运动-身体的行为类似于KinematicBody2D,必须通过代码移动。
使用RigidBody2D
使用刚体的好处之一是无需编写任何代码即可“免费”获得许多行为。例如,如果您要制作带有下降块的“愤怒的小鸟”式游戏,则只需创建RigidBody2Ds并调整其属性。堆积,下落和弹跳将由物理引擎自动计算。
但是,如果你想有过身体有一定的控制,你应该照顾-改变position,linear_velocity,刚体的或其他物理性质可能会导致意外的行为。如果您需要更改任何与物理学相关的属性,则应使用_integrate_forces() 回调而不是_physics_process()。在此回调中,您可以访问人体的Physics2DDirectBodyState,它可以安全地更改属性并将其与物理引擎同步。
例如,以下是“小行星”式太空飞船的代码:
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)施加到物体上,然后让物理引擎计算出所产生的运动。
注意
当刚体进入睡眠状态时,_integrate_forces()
将不会调用该功能。要覆盖此行为,您将需要通过创建碰撞,向其施加力或禁用can_sleep
属性来使身体保持清醒状态。请注意,这可能会对性能产生负面影响。
联系人报告
默认情况下,刚体不跟踪接触,因为如果场景中有很多刚体,这可能需要大量的内存。若要启用联系人报告,请将contacts_reported 属性设置为非零值。然后可以通过Physics2DDirectBodyState.get_contact_count()和相关函数来获取联系人 。
可以通过contact_monitor 属性启用通过信号的接触监视。有关可用信号的列表,请参见RigidBody2D。
KinematicBody2D
KinematicBody2D实体可以检测与其他实体的碰撞,但不受重力或摩擦等物理属性的影响。相反,它们必须由用户通过代码控制。物理引擎不会移动运动体。
移动运动机构时,请勿position直接设置它。而是使用move_and_collide()ormove_and_slide()方法。这些方法沿着给定的矢量移动物体,如果检测到与另一个物体的碰撞,它将立即停止。身体碰撞后,任何碰撞响应都必须手动编码。
运动碰撞响应
发生碰撞后,您可能希望身体反弹,沿墙滑动或改变其撞击的对象的属性。处理碰撞响应的方式取决于您用来移动KinematicBody2D的方法。
move_and_collide
使用时move_and_collide(),该函数返回 KinematicCollision2D对象,该对象包含有关碰撞和碰撞体的信息。您可以使用此信息来确定响应。
例如,如果要查找空间中发生碰撞的点:
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();
}
}
}
或从碰撞对象反弹:
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。
例如,使用以下代码制作一个可以在地面上行走(包括斜坡)并在站立在地面上时可以跳跃的角色:
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));
}
}
有关使用的更多详细信息,请参见运动字符(2D)move_and_slide(),包括带有详细代码的演示项目。
- 点赞
- 收藏
- 关注作者
评论(0)