跳过正文
  1. 文章/
  2. 游戏/
  3. Cocos/

6、物理系统

·5018 字·11 分钟· loading · loading · ·
游戏 Cocos
GradyYoung
作者
GradyYoung
Cocos - 点击查看当前系列文章
§ 6、物理系统 「 当前文章 」

Cocos Creator 的物理系统提供了高效的组件化工作流程和便捷的使用方法。目前支持刚体、碰撞组件、触发和碰撞事件、物理材质、射线检测等等特性。

物理引擎
#

物理引擎主要包含以下四种:

  • builtin
    • builtin 内置物理引擎 仅有碰撞检测 的功能。相对于其它的物理引擎,它没有复杂的物理模拟计算(比如反弹、惯性等),如果您的项目不需要这一部分的物理模拟,那么建议使用 builtin,使游戏的包体更小。
  • cannon
    • 是一个开源的物理引擎,使用 JavaScript 开发并实现了比较全面的物理模拟功能。cannon.js 模块大小约为 141KB
    • 当选择的物理引擎为 cannon.js 时,需要在节点上添加 刚体组件 才能进行物理模拟。然后再根据需求添加 碰撞组件,该节点就会增加相应的碰撞体,用于检测是否与其它碰撞体产生碰撞。
  • bulletammo.js
    • ammo.js 模块较大(约 1.5MB),但它具有完善的物理功能,以及更佳的性能,未来我们也将在此投入更多工作。
  • PhysX
    • 是由英伟达公司开发的开源实时商业物理引擎,它具有完善的功能特性和极高的稳定性,同时也兼具极佳的性能表现。
    • 但由于 PhysX 目前的包体过于庞大(约 5MB)以及自身的一些限制,导致部分平台无法得到良好支持,包括:
      • 各类有包体限制的小游戏平台
      • 安卓 x86 设备

可以在(项目 - 项目设置 - 功能裁剪)中设置项目使用的物理引擎,若不需要用到任何物理相关的组件和接口,可以取消物理系统选项的勾选,使游戏的包体更小。

主要针对各类小游戏平台和原生平台,并对使用 BulletPhysX 物理时的性能进行了对比:

  • 在原生和抖音小游戏平台上,使用 PhysX 物理可以得到更加良好的性能。
  • 在各类小游戏平台上,使用 Bullet 物理可以得到更加良好的性能。

image-20240902112214771

物理系统配置
#

物理系统模块(PhysicsSystem)用于管理整个物理系统,负责同步物理元素、触发物理事件和调度物理世界的迭代。

有两种办法可以配置物理系统,一种是在编辑器中配置,另一种是通过代码配置。

通过物理配置面板
#

通过(项目设置 - 物理配置)对物理系统进行相关配置。

image-20240902112609611

属性 说明
Gravity X 重力矢量,设置 x 分量上的重力值
Gravity Y 重力矢量,设置 y 分量上的重力值
Gravity Z 重力矢量,设置 z 分量上的重力值
AllowSleep 是否允许系统进入休眠状态,默认值 true
SleepThreshold 进入休眠的默认速度临界值,默认值 0.1,最小值 0
AutoSimulation 是否开启自动模拟, 默认值 true
FixedTimeStep 每步模拟消耗的固定时间,默认值 1/60,最小值 0
MaxSubSteps 每步模拟的最大子步数,默认值 1,最小值 0
Friction 摩擦系数,默认值 0.5
RollingFriction 滚动摩擦系数,默认值 0.1
SpinningFriction 自旋摩擦系数,默认值 0.1
Restitution 弹性系数,默认值 0.1
CollisionMatrix 碰撞矩阵,仅用于初始化

程序化配置
#

程序化配置目前可以通过直接访问 PhysicsSystem.instance 对物理系统进行配置。部分代码示例如下:

import { _decorator, Component, Node, Vec3, PhysicsSystem } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('Example')
export class Example extends Component {
    start () {
        PhysicsSystem.instance.enable = true;
        PhysicsSystem.instance.gravity = new Vec3(0, -10, 0);
        PhysicsSystem.instance.allowSleep = false;
    }
}

碰撞矩阵
#

碰撞矩阵是分组和掩码功能的进一步封装,它用于初始化物理元素的分组和掩码。

image-20240902114017977

碰撞矩阵默认情况下只有一个 DEFAULT 分组,新建分组默认不与其它组碰撞。

点击 + 按钮可以新增分组。新增分组的 IndexName 均为必填。

  • Index 代表的是碰撞分组值, 最高支持 32 位,即数值范围为 [0, 31)。分组值不可重复。
  • Name 代表的是碰撞分组名。此处在这里设置的名字只是为了用户进行碰撞分组配置方便,无法通过代码获取,代码能获取到的只有分组值。

