Urho3D使用子弹库实现刚体物理模拟。
要使用,必须首先在场景中创建PhysicsWorld组件。
物理模拟有自己的固定更新速率,默认为60Hz。当渲染帧速率高于物理更新速率时,将对物理运动进行插值,使其始终看起来平滑。可以使用SetFps()函数更改更新率。物理更新速率还决定了固定时间步长场景逻辑更新的频率。可以使用SetMaxSubSteps()函数配置每帧物理步长或自适应时间步长的硬限制。这些可以帮助防止由于CPU无法处理物理负载而导致的“死亡螺旋”。然而,请注意,使用其中之一可能会导致时间减慢(当步长受限时)或物理行为不一致(当使用自适应步长时)
其他物理组件包括:
场景节点中必须同时存在RigidBody和至少一个CollisionShape组件,才能使其物理行为(碰撞形状本身不起作用)。同一节点中可能存在多个碰撞形状以创建复合形状。可以为每个节点指定相对于节点变换的偏移位置和旋转。三角形网格和凸包几何体需要指定要使用的模型资源和LOD级别。
CollisionShape提供了两个用于定义碰撞几何体的API。设置单个财产,例如形状类型或大小,或者同时指定形状类型及其所有财产:例如,请参见SetBox()、SetCapsule()或SetTriangleMesh()。
刚体可以是静态的,也可以是移动的。如果质量为0,则物体是静止的,如果质量大于0,则是运动的。请注意,移动对象不支持三角形网格碰撞形状;由于项目符号库中的限制,它不会正确碰撞。在这种情况下,可以改用凸包或GImpact三角形网格形状。
刚体的碰撞行为由几个变量控制。首先,碰撞层和遮罩定义要碰撞的其他对象:请参见SetCollisionLayer()和SetCollisionMask()。默认情况下,刚体位于层1上;该层将与另一主体的碰撞掩码进行“与”运算,以查看是否应报告碰撞。刚体也可以设置为触发模式,仅报告碰撞而不实际应用碰撞力。这可用于实现触发区域。最后,摩擦、滚动摩擦和恢复系数(0-1之间)控制了动能在碰撞中的传递。请注意,滚动摩擦默认为零,如果希望例如球体在地板上滚动最终停止,则需要在球体和地板刚体上设置非零滚动摩擦。
默认情况下,当施加力时,刚体可以围绕所有三个坐标轴移动和旋转。要限制移动,请使用SetLinearFactor()和SetAngularFactor(),并将希望使用的轴设置为1,不希望使用的设置为0。例如,移动的人形角色通常由胶囊形状表示:为了确保它们保持直立,并且只有在代码中明确设置旋转时才旋转,请将角度因子设置为0,0,0。
为了防止快速移动的刚体穿过障碍物,可以使用连续碰撞检测。它将对象近似为扫掠球体,但有性能成本,因此应仅在必要时使用。使用非零值调用SetCcdAdius()和SetCcdMotionThreshold()以启用。为了防止错误碰撞,实体的实际碰撞形状应完全包含半径。运动阈值是CCD进入每个模拟步骤所需的运动:例如,尺寸为1的盒子也应具有运动阈值1。
所有物理计算都在世界空间中进行。包含刚体组件的节点最好是场景的父节点(根节点),以确保独立运动。对于碎布玩偶,这不是绝对的,因为保持正确的骨骼层次更重要,但请注意,碎布玩偶骨骼可能会远离动画模型的根场景节点。
当同一节点中存在多个碰撞形状时,对它们的编辑可能会导致刚体中的冗余质量/惯性更新计算。为了优化这些情况下的性能,可以在调用DisableMassUpdate()和EnableMassUpdate()。
需要针对两个连接的实体定义约束位置(以及旋转,如果相关),请参见SetPosition()和SetOtherPosition()。如果约束将一个物体连接到静态世界,那么“其他物体位置”和“其他物体旋转”意味着静态末端在世界空间中的变换。还有一个辅助函数SetWorldPosition()将约束指定给世界空间位置;这将设置两个相对位置。
指定约束的运动轴而不是旋转是一种替代方法,因为它更直观,请参见SetAxis()。但是,通过显式指定旋转,可以确保约束的方向完全符合您的需要。
铰链、滑块和圆锥体扭曲约束支持定义运动限制。为了通用,这些代码被稍微无意地编码到Vector2中。对于铰链约束,下限和上限X坐标定义最小和最大角度(以度为单位)。例如-45到45。对于滑块约束,X坐标以世界空间单位定义最大线性运动,Y坐标以度定义最大角运动。圆锥体扭曲约束仅使用上限以以下方式定义最大角度(最小角度始终为-最大):X坐标是扭曲(主)轴的极限,而Y是围绕其他轴的摆动运动的极限。
物理世界在其更新步骤中发送8种类型的事件:
请注意,如果渲染帧速率很高,物理可能根本不会在每一帧上分级:在这种情况下,这些事件将不会被发送。
一个新的或正在进行的物理碰撞事件将报告碰撞的场景节点和刚体,无论其中任何一个实体是触发器,以及接触点列表。
接触点编码在字节缓冲区中,可以使用VectorBuffer或MemoryBuffer帮助类读取。每个触点重复以下结构:
从NinjaSnowWar游戏对象碰撞处理代码中读取脚本中的碰撞事件和接触点数据的示例:
void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
{
Node@ otherNode = eventData["OtherNode"].GetPtr();
RigidBody@ otherBody = eventData["OtherBody"].GetPtr();
VectorBuffer contacts = eventData["Contacts"].GetBuffer();
while (!contacts.eof)
{
Vector3 contactPosition = contacts.ReadVector3();
Vector3 contactNormal = contacts.ReadVector3();
float contactDistance = contacts.ReadFloat();
float contactImpulse = contacts.ReadFloat();
// Do something with the contact data...
}
}
提供以下有关物理世界的查询: