Day 04 · 像素级控制你的游戏Cocos 物理系统与碰撞检测完全指南学习目标掌握 Cocos 2D 物理系统实现刚体运动、碰撞检测和物理事件响应预计时间3 小时难度⭐⭐⭐☆☆Cocos 物理系统概览Cocos Creator 3.8 提供两套物理系统物理系统引擎适用场景2D 物理推荐新手Box2DBuiltin横版游戏、弹球、重力类游戏3D 物理Bullet/PhysX3D 游戏、复杂物理模拟本文专注2D 物理系统。1. 开启物理系统在使用物理功能前需要先在项目设置中开启菜单栏 →项目→项目设置切换到功能裁剪标签找到2D 物理系统选择Box2D也可以在脚本中初始化import{_decorator,Component,PhysicsSystem2D,ERigidBody2DType}fromcc;const{ccclass}_decorator;ccclass(PhysicsInit)exportclassPhysicsInitextendsComponent{onLoad(){// 设置全局重力默认向下 -10PhysicsSystem2D.instance.gravity.set(0,-980);// 开启物理调试绘制开发时开启发布时关闭PhysicsSystem2D.instance.debugDrawFlags1;}}2. RigidBody2D 刚体组件刚体RigidBody2D让节点具有物理质量可以受力、有速度。2.1 刚体类型类型常量说明动态DynamicERigidBody2DType.Dynamic受物理引擎控制可受力、受重力静态StaticERigidBody2DType.Static完全静止不受任何力影响适合地面、墙壁运动学KinematicERigidBody2DType.Kinematic不受物理引擎控制但可以碰撞适合电梯、平台2.2 在编辑器中添加选中节点 →添加组件→Physics 2D→RigidBody 2D2.3 脚本控制刚体import{_decorator,Component,RigidBody2D,ERigidBody2DType,Vec2}fromcc;const{ccclass,property}_decorator;ccclass(PhysicsPlayer)exportclassPhysicsPlayerextendsComponent{propertyjumpForce:number600;propertymoveSpeed:number200;private_body:RigidBody2Dnull!;private_isOnGround:booleanfalse;onLoad(){this._bodythis.getComponent(RigidBody2D)!;}start(){// 设置刚体属性this._body.typeERigidBody2DType.Dynamic;this._body.gravityScale1;// 重力倍率this._body.linearDamping0.5;// 线性阻力0无阻力this._body.angularDamping1;// 角阻力this._body.fixedRotationtrue;// 固定旋转防止角色倒地}jump(){if(!this._isOnGround)return;// 方式1施加冲力瞬间速度变化适合跳跃this._body.applyLinearImpulseToCenter(newVec2(0,this.jumpForce),true// wake true唤醒休眠的刚体);// 方式2直接设置速度// this._body.linearVelocity new Vec2(0, this.jumpForce);}moveLeft(){constvelthis._body.linearVelocity;this._body.linearVelocitynewVec2(-this.moveSpeed,vel.y);}moveRight(){constvelthis._body.linearVelocity;this._body.linearVelocitynewVec2(this.moveSpeed,vel.y);}stopHorizontal(){constvelthis._body.linearVelocity;this._body.linearVelocitynewVec2(0,vel.y);}// 施加持续力适合推力、风力等applyForce(forceX:number,forceY:number){this._body.applyForceToCenter(newVec2(forceX,forceY),true);}}3. Collider2D 碰撞体碰撞体定义物体的物理形状不一定等于视觉形状。3.1 常用碰撞体类型碰撞体说明适用BoxCollider2D矩形角色、平台、大多数场景CircleCollider2D圆形圆形物体、弹球PolygonCollider2D多边形不规则形状ChainCollider2D链条不闭合多段线坡道、地形3.2 编辑器配置添加BoxCollider2D后在 Inspector 中可以看到关键属性Offset碰撞体相对于节点中心的偏移Size碰撞体的宽高独立于节点的 ContentSizeSensor勾选后变为传感器只检测重叠不产生物理阻挡Tag碰撞标签用于区分不同类型的碰撞体3.3 碰撞事件处理import{_decorator,Component,RigidBody2D,Collider2D,Contact2DType,IPhysics2DContact}fromcc;const{ccclass}_decorator;ccclass(CollisionHandler)exportclassCollisionHandlerextendsComponent{onLoad(){// 获取碰撞体组件constcolliderthis.getComponent(Collider2D)!;// 注册碰撞事件collider.on(Contact2DType.BEGIN_CONTACT,this.onBeginContact,this);collider.on(Contact2DType.END_CONTACT,this.onEndContact,this);// PRE_SOLVE 和 POST_SOLVE 仅在非传感器碰撞时触发collider.on(Contact2DType.PRE_SOLVE,this.onPreSolve,this);}// 碰撞开始onBeginContact(selfCollider:Collider2D,otherCollider:Collider2D,contact:IPhysics2DContact|null){console.log(碰撞开始);console.log(自身碰撞体 Tag:,selfCollider.tag);console.log(对方节点名称:,otherCollider.node.name);console.log(对方碰撞体 Tag:,otherCollider.tag);// 判断是否落地通过 Tag 区分地面if(otherCollider.tag1){// Tag 1 地面this._isOnGroundtrue;}// 判断对方节点类型constenemyotherCollider.node.getComponent(Enemy);if(enemy){console.log(碰到了敌人);}}// 碰撞结束onEndContact(selfCollider:Collider2D,otherCollider:Collider2D,contact:IPhysics2DContact|null){if(otherCollider.tag1){this._isOnGroundfalse;}}// 碰撞解算前可以在这里修改碰撞行为onPreSolve(selfCollider:Collider2D,otherCollider:Collider2D,contact:IPhysics2DContact|null){// 禁止特定碰撞例如单向平台只从下往上可以穿过if(contactotherCollider.tag2){contact.disabledtrue;}}private_isOnGround:booleanfalse;onDestroy(){constcolliderthis.getComponent(Collider2D);if(collider){collider.off(Contact2DType.BEGIN_CONTACT,this.onBeginContact,this);collider.off(Contact2DType.END_CONTACT,this.onEndContact,this);collider.off(Contact2DType.PRE_SOLVE,this.onPreSolve,this);}}}4. 碰撞分组与过滤实际游戏中你不希望子弹和子弹碰撞玩家和友军不互斥。Cocos 用**分组Group**来管理4.1 设置碰撞分组项目设置→物理→碰撞矩阵Collision Matrix常见分组设计分组 0: Default默认 分组 1: Player玩家 分组 2: Enemy敌人 分组 3: PlayerBullet玩家子弹 分组 4: EnemyBullet敌人子弹 分组 5: Ground地面 碰撞矩阵 Player 碰撞 Enemy ✅ Player 碰撞 EnemyBullet ✅ Player 碰撞 Ground ✅ PlayerBullet 碰撞 Enemy ✅ PlayerBullet 碰撞 PlayerBullet ❌子弹之间不碰撞 Enemy 碰撞 Enemy ❌敌人之间不碰撞4.2 在脚本中设置节点分组import{_decorator,Component,PhysicsGroup}fromcc;const{ccclass}_decorator;ccclass(GroupSetter)exportclassGroupSetterextendsComponent{onLoad(){// 通过枚举设置分组需要在项目设置中先定义分组this.node.layer11;// 设置为分组 1Player}}5. 射线检测Raycast射线检测是非常实用的功能检测前方是否有障碍物、子弹是否击中目标等。import{_decorator,Component,PhysicsSystem2D,PhysicsRayResult,Vec2}fromcc;const{ccclass}_decorator;ccclass(RaycastDemo)exportclassRaycastDemoextendsComponent{update(deltaTime:number){this.checkGrounded();this.checkAhead();}// 检测是否在地面上向下发射射线checkGrounded():boolean{conststartPosnewVec2(this.node.worldPosition.x,this.node.worldPosition.y);constendPosnewVec2(startPos.x,startPos.y-60);// 向下60像素constresultsPhysicsSystem2D.instance.raycast(startPos,endPos);if(resultsresults.length0){// 找到了碰撞点consthitresults[0];console.log(距地面距离:,hit.fraction*60);returntrue;}returnfalse;}// 检测前方障碍物checkAhead(){constposthis.node.worldPosition;conststartnewVec2(pos.x,pos.y);constendnewVec2(pos.x100,pos.y);// 向右100像素// raycastAll: 返回所有碰撞结果constresultsPhysicsSystem2D.instance.raycast(start,end,true);if(resultsresults.length0){results.forEach(result{console.log(前方障碍物:,result.collider.node.name);});}}}6. 完整实战横版跑酷基础框架综合今天所学构建一个可以跑、跳、落地的角色import{_decorator,Component,RigidBody2D,Collider2D,Contact2DType,IPhysics2DContact,Vec2,input,Input,EventKeyboard,KeyCode}fromcc;const{ccclass,property}_decorator;ccclass(PlatformerPlayer)exportclassPlatformerPlayerextendsComponent{property({displayName:移动速度,min:0,max:500})moveSpeed:number250;property({displayName:跳跃力度,min:0,max:1500})jumpForce:number700;private_body:RigidBody2Dnull!;private_isGrounded:booleantrue;private_groundContactCount:number0;// 同时接触的地面数量onLoad(){this._bodythis.getComponent(RigidBody2D)!;constcolliderthis.getComponent(Collider2D)!;collider.on(Contact2DType.BEGIN_CONTACT,this.onBeginContact,this);collider.on(Contact2DType.END_CONTACT,this.onEndContact,this);}start(){this._body.fixedRotationtrue;this._body.gravityScale2.5;// 更快的下落感input.on(Input.EventType.KEY_DOWN,this.onKeyDown,this);input.on(Input.EventType.KEY_UP,this.onKeyUp,this);}onBeginContact(self:Collider2D,other:Collider2D,contact:IPhysics2DContact|null){// Tag 为 1 的是地面if(other.tag1){this._groundContactCount;this._isGroundedthis._groundContactCount0;}}onEndContact(self:Collider2D,other:Collider2D,contact:IPhysics2DContact|null){if(other.tag1){this._groundContactCountMath.max(0,this._groundContactCount-1);this._isGroundedthis._groundContactCount0;}}onKeyDown(event:EventKeyboard){console.log(event.keyCode)this._pressedKeys.add(event.keyCode);if(event.keyCodeKeyCode.SPACE||event.keyCodeKeyCode.ARROW_UP){this.tryJump();}}onKeyUp(event:EventKeyboard){this._pressedKeys.delete(event.keyCode);}tryJump(){console.log(_isGrounded ,this._isGrounded);if(!this._isGrounded)return;// 保持横向速度叠加跳跃速度constvelthis._body.linearVelocity;this._body.linearVelocitynewVec2(vel.x,this.jumpForce);}update(deltaTime:number){// 键盘左右移动lettargetVX0;if(this._pressedKeys.has(KeyCode.ARROW_LEFT)||this._pressedKeys.has(KeyCode.KEY_A)){targetVX-this.moveSpeed;}elseif(this._pressedKeys.has(KeyCode.ARROW_RIGHT)||this._pressedKeys.has(KeyCode.KEY_D)){targetVXthis.moveSpeed;}// 平滑过渡横向速度地面时较大阻力空中时较小constcurrentVXthis._body.linearVelocity.x;constlerpFactorthis._isGrounded?0.2:0.05;constnewVXcurrentVX(targetVX-currentVX)*lerpFactor;this._body.linearVelocitynewVec2(newVX,this._body.linearVelocity.y);}private_pressedKeys:SetKeyCodenewSet();// (在 start() 中已注册 KEY_DOWN还需注册 KEY_UP)onDestroy(){constcolliderthis.getComponent(Collider2D);if(collider){collider.off(Contact2DType.BEGIN_CONTACT,this.onBeginContact,this);collider.off(Contact2DType.END_CONTACT,this.onEndContact,this);}input.off(Input.EventType.KEY_DOWN,this.onKeyDown,this);input.off(Input.EventType.KEY_UP,this.onKeyUp,this);}}7. 今日总结✅ 理解物理系统的开启配置✅ 掌握 RigidBody2D 的三种类型和速度/力的控制✅ 掌握 Collider2D 的碰撞体类型和碰撞事件✅ 学会碰撞分组与过滤碰撞矩阵✅ 学会射线检测✅ 实现完整横版角色控制器⚠️ 物理常见坑问题原因解决方案角色飞速穿过地面速度过快隧穿效应开启bullet模式或减小移动速度角色站在边缘会卡住碰撞体边角摩擦给角色碰撞体底部加 CircleCollider 或减小摩擦力碰撞事件重复触发多个碰撞体在同一节点用 Tag 区分或只给主碰撞体注册事件跳跃时跳两次groundCount 计数错误用计数器代替布尔值跟踪接地状态← Day 03 | 系列目录 | Day 05 →