更新于 

物理系统

2D物理系统

2D物理系统

2D物理系统官方文档(2.4版本)

2.4版本cocos中需要手动开启物理引擎(需要在onLoad方法内执行):

1
cc.director.getPhysicsManager().enabled = true

想要开启一个节点的物理系统,需要给这个节点添加两个组件:

  • RigidBody 刚体组件
  • Collider 碰撞组件

RigidBody 刚体

RigidBody
  • Enabled Contact Listener
    • 开启物理碰撞检测
  • Bullet
    • 防止高速移动的物体穿过墙面或地面等刚体
  • Type
    • 几种刚体类型
      • static 静态刚体
      • kinematic 不受外力
      • Dynamic 动态刚体
      • Animated 设置线速度和角速度驱动的刚体
  • Allow Sleep
    • 允许刚体静止不动时进入睡眠状态,降低CPU占用率
  • Gravity Scale
    • 重力倍数,1为1倍重力
  • Linear Damping
    • 线性速度衰减系数
  • Angular Damping
    • 角速度衰减系数
  • Linear Velocity
    • 线性初速度
  • Angular Velocity
    • 角初速度
  • Fixed Rotation
    • 禁止物体旋转
  • Awake OnLoad
    • 是否在初始化时唤醒此刚体

将AwakeOnLoad设置为false,
在代码中可以通过修改刚体组件的awake为true,来开启刚体:

1
this.getComponent(cc.RigidBody).awake = true

刚体方法文档

  • 给刚体施加力

    • 参数1:force 在x、y轴上的大小分量
    • 参数2:point 作用点
    • 参数3:wake 是否唤醒刚体
      1
      rb.applyForce(force:cc.Vec2, point:cc.Vec2, wake:boolean)
  • 施加力到刚体的质心上

    • 参数1:force 在x、y轴上的大小分量
    • 参数2:wake 是否唤醒刚体
      1
      rb.applyForceToCenter(force:cc.Vec2, wake:boolean)
  • 施加扭力

    • 参数1:torque 扭力大小
    • 参数2:wake 是否唤醒
      1
      rb.applyTorque(torque:number, wake:boolean)
  • 施加冲量

    • 参数1:impulse 冲量在x、y轴上的大小分量
    • 参数2:point 作用点
    • 参数3:wake 是否唤醒
      1
      rb.applyLinearImpulse(impulse:cc.Vec2, point:cc.Vec2, wake:boolean)
  • 施加角速度冲量

    • 参数1:impulse 角速度冲量大小
    • 参数2:wake 是否唤醒
      1
      rb.applyAngularImpulse(impulse:number, wake:boolean)
力与冲量

cocos中主要有两种方式移动一个物体:

  • 力 Force
    • 物体本身移动的内力
    • 力会随时间修改物体的坐标和旋转角度
    • 添加物体内在力的API:
      • applyForce
      • applyForceToCenter
      • applyTorque
  • 冲量 Impulse
    • 从外界推动物体的外力
    • 冲量会立刻改变物体的坐标或旋转角度
    • 冲量(如线性初速度、角初速度),会根据衰减系数而逐渐减弱,直到物体的变化完全停止
    • 添加物体外力的API:
      • applyLinearImpulse
      • applyAngularImpulse

Collider 物理碰撞体

碰撞组件

RigidBody必须在组件上存在Collider碰撞体的情况下才能生效,

Collider碰撞组件具有以下的基础属性:

  • Density 密度
  • Sensor 是否是传感器类型
    • 设置了传感器的碰撞物本身不会产生碰撞效果,但是可以监听到碰撞事件
    • 常用场景:某个特殊点监听到玩家的碰撞之后,触发事件(场景跳转/交互效果)
  • Friction 摩擦系数
  • Restitution 弹性系数
  • Tag
    • 和collider属性一样,tag用于在碰撞回调中识别碰撞体的类型
碰撞回调

碰撞回调官方文档

想要触发碰撞体的碰撞回调,
需要开启碰撞体RigidBody的Enabled Contact Listener属性,

碰撞回调函数中有3个参数:

  • contact 碰撞信息
  • self 碰撞物自己
  • other 另一个碰撞物
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 开始碰撞
onBeginContact(contact:cc.PhysicsContact, self:cc.PhysicsCollider, other:cc.PhysicsCollider){
}

// 结束碰撞
onEndContact(contact:cc.PhysicsContact, self:cc.PhysicsCollider, other:cc.PhysicsCollider){
}

// 每次处理碰撞体接触逻辑前调用
onPreSolve(contact:cc.PhysicsContact, self:cc.PhysicsCollider, other:cc.PhysicsCollider){
}

// 每次处理完成碰撞体接触逻辑后调用
onPostSolve(contact:cc.PhysicsContact, self:cc.PhysicsCollider, other:cc.PhysicsCollider){
}

