cocos游戏开发中多角色碰撞,物理反弹后改变方向的实现逻辑

这里每一个人物都是一个碰撞体+刚体,然后可以实现手动控制,也可以实现开始就随机选择一个方向移动,然后碰到墙壁之后,按照物理定理来计算反弹角度,然后再根据反弹角度继续移动,墙壁是一个静态刚体,所以不会移动。刚开始使用的角色控制器来实现的,但是有时候会出现碰到墙壁后,就不再移动的情况,也不知道怎么回事,后来就索性手动学了移动逻辑。

移动逻辑其实没那么复杂,但是对我一个刚入门cocos的新玩家来讲,还是挺费脑子的......

1.需要先确定一个移动方向,这个方向是一个单位向量,只表示方向,

2.计算位移(不修改原方向向量),通过方向向量和速度数乘来计算每一帧的位移距离。

3.然后获取当前节点的玩家坐标,根据这个坐标,用向量加法 + 位移 = 每一帧玩家位置

4.使用drawLineOriginDirLen来绘制玩家当前移动的方向,用于调试玩家真正的移动方向

5.使用this.node.setPosition来设置玩家最终的位置。

其中碰撞逻辑onCollisionEnter还是有点东西的,需要获取两个物体的碰撞法线,然后计算反射方向,这个逻辑是AI写的,我也没有看懂......还需要再多研究研究

javascript 复制代码
import {
    _decorator,
    Component,
    Vec3,
    Quat,
    input,
    Input,
    EventKeyboard,
    KeyCode,
    Color,
    Collider,
    toRadian,
    ICollisionEvent,
} 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
        }
    }
}
相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax