Cocos游戏开发中,检测两个物体碰撞,并实现物理反弹逻辑

其中涉及到的技术点还是挺多的,学的脑子疼,才发现游戏开发原来这么费脑子,既要学习数学,又要学习物理,还有懂得各种计算,真的脑壳疼,要不是老大要开发一个抖音小玩法的游戏,我是真的不想费这脑子.......而且小玩法的坑还更多

下面是这个玩家脚本的碰撞代码:

javascript 复制代码
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)
    @property
    moveSpeed: number = 5

    // 旋转速度 (度/秒)
    @property
    rotateSpeed: number = 90

    // 跳跃高度
    @property
    jumpHeight: number = 2

    // 重力加速度
    @property
    gravity: number = 9.8

    // 手动控制
    @property
    manualControl: 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.5

    start() {
        // 设置键盘输入监听
        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.contacts
        const 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.7

            if (isWall) {
                console.log('墙壁碰撞,法线方向:', worleNormal)
                this._handleBounce(worleNormal)
            } else {
                console.log('非墙壁碰撞(可能是地面或天花板)')
            }
        } else {
            console.log('没有碰撞')
        }
    }

    // 初始化随机移动(添加方向有效性检查)
    private _initializeRandomMovement() {
        // 生成随机角度 (0-360度)
        const randomAngle = Math.random() * 360
        const 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 = true
        this._bounceTimer = 0

        // 计算当前移动方向
        Vec3.copy(this._currentVelocity, this._randomMoveDirection)
        Vec3.multiplyScalar(
            this._currentVelocity,
            this._currentVelocity,
            this.moveSpeed
        )

        console.log('碰撞前的移动方向', this._currentVelocity)

        // 计算点积并确保不会出现NaN
        const dotProduct = Vec3.dot(this._currentVelocity, hitNormal)
        if (isNaN(dotProduct)) {
            console.error('计算点积时出现NaN,使用随机方向替代')
            this._initializeRandomMovement()
            return
        }

        // 计算反射方向:v' = v - 2(v·n)n
        const 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.forward
            moveDirection.add(backward)
        }

        // 左右移动 (基于当前朝向)
        if (this._input.left) {
            const left = this.node.right.negative()
            moveDirection.add(left)
        }
        if (this._input.right) {
            const right = this.node.right
            moveDirection.add(right)
        }

        // 标准化移动方向并应用速度
        if (!Vec3.equals(moveDirection, Vec3.ZERO)) {
            Vec3.normalize(moveDirection, moveDirection)
            moveDirection.multiplyScalar(this.moveSpeed * deltaTime)
        }

        // 更新位置
        const newPosition = this.node.getPosition()
        // 将移动方向和当前位置相加,得到新的位置newPosition
        Vec3.add(newPosition, newPosition, moveDirection)

        // 简单地面检测
        if (newPosition.y <= 0) {
            newPosition.y = 0
            this._verticalVelocity = 0
            this._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 = true
                break
            case KeyCode.KEY_S:
                this._input.backward = true
                break
            case KeyCode.KEY_A:
                this._input.left = true
                break
            case KeyCode.KEY_D:
                this._input.right = true
                break
            case KeyCode.SPACE:
                this._input.jump = true
                break
        }
    }

    onKeyUp(event: EventKeyboard) {
        switch (event.keyCode) {
            case KeyCode.KEY_W:
                this._input.forward = false
                break
            case KeyCode.KEY_S:
                this._input.backward = false
                break
            case KeyCode.KEY_A:
                this._input.left = false
                break
            case KeyCode.KEY_D:
                this._input.right = false
                break
            case KeyCode.SPACE:
                this._input.jump = false
                break
        }
    }
}
相关推荐
慧一居士19 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead21 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
伍哥的传说8 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js