Cocos Creator 3.8.7物理系统与动态碰撞体实战
1. Cocos Creator 3.8.7 物理系统基础解析
在游戏开发中,物理系统是实现真实交互的核心模块。Cocos Creator 3.8.7版本内置了基于Box2D的2D物理引擎,这套系统通过刚体(RigidBody)和碰撞体(Collider)两大组件来构建物理世界。
刚体组件负责定义物体的物理属性,包括质量、速度、旋转等动力学特性。而碰撞体组件则定义了物体的形状和边界,用于检测与其他物体的接触。两者配合使用才能实现完整的物理效果。值得注意的是,刚体可以没有碰撞体(比如用于触发区域),但碰撞体必须依附于刚体才能参与物理模拟。
在3.8.7版本中,2D物理系统的主要组件包括:
- RigidBody2D:2D刚体组件
- BoxCollider2D:矩形碰撞体
- CircleCollider2D:圆形碰撞体
- PolygonCollider2D:多边形碰撞体
- PhysicsSystem2D:物理系统管理类
2. 动态添加碰撞体的实现方法
2.1 圆形碰撞体的创建
在运行时动态添加碰撞体是常见的需求,比如玩家拾取道具后为其添加物理属性。以下是创建圆形碰撞体的标准做法:
// 获取或创建目标节点 let targetNode = this.nowball; // 添加圆形碰撞体组件 let ballCollider = targetNode.addComponent(CircleCollider2D); // 设置碰撞体半径(单位:像素) ballCollider.radius = 45; // 应用设置(3.8.7版本必须调用) ballCollider.apply();关键参数说明:
radius:碰撞体的半径大小,应该根据节点实际尺寸设置apply():在3.8.7版本中必须显式调用才能使设置生效
注意:碰撞体默认会继承节点的缩放(scale)属性,如果节点有缩放,实际碰撞范围也会相应变化。可以通过设置
ballCollider.density来调整碰撞体的质量密度。
2.2 碰撞体类型选择策略
根据游戏需求,可以选择不同类型的碰撞体:
BoxCollider2D:适合方形物体
let boxCollider = node.addComponent(BoxCollider2D); boxCollider.size = new Size(100, 50);PolygonCollider2D:适合不规则形状
let polyCollider = node.addComponent(PolygonCollider2D); polyCollider.points = [v2(0,0), v2(100,0), v2(50,100)];
选择原则:
- 优先使用简单碰撞体(圆/方),性能更好
- 复杂形状可以用多个简单碰撞体组合
- 精确碰撞检测才使用多边形碰撞体
3. 动态添加刚体的完整方案
3.1 基础刚体创建
刚体是物理模拟的核心,以下是创建动态刚体的标准流程:
// 获取目标节点 let ballNode = this.ball; // 添加刚体组件 let rigidBody = ballNode.addComponent(RigidBody2D); // 设置刚体类型(Dynamic/Kinematic/Static) rigidBody.type = ERigidBody2DType.Dynamic; // 禁止休眠(保持活跃状态) rigidBody.allowSleep = false; // 加载时自动唤醒 rigidBody.awakeOnLoad = true; // 立即唤醒刚体 rigidBody.wakeUp();3.2 刚体参数详解
刚体的核心参数需要根据游戏需求精心配置:
type:刚体类型
Dynamic:完全受物理影响(默认)Kinematic:通过代码控制移动Static:静态不可移动物体
物理材质:
let physicsMaterial = new PhysicsMaterial2D(); physicsMaterial.friction = 0.4; physicsMaterial.restitution = 0.5; rigidBody.sharedMaterial = physicsMaterial;其他重要属性:
gravityScale:重力缩放系数linearDamping:线性阻尼(空气阻力)angularDamping:旋转阻尼
3.3 刚体与碰撞体的绑定关系
在实际开发中需要注意:
- 刚体应该先于碰撞体添加
- 一个节点只能有一个刚体
- 一个刚体可以有多个碰撞体
- 子节点的碰撞体会继承父节点的刚体
典型错误示例:
// 错误顺序:先加碰撞体再加刚体 node.addComponent(CircleCollider2D); node.addComponent(RigidBody2D); // 可能导致物理行为异常4. 实战问题排查与优化技巧
4.1 常见问题解决方案
碰撞体不生效:
- 检查是否忘记调用
apply() - 确认物理系统已启用(
PhysicsSystem2D.instance.enable = true) - 检查节点层级是否合理
- 检查是否忘记调用
刚体穿透问题:
- 增加物理迭代次数:
PhysicsSystem2D.instance.velocityIterations = 10; PhysicsSystem2D.instance.positionIterations = 10; - 适当减小时间步长(fixedTimeStep)
- 增加物理迭代次数:
性能优化建议:
- 静态物体设为
Static类型 - 不可见物体设置
rigidBody.enabledContactListener = false - 远离视口的物体可以临时禁用物理模拟
- 静态物体设为
4.2 高级技巧:碰撞分组管理
通过碰撞矩阵可以精细控制哪些物体应该发生碰撞:
// 定义碰撞分组 enum PhysicsGroup { PLAYER = 1 << 0, ENEMY = 1 << 1, ITEM = 1 << 2 } // 设置分组 rigidBody.group = PhysicsGroup.PLAYER; // 配置碰撞矩阵(哪些组之间会碰撞) PhysicsSystem2D.instance.setCollisionMatrix( PhysicsGroup.PLAYER, PhysicsGroup.ENEMY | PhysicsGroup.ITEM );4.3 调试技巧
显示碰撞体边框:
PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.Aabb | EPhysics2DDrawFlags.Pair | EPhysics2DDrawFlags.Shape;实时监控物理状态:
console.log(rigidBody.linearVelocity); // 查看速度 console.log(rigidBody.isAwake); // 是否处于唤醒状态
5. 完整示例:弹球游戏实现
下面是一个完整的弹球物理实现示例:
export class BallPhysics extends Component { onLoad() { // 初始化物理系统 PhysicsSystem2D.instance.enable = true; PhysicsSystem2D.instance.gravity = new Vec2(0, -980); // 创建球体刚体 let rigidBody = this.node.addComponent(RigidBody2D); rigidBody.type = ERigidBody2DType.Dynamic; rigidBody.linearDamping = 0.1; rigidBody.angularDamping = 0.5; // 添加圆形碰撞体 let collider = this.node.addComponent(CircleCollider2D); collider.radius = this.node.width / 2; collider.apply(); // 设置物理材质 let material = new PhysicsMaterial2D(); material.restitution = 0.8; // 弹性系数 rigidBody.sharedMaterial = material; // 初始速度 rigidBody.linearVelocity = new Vec2(300, 500); } update() { // 防止球体飞出屏幕 let pos = this.node.position; let viewSize = view.getVisibleSize(); if(pos.x < 0 || pos.x > viewSize.width || pos.y < 0 || pos.y > viewSize.height) { this.resetBall(); } } resetBall() { let rigidBody = this.node.getComponent(RigidBody2D); rigidBody.linearVelocity = Vec2.ZERO; rigidBody.angularVelocity = 0; this.node.setPosition(0, 0); } }在实际项目中,动态物理组件的创建时机非常重要。我通常会在对象池获取对象时初始化物理组件,在回收时禁用物理模拟,这样可以获得最佳性能。另外需要注意,物理计算是在固定时间步长进行的,与渲染帧率不同步,因此处理物理相关逻辑时最好放在fixedUpdate而不是update中。