如上图就是飞机大战的碰撞矩阵

index 1的意思就是:self_plane:玩家飞机可以与enemy_planeenemy_bullet发生碰撞

配置完成碰撞矩阵之后,就可以对需要产生碰撞的对象添加 刚体(RigidBody) 组件,设置碰撞分组 Group

image-20240902114657145

通常,在游戏开发中,需要在碰撞发生前设置好可碰撞分组,在碰撞发生时处理相关的逻辑。在 Cocos Creator 中,所有的碰撞数据获取到的是数值,这样不利于开发过程中的判断。因此,可以通过定义分组对象或者枚举的形式,清晰的知道每一串数字的意义。

// 常量
export class Constant {

    public static CollisionType = {
        self_plane: 1 << 1,
        enemy_plane: 1 << 2,
        self_bullet: 1 << 3,
        enemy_bullet: 1 << 4
    }
   
}

掩码
#

可以理解为,可以与当前组件发生碰撞的组件实际二进制值,根据上图的配置,Cocos Creator 会将数据解析为以下值:

  • DEFAULTIndex 值为 0,分组实际值为 1<<0=1,二进制值为 0000 0001;掩码值实际值为 1<<0=1,二进制值为 0000 0001
  • self_planeIndex 值为 1,分组实际值为 1<<1=2,二进制为 0000 0010;掩码值实际值为 (1<<3)+(1<<4)=24,二进制值为 0001 1000
  • enemy_bulletIndex 值为 4,分组实际值为 1<<4=16,二进制为 0001 0000;掩码值实际值为 1<<1=2,二进制值为 0000 0010

通过程序设置
#

// 获取分组
const rigid = this.node.getComponent(RigidBody);
// 设置掩码,等价于 rigid.setGroup(1 << 1) 或 rigid.setGroup(1)
rigid.setGroup(Constant.CollisionType.self_plane);
// 获取分组,二进制值
const group = rigid.getGroup();



// 如果当前分组并未在碰撞矩阵中定义,也可以动态添加
const group = 1 << 7;
const rigid = this.getComponent(RigidBody);
rigid.addGroup(group);
rigid.removeGroup(group);

// 设置和获取掩码
const rigid = this.getComponent(RigidBody);
const mask = (1 << 0) + (1 << 1); // 等价于 1 << 0 | 1 << 1
rigid.setMask(mask);
rigid.getMask();

物理组件
#

碰撞组件
#

碰撞组件可用于定义需要进行物理碰撞的物体形状,不同的几何形状拥有不同的属性。碰撞体通常分为以下几种:

  1. 基础碰撞体。常见的包含 盒(BoxCollider)、球(SphereCollider)、圆柱(CylinderCollider)、圆锥(ConeCollider)、胶囊(CapsuleCollider) 碰撞体。
  2. 复合碰撞体。可以通过在一个节点身上添加一个或多个基础碰撞体,简易模拟游戏对象形状,同时保持较低的性能开销。
  3. 网格碰撞体(MeshCollider)。根据物体网格信息生成碰撞体,完全的贴合网格。
  4. 单纯形碰撞体(SimplexCollider)。提供点、线、三角面、四面体碰撞。
  5. 平面碰撞体(PlaneCollider)。可以代表无限平面或半空间。这个形状只能用于静态的、非移动的物体。
  6. 地形碰撞体(TerrainCollider)。一种用于凹地形的特殊支持。

添加碰撞体
#

例如盒碰撞器组件,新建一个 3D 对象 Cube,在 资源管理器 中点击左上角的 + 创建按钮,然后选择 创建 -> 3D 对象 -> Cube 立方体。在右侧的 属性检查器 面板下方点击 添加组件 按钮,选择 Physics -> BoxCollider 添加一个碰撞器组件。

add-boxcollider

也可以使用代码添加

import { BoxCollider } from 'cc'

const boxCollider = this.node.addComponent(BoxCollider);
属性 说明
Attached 碰撞器所绑定的刚体
Material 碰撞器所使用的物理材质,未设置时使用引擎默认的物理材质
IsTrigger 是否为触发器,触发器不会产生物理反馈

刚体组件
#

刚体是组成物理世界的基本对象,它可以使游戏对象的运动方式受物理控制。例如:刚体可以使游戏对象受重力影响做自由下落,也可以在力和扭矩的作用下,让游戏对象模拟真实世界的物理现象。

什么情况下需要添加刚体
#

  1. 配置碰撞分组并让其生效。
  2. 物体需要具备运动学或动力学行为