关于cc.PhysicsContact类型,可以获取到以下碰撞信息:

  • colliderA 碰撞体A
  • colliderB 碰撞体B
  • disabled 禁用
  • disabledOnce 在当前时间步(dt)中忽略此接触信息
  • getWorldMainfold() 获取世界坐标系下的碰撞信息
    • 获取到的碰撞信息具有以下成员:
      • points 碰撞点数组,常见有1-2个碰撞点
      • normal 碰撞点上的法向量,由自身碰撞体指向对方碰撞体
  • getMainfold() 获取局部坐标系下的碰撞信息
  • getImpulse() 获取冲量信息
    • 在onPostSolve回调中可以获取到,返回值包含2个参数:
      • normalImpulses 向量冲量
      • tangentImpulse 切线冲量
  • getFriction/setFriction/resetFriction() 获取/设置/重置摩擦力系数
  • getRestitution/setRestitution/resetRestitution() 获取/设置/重置弹性系数
  • isTouching() 碰撞体是否已经解除

物理检测

物理系统管理器

物理系统官方文档

cocos中有3种物理检测的方法:

  • 点检测
  • 矩形检测
  • 射线检测
点检测

检测某个指定点位是否存在碰撞体,
如果存在多个碰撞体,只会返回一个随机结果,

只能用于测试dynamic刚体

1
let pysicsCollider = cc.director.getPhysicsManager().testPoint(point)
矩形检测

矩形检测需要指定一个世界坐标系下的矩形,
如果一个碰撞体的包围盒与矩阵存在重叠部分,会返回该碰撞体

同样只能检测dynamic类型的碰撞体:

1
2
let rect = cc.rect(0, 0, 300, 300)
let colliders = cc.director.getPhysicsManager().testAABB(rect)
射线检测

射线检测根据指定的线段,来判断线段穿过的碰撞体,
返回线段在穿过碰撞体的法向量、点位以及其它一些信息

用法:

1
2
3
4
5
6

let p1 = cc.v2(0, 200)
let p2 = cc.v2(175, 300)
let type = cc.RayCastType.Closest
let results = cc.director.directorgetPhysicsManager().rayCast(p1, p2, type)

参数说明:

  • p1 线段起点
  • p2 线段终点
  • type 射线类型,在cc.RayCastType中定义,包括以下4种
    • All:检测射线路径上的所有碰撞体,检测到的结果顺序不是固定的
    • AllClosest: 返回射线路径上的所有碰撞体,但是会对返回值进行筛选,只返回每个碰撞体与射线接触的第一个点
    • Any:
      • 官方解释:检测射线路径上任意,一旦检测到任何碰撞体,立即结束检测
      • 实际使用:返回的是最远的碰撞体
    • Closest:检测射线路径上最近的碰撞体,默认值
物理检测实现自动寻路

实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@ccclass
export default class AutoFindWay extends cc.Component {
// 物理管理器
manager = null
// 刚体
rb:cc.RigidBody = null
// 方向顺序, 默认向上走
dir = [1,0]
// 速度
speed = 50000
onLoad(){
this.manager = cc.director.getPhysicsManager()
this.manager.enabled = true // 开启物理管理器
}

start () {
this.rb = this.getComponent(cc.RigidBody)
}
testRaycast(dt){
// 开始点
let startPoint = this.node.getPosition()
// 结束点
let endPoint
// 探测长度
let len = 100
if(this.dir[0] != 0){
// 沿x轴前进
let points = this.manager.rayCast(
startPoint,
cc.v2(startPoint.x + len * this.dir[0], startPoint.y),
cc.RayCastType.Closest)

// x轴移动受阻,开始检测上下方向是否可行(排除碰撞物为重点的可能性)
if(points.length>0 && points[0].collider.tag != 1){
let hasChange = false
let allDir = [-1,1]
// 随机选择先上还是先下
if(Math.random() > 0.5) allDir.reverse()
for(let i of allDir){
let points = this.manager.rayCast(
startPoint,
cc.v2(startPoint.x, startPoint.y + len * i),
cc.RayCastType.Closest)
if(points.length == 0){
this.dir = [0, i]
hasChange = true
break;
}
}
// 当前方向不可行,上下方向也不可行,此时回头
if(!hasChange){ this.dir[0] *= -1}
}
}else if(this.dir[1] != 0){
// 沿y轴前进
let points = this.manager.rayCast(
startPoint,
cc.v2(startPoint.x , startPoint.y + len * this.dir[1]),
cc.RayCastType.Closest)

// 检测到点的存在,开始检测左右方向
if(points.length>0 && points[0].collider.tag != 1){
let hasChange = false
let allDir = [-1,1]
if(Math.random() > 0.5) allDir.reverse()
for(let i of allDir){
let points = this.manager.rayCast(
startPoint,
cc.v2(startPoint.x + len * i, startPoint.y ),
cc.RayCastType.Closest)
if(points.length == 0){
this.dir = [i, 0]
hasChange = true
break;
}
}
// 未改变
if(!hasChange){ this.dir[1] *= -1}
}
}
// 添加线性速度
this.rb.linearVelocity = new cc.Vec2(
this.speed * dt * this.dir[0],
this.speed * dt * this.dir[1]
)
}

update (dt) {
this.testRaycast(dt)
}
}