Cocos游戏开发中,检测两个物体碰撞,并实现物理反弹逻辑
其中涉及到的技术点还是挺多的,学的脑子疼,才发现游戏开发原来这么费脑子,既要学习数学,又要学习物理,还有懂得各种计算,真的脑壳疼,要不是老大要开发一个抖音小玩法的游戏,我是真的不想费这脑子.......而且小玩法的坑还更多
下面是这个玩家脚本的碰撞代码:
import {_decorator,Component,Vec3,Quat,input,Input,EventKeyboard,KeyCode,Color,ITriggerEvent,Collider,toRadian,ICollisionEvent,CollisionEventType,
} from 'cc'
import { drawLineOriginDirLen } from '../Utils/Debug/DebugDraw'
const { ccclass, property } = _decorator@ccclass('SimpleMovementController')
export class SimpleMovementController extends Component {// 移动速度 (m/s)@propertymoveSpeed: number = 5// 旋转速度 (度/秒)@propertyrotateSpeed: number = 90// 跳跃高度@propertyjumpHeight: number = 2// 重力加速度@propertygravity: number = 9.8// 手动控制@propertymanualControl: boolean = true// 当前垂直速度private _verticalVelocity: number = 0// 是否在地面private _isGrounded: boolean = true// 输入状态private _input = {forward: false,backward: false,left: false,right: false,jump: false,}// 随机移动相关属性private _isRandomMoving = false// 随机移动方向private _randomMoveDirection = new Vec3()// 当前移动速度向量private _currentVelocity = new Vec3()// 是否正在反弹private _isBouncing = false// 反弹计时器,防止连续反弹private _bounceTimer = 0// 反弹冷却时间,单位秒private _bounceCooldown = 0.001// 随机移动计时器,单位秒private _randomMoveTimer = 0// 反弹相关属性// 反弹系数 (0-1),1表示完全弹性碰撞,0表示完全非弹性碰撞@_decorator.property({ range: [0, 1, 0.1] })public bounceCoefficient = 0.8// 添加反弹最小速度阈值@_decorator.property({ tooltip: '反弹后最小速度阈值,避免过小的移动' })public minBounceSpeed = 0.5start() {// 设置键盘输入监听input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this)input.on(Input.EventType.KEY_UP, this.onKeyUp, this)// 设置碰撞监听const colider = this.node.getComponent(Collider)if (colider) {console.log('colider found')colider.on('onCollisionEnter', this.onCollisionEnter, this)} else {console.log('colider not found')}// 手动还是自动if (this.manualControl) {console.log('手动控制')} else {this._initializeRandomMovement()}}onCollisionEnter(event: ICollisionEvent) {console.log('onCollisionEnter---->', event)// 检查是否是墙壁碰撞(法线向上分量小于0.7认为是墙壁)const contacts = event.contactsconst worleNormal = new Vec3()if (contacts &&contacts.length > 0) {// 获取第一个接触点的世界法线contacts[0].getWorldNormalOnA(worleNormal)console.log('worleNormal---->', worleNormal)// 检查是否是墙壁碰撞(法线向上分量小于0.7认为是墙壁)const isWall = Math.abs(worleNormal.y) < 0.7if (isWall) {console.log('墙壁碰撞,法线方向:', worleNormal)this._handleBounce(worleNormal)} else {console.log('非墙壁碰撞(可能是地面或天花板)')}} else {console.log('没有碰撞')}}// 初始化随机移动(添加方向有效性检查)private _initializeRandomMovement() {// 生成随机角度 (0-360度)const randomAngle = Math.random() * 360const randomRadian = toRadian(randomAngle)// 设置随机移动方向并确保归一化this._randomMoveDirection = new Vec3(Math.sin(randomRadian),0,Math.cos(randomRadian))// 确保随机移动方向归一化Vec3.normalize(this._randomMoveDirection, this._randomMoveDirection)// 启用随机移动this._isRandomMoving = true// 重置随机移动计时器this._randomMoveTimer = 0}// 处理反弹逻辑private _handleBounce(hitNormal: Vec3) {// 设置反弹状态this._isBouncing = truethis._bounceTimer = 0// 计算当前移动方向Vec3.copy(this._currentVelocity, this._randomMoveDirection)Vec3.multiplyScalar(this._currentVelocity,this._currentVelocity,this.moveSpeed)console.log('碰撞前的移动方向', this._currentVelocity)// 计算点积并确保不会出现NaNconst dotProduct = Vec3.dot(this._currentVelocity, hitNormal)if (isNaN(dotProduct)) {console.error('计算点积时出现NaN,使用随机方向替代')this._initializeRandomMovement()return}// 计算反射方向:v' = v - 2(v·n)nconst reflection = new Vec3()Vec3.multiplyScalar(reflection, hitNormal, 2 * dotProduct)Vec3.subtract(reflection, this._currentVelocity, reflection)// 应用反弹系数Vec3.multiplyScalar(reflection, reflection, this.bounceCoefficient)// 检查反射向量是否有效const reflectionLength = Vec3.len(reflection)if (reflectionLength < this.minBounceSpeed) {// 如果反弹后速度太小,重新生成随机方向console.warn('反弹后速度太小,重新生成随机方向')this._initializeRandomMovement()return}// 更新随机移动方向(保持归一化)Vec3.normalize(this._randomMoveDirection, reflection)console.log('反弹后的移动方向', this._randomMoveDirection)}// 手动控制private _manualControl(deltaTime: number) {// 计算移动方向const moveDirection = new Vec3(0, 0, 0)// 前后移动 (基于当前朝向)if (this._input.forward) {const forward = this.node.forward.negative()moveDirection.add(forward)}if (this._input.backward) {const backward = this.node.forwardmoveDirection.add(backward)}// 左右移动 (基于当前朝向)if (this._input.left) {const left = this.node.right.negative()moveDirection.add(left)}if (this._input.right) {const right = this.node.rightmoveDirection.add(right)}// 标准化移动方向并应用速度if (!Vec3.equals(moveDirection, Vec3.ZERO)) {Vec3.normalize(moveDirection, moveDirection)moveDirection.multiplyScalar(this.moveSpeed * deltaTime)}// 更新位置const newPosition = this.node.getPosition()// 将移动方向和当前位置相加,得到新的位置newPositionVec3.add(newPosition, newPosition, moveDirection)// 简单地面检测if (newPosition.y <= 0) {newPosition.y = 0this._verticalVelocity = 0this._isGrounded = true}// 调试模式下绘制移动方向drawLineOriginDirLen(this.node.worldPosition,Vec3.normalize(new Vec3(), moveDirection),10,Color.RED)// 将节点位置更新到最新位置this.node.setPosition(newPosition)// 旋转控制 (Q/E键)if (this._input.left) {this.node.rotate(Quat.fromAxisAngle(new Quat(),Vec3.UP,((this.rotateSpeed * Math.PI) / 180) * deltaTime))}if (this._input.right) {this.node.rotate(Quat.fromAxisAngle(new Quat(),Vec3.UP,((-this.rotateSpeed * Math.PI) / 180) * deltaTime))}}// 随机控制private _randomControl(deltaTime: number) {// 确保方向向量是归一化的Vec3.normalize(this._randomMoveDirection, this._randomMoveDirection)// 计算位移(不修改原方向向量)const displacement = new Vec3(this._randomMoveDirection)displacement.multiplyScalar(this.moveSpeed * deltaTime)// 更新世界坐标(避免父节点变换影响)const newPosition = this.node.getPosition()Vec3.add(newPosition, newPosition, displacement)this.node.setPosition(newPosition)// 调试模式下绘制移动方向drawLineOriginDirLen(this.node.getPosition(),Vec3.normalize(new Vec3(), displacement),10,Color.RED)// drawLineOriginDirLen(// this.node.worldPosition,// Vec3.normalize(new Vec3(), this._randomMoveDirection),// 10,// Color.RED// )// // 计算位移(不修改原方向向量)// const newPosition = this.node.getPosition()// // 创建一个与随机方向向量相同的新向量(避免修改原方向向量导致原来的方向向量越来越小)// const displacement = new Vec3(this._randomMoveDirection)// // 将位移向量乘以速度和时间,得到位移量// displacement.multiplyScalar(this.moveSpeed * deltaTime)// Vec3.add(newPosition, newPosition, displacement)// console.log('newPosition---->', newPosition)// this.node.setPosition(newPosition)// 调试信息// console.log('Random Move Direction:', this._randomMoveDirection)// console.log('Displacement:', displacement)// console.log('New Position:', newPosition)}update(deltaTime: number) {if (this.manualControl) {this._manualControl(deltaTime)} else {this._randomControl(deltaTime)}}onKeyDown(event: EventKeyboard) {switch (event.keyCode) {case KeyCode.KEY_W:this._input.forward = truebreakcase KeyCode.KEY_S:this._input.backward = truebreakcase KeyCode.KEY_A:this._input.left = truebreakcase KeyCode.KEY_D:this._input.right = truebreakcase KeyCode.SPACE:this._input.jump = truebreak}}onKeyUp(event: EventKeyboard) {switch (event.keyCode) {case KeyCode.KEY_W:this._input.forward = falsebreakcase KeyCode.KEY_S:this._input.backward = falsebreakcase KeyCode.KEY_A:this._input.left = falsebreakcase KeyCode.KEY_D:this._input.right = falsebreakcase KeyCode.SPACE:this._input.jump = falsebreak}}
}