添加刚体
#

点击 属性检查器 下方的 添加组件 -> Physics -> RigidBody,即可添加刚体组件到节点上。

add-rigidbody-in-inspector

也可以使用代码进行添加

import { RigidBody } from 'cc'

// 添加刚体
const rigidbody = this.node.addComponent(RigidBody);

// 获取刚体
const rigidBody = this.node.getComponent(RigidBody);
属性 说明
Group 刚体分组
Type 刚体类型。 DYNAMIC:动力学 STATIC:静态 KINEMATIC:运动学

刚体具有以下属性:

  • Group:刚体分组
  • Type:刚体类型
    • STATIC:静态刚体。可以是手动设置刚体类型的游戏对象,也可以是具有碰撞体而没有刚体的游戏对象。如果一个节点默认只添加了碰撞器而没有添加刚体,那么这个节点可以认为默认使用的是 静态刚体。静态刚体在大多数情况下用于一些始终停留在一个地方,不会轻易移动的游戏物体,例如:建筑物。若物体需要持续运动,应设置为 KINEMATIC 类型。静态刚体与其他物体发生碰撞时,不会产生物理行为,因此,也不会移动。
    • DYNAMIC:动力学刚体。刚体碰撞完全由物理引擎模拟,可以通过 力的作用 运动物体(需要保证质量大于 0)。例如:斯诺克游戏击球后,母球滚动与其他球撞击;
    • KINEMATIC:运动学刚体。具有碰撞体和运动刚体,可以直接通过移动刚体对象的变换属性,但不会像动力学刚体一样响应力和碰撞,通常用于表达电梯这类平台运动的物体。它与静态刚体类似,不同的地方在于移动的运动刚体会对其他对象施加摩擦力,并在接触时唤醒其他刚体。

控制刚体
#

针对不同的类型,让刚体运动的方式不同:

  • 对于静态刚体(STATIC),应当尽可能保持物体静止,但仍然可以通过变换(位置、旋转等)来改变物体的位置。
  • 对于运动学刚体(KINEMATIC),应当通过改变变换(位置、旋转等)使其运动。
  • 对于动力学(DYNAMIC)刚体,需要改变其速度,有以下几种方式:
1、通过重力
#

刚体组件提供了 UseGravity 属性,需要使用重力时候,需将 UseGravity 属性设置为 true

2、通过施加力
#

刚体组件提供了 applyForce 接口,根据牛顿第二定律,可对刚体某点上施加力来改变物体的原有状态。

import { math } from 'cc'

rigidBody.applyForce(new math.Vec3(200, 0, 0));
3、通过施加扭矩
#

刚体组件提供了 applyTorque 接口,可用于改变刚体的角速度。

rigidBody.applyTorque(new math.Vec3(200, 0, 0));
4、通过施加冲量
#

刚体组件提供了 applyImpulse 接口,施加冲量到刚体上的一个点,根据动量定理,将立即改变刚体的线性速度。 如果冲量施加到的点在力方向上的延长线不过刚体的质心,那么将产生一个非零扭矩并影响刚体的角速度。

rigidBody.applyImpulse(new math.Vec3(5, 0, 0));
5、通过改变速度
#

刚体组件提供了 setLinearVelocity 接口,可用于改变线性速度。

rigidBody.setLinearVelocity(new math.Vec3(5, 0, 0));

刚体组件提供了 setAngularVelocity 接口,可用于改变旋转速度。

rigidBody.setAngularVelocity(new math.Vec3(5, 0, 0));
限制刚体的运动
#
1、通过休眠
#

休眠刚体时,会将刚体所有的力和速度清空,使刚体停下来。

if (rigidBody.isAwake) {
    rigidBody.sleep();
}

唤醒刚体时,刚体的力和速度将会恢复。

if (rigidBody.isSleeping) {
    rigidBody.wakeUp();
}

注意:执行部分接口,例如施加力或冲量、改变速度、分组和掩码会尝试唤醒刚体。

2、通过阻尼
#

刚体组件提供了 linearDamping 线性阻尼和 angularDamping 旋转阻尼属性,可以通过 linearDampingangularDamping 方法对其获取或设置。

阻尼参数的范围建议在 01 之间,0 意味着没有阻尼,1 意味着满阻尼。

if (rigidBody) {
    rigidBody.linearDamping = 0.5;
    let linearDamping = rigidBody.linearDamping;

    rigidBody.angularDamping = 0.5;
    let angularDamping = rigidBody.angularDamping;
}

恒力组件
#

恒力组件是一个工具组件,依赖于刚体组件,每帧都会对一个刚体施加给定的力和扭矩。

恒力组件

属性 说明
force 在世界坐标系中,对刚体施加的力
localForce 在本地坐标系中,对刚体施加的力
torque 在世界坐标系中,对刚体施加的扭转力
localTorque 在本地坐标系中,对刚体施加的扭转力

约束
#

在物理引擎中,约束 用于模拟物体间的连接情况,如连杆、绳子、弹簧或者布娃娃等。

约束依赖刚体组件,若节点无刚体组件,则添加约束时,引擎会自动添加刚体组件。

物理事件
#

触发器与碰撞器
#

img

碰撞组件属性 IsTrigger 决定了组件为触发器还是碰撞器。将 IsTrigger 设置为 true 时,组件为触发器。触发器只用于碰撞检测和触发事件,会被物理引擎忽略。默认设置 false,组件为碰撞器,可以结合刚体产生碰撞效果。

两者的区别如下:

  • 触发器不会与其它触发器或者碰撞器做更精细的检测。
  • 碰撞器与碰撞器会做更精细的检测,并会产生碰撞数据,如碰撞点、法线等。

触发事件和碰撞事件区别
#

  • 触发事件由触发器生成,碰撞事件根据碰撞数据生成。
  • 触发事件可以由触发器和另一个触发器/碰撞器产生。
  • 碰撞事件需要由两个碰撞器产生,并且至少有一个是动力学刚体。

触发事件
#

事件 说明
onTriggerEnter 触发开始时触发该事件
onTriggerStay 触发保持时会频发触发该事件
onTriggerExit 触发结束时触发该事件

接收到触发事件的前提是两者都必须带有碰撞组件,并且至少有一个是触发器类型。当使用物理引擎为非 builtin 物理引擎时,还需要确保至少有一个物体带有的是非静态刚体(只有碰撞组件没有刚体组件的对象,视为持有静态刚体的对象),而 builtin 物理引擎则没有这个限制。

监听触发事件
#

// 此处的节点添加了 BoxCollider 组件
import { BoxCollider, ITriggerEvent } from 'cc'

public start () {
    let collider = this.node.getComponent(BoxCollider);
    collider.on('onTriggerStay', this.onTriggerStay, this);
}

private onTriggerStay (event: ITriggerEvent) {
    console.log(event.type, event);
}

碰撞事件
#

碰撞事件根据碰撞数据生成,静态类型的刚体之间不会产生碰撞数据。

事件 说明
onCollisionEnter 碰撞开始时触发
onCollisionStay 碰撞保持时不断的触发
onCollisionExit 碰撞结束时触发

接收到碰撞事件的前提是两者都必须带有碰撞组件、至少有一个是非静态刚体并且使用的是非 builtin 的物理引擎。

监听碰撞事件
#

import { Collider, ICollisionEvent } from 'cc'

public start () {
    let collider = this.node.getComponent(Collider);
    // 监听触发事件
    collider.on('onCollisionStay', this.onCollision, this);
}

private onCollision (event: ICollisionEvent) {
    console.log(event.type, event);
    // 获取另一个组件的碰撞分组,为二进制
    const otherGroup = event.otherCollider.getGroup();
}

物理材质
#

物理材质是一种资源,它记录了物体的物理属性,这些信息用来计算碰撞物体受到的摩擦力和弹力等。

创建物理材质
#

在编辑器内创建
#

属性检查器 内右键任意空白处或点击 + 号间都可以创建物理材质:

创建物理材质

通过代码创建
#

也可通过代码实例化物理材质:

import { PhysicsMaterial } from 'cc';

let newPMtl = new PhysicsMaterial();
newPMtl.friction = 0.1;
newPMtl.rollingFriction = 0.1;
newPMtl.spinningFriction = 0.1;
newPMtl.restitution = 0.5;

属性
#

属性 属性说明
Friction 摩擦系数
RollingFriction 滚动摩擦系数
SpinningFriction 自旋摩擦系数
Restitution 回弹系数

使用
#

目前物理材质以碰撞体为单位进行设置,每个 Collider 都具有一个 Material 的属性(不设置时, Collider 将会引用物理系统中的默认物理材质)。

应用到 Collider 同样也分编辑器操作和代码操作两种方式。

编辑器内操作,只需要将资源拖入到 cc.PhysicMaterial 属性框中即可,如下图所示:

应用物理材质

在代码中操作:

import { Collider } from 'cc';

let collider = this.node.getComponent(Collider);
if (collider) {
    collider.material = newPMtl;
    collider.material.rollingFriction = 0.1;
}
Cocos - 点击查看当前系列文章
§ 6、物理系统 「 当前文章 」