版本:2.4.10

参考:

四叉树与碰撞检测 !Cocos Creator !

碰撞检测优化-四叉树

游戏编程模式-空间分区

 

一 演示效果

碰撞红色,未碰撞蓝色。

 

二     二叉树、四叉树、八叉树

二叉树:树形结构,每个节点最多2个子树。

四叉树:树状数据结构,每个节点有四个子区块。

八叉树:描述三维空间的树状结构,任意子节点有0或8个。

 

           二叉树                                                 四叉树                                            八叉树

    

 

三  普通碰撞检测

场景中有500个矩形小球,要进行碰撞检测,普通的检测就是遍历一遍小球数组,两两进行碰撞检测。

 

 

遍历小球数组,使用函数rectRect()进行碰撞检测。

for (let i = 0; i < this.ballList.length; i++) {
    for (let j = i + 1; j < this.ballList.length; j++) {
        count++;
        let ballA = this.ballList[i];
        let ballB = this.ballList[j];
        if (this.rectRect(ballA.node, ballB.node)) {
            console.log("碰撞");
        }
    }
}

 

这种方式需要遍历每一个小球,造成大量的计算。500个小球需要进行499500次碰撞检测,消耗时间大约10-11毫秒。

 

 

三  划分区域进行碰撞检测(四叉树)

将场景划为为不同区域,小球只和自己同一区域的小球进行碰撞检测,这样可以大量减少碰撞次数。

这种方式需要额外计算每个小球属于哪一个区域,但是对比碰撞检测的消耗,还是比较节省效率的。

     

 

 

遍历小球列表ballList,计算小球所在格子的行列值,将小球保存到对应的格子列表中。

for (let i = 0; i < this.ballList.length; i++) {
    let ball = this.ballList[i];
    let row = Math.floor((ball.node.y + this.maxHeight) / this.gridSize);
    let col = Math.floor((ball.node.x + this.maxWidth) / this.gridSize);
    ball.row = row;
    ball.col = col;
    this.gridList[row][col].push(ball);
}

 

遍历小球列表ballList,根据小球的行列值row、col可以从格子gridLsit中获取同一区域的小球。每个小球只和自己同一区域的小球进行碰撞检测。

for (let i = 0; i < this.ballList.length; i++) {
    let ballA = this.ballList[i];
    let list = this.gridList[ballA.row][ballA.col];
    for (let j = 0; j < list.length; j++) {
        let ballB = list[j];
        if (ballA != ballB) {
            if (this.rectRect(ballA.node, ballB.node)) {
                console.log("碰撞");
            }
        }
    }
}

 

检测次数大约67364次,耗时4毫秒。对比普通检测,检测次数从499500变成了67364,耗时从10毫秒变成了4毫秒。

 

 

 

四 完整代码

Ball.ts:

const { ccclass, property } = cc._decorator;

/**
 * 小球
 * @author chenkai 2022.9.9
 */
@ccclass
export default class Ball extends cc.Component {
    /**行 */
    public row: number = 0;
    /**列 */
    public col: number = 0;
    /**移动速度 */
    public speed: number = 2;
    /**x轴速度 */
    public xSpeed: number = 0;
    /**y轴速度 */
    public ySpeed: number = 0;
}

  

MainScene.ts:

const { ccclass, property } = cc._decorator;

/**
 * 主场景
 * @author chenkai 2022.9.9
 */
@ccclass
export default class MainScene extends cc.Component {

    @property({ type: cc.Prefab, tooltip: "球(矩形)" })
    pb_ball: cc.Prefab = null;

    /**舞台宽度/2 */
    private maxWidth: number;
    /**舞台高度/2 */
    private maxHeight: number;
    /**小球列表 */
    private ballList: Ball[] = [];
    /**格子区域二位数组 */
    private gridList = [];
    /**格子行数 */
    private gridRow: number = 4;
    /**格子列数 */
    private gridCol: number = 4;
    /**格子高宽 */
    private gridSize: number = 400;
    /**检查类型 1普通检测 2划为区域检测 */
    private checkType: number = 2;

    onLoad() {
        //舞台边缘值
        this.maxWidth = cc.view.getVisibleSize().width / 2;
        this.maxHeight = cc.view.getVisibleSize().height / 2;
        //格子列表
        for (let i = 0; i < this.gridRow; i++) {
            this.gridList[i] = [];
            for (let j = 0; j < this.gridCol; j++) {
                this.gridList[i][j] = [];
            }
        }
        //创建小球
        this.createBall();
    }

    update(dt) {
        this.updateBallMove();

        let startTime = new Date().getTime();
        //普通碰撞检测
        if (this.checkType == 1) {
            this.checkNormalCollision();
            //划分格子碰撞检测
        } else {
            this.updateBallGrid();
            this.checkCollision();
        }
        console.log("消耗时间:", new Date().getTime() - startTime);
    }

    /**普通的碰撞检测 */
    private checkNormalCollision() {
        //将小球置蓝色
        for (let i = 0; i < this.ballList.length; i++) {
            this.ballList[i].node.color = new cc.Color().fromHEX("#0000ff");
        }
        //碰撞检测
        let count = 0;
        for (let i = 0; i < this.ballList.length; i++) {
            for (let j = i + 1; j < this.ballList.length; j++) {
                count++;
                let ballA = this.ballList[i];
                let ballB = this.ballList[j];
                if (this.rectRect(ballA.node, ballB.node)) {
                    ballA.node.color = new cc.Color().fromHEX("#ff0000");
                    ballB.node.color = new cc.Color().fromHEX("#ff0000");
                }
            }
        }
        console.log("计算次数:", count);
    }

    /**创建小球 */
    private createBall() {
        for (let i = 0; i < 1000; i++) {
            //随机位置
            let node: cc.Node = cc.instantiate(this.pb_ball);
            node.parent = this.node;
            node.x = Math.random() * this.maxWidth * 2 - this.maxWidth;
            node.y = Math.random() * this.maxHeight * 2 - this.maxHeight;
            //随机速度
            let ball: Ball = node.getComponent(Ball);
            ball.xSpeed = Math.random() * ball.speed;
            ball.ySpeed = Math.random() * ball.speed;
            this.ballList.push(ball);
        }
    }

    /**刷新小球移动 */
    private updateBallMove() {
        let len = this.ballList.length;
        let ball: Ball;
        for (let i = 0; i < len; i++) {
            ball = this.ballList[i];
            //移动
            ball.node.x += ball.xSpeed;
            ball.node.y += ball.ySpeed;
            //边缘检测 达到边缘后速度取反
            if (ball.node.x + ball.node.width / 2 > this.maxWidth) {
                ball.node.x = this.maxWidth - ball.node.width / 2;
                ball.xSpeed = -ball.speed;
            } else if (ball.node.x - ball.node.width / 2 < -this.maxWidth) {
                ball.node.x = - this.maxWidth + ball.node.width / 2;
                ball.xSpeed = ball.speed;
            }
            if (ball.node.y + ball.node.height / 2 > this.maxHeight) {
                ball.node.y = this.maxHeight - ball.node.height / 2;
                ball.ySpeed = -ball.speed;
            } else if (ball.node.y - ball.node.height / 2 < -this.maxHeight) {
                ball.node.y = -this.maxHeight + ball.node.height / 2;
                ball.ySpeed = ball.speed;
            }
        }
    }

    /**刷新小球所在格子 */
    private updateBallGrid() {
        //清理格子
        for (let i = 0; i < this.gridRow; i++) {
            for (let j = 0; j < this.gridCol; j++) {
                this.gridList[i][j].length = 0;
            }
        }
        //将小球置蓝色,重新计算小球所属行列的格子
        for (let i = 0; i < this.ballList.length; i++) {
            let ball = this.ballList[i];
            let row = Math.floor((ball.node.y + this.maxHeight) / this.gridSize);
            let col = Math.floor((ball.node.x + this.maxWidth) / this.gridSize);
            ball.row = row;
            ball.col = col;
            this.gridList[row][col].push(ball);
            ball.node.color = new cc.Color().fromHEX("#0000ff");
        }
    }

    /**碰撞检测 */
    private checkCollision() {
        let count = 0;
        for (let i = 0; i < this.ballList.length; i++) {
            let ballA = this.ballList[i];
            let list = this.gridList[ballA.row][ballA.col];
            for (let j = 0; j < list.length; j++) {
                count++;
                let ballB = list[j];
                if (ballA != ballB) {
                    if (this.rectRect(ballA.node, ballB.node)) {
                        ballA.node.color = new cc.Color().fromHEX("#ff0000");
                        ballB.node.color = new cc.Color().fromHEX("#ff0000");
                    }
                }
            }
        }
        console.log("检查次数:", count);
    }

    /**
     * cc.Intersection.rectRect
     * @param a  
     * @param b 
     * @returns true碰撞 false未碰撞
     */
    private rectRect(a: cc.Node, b: cc.Node) {
        var a_min_x = a.x - a.width / 2;
        var a_min_y = a.y - a.height / 2;
        var a_max_x = a.x + a.width / 2;
        var a_max_y = a.y + a.height / 2;
        var b_min_x = b.x - b.width / 2;
        var b_min_y = b.y - b.height / 2;
        var b_max_x = b.x + b.width / 2;
        var b_max_y = b.y + b.height / 2;
        return a_min_x <= b_max_x && a_max_x >= b_min_x && a_min_y <= b_max_y && a_max_y >= b_min_y;
    }
}

  

 

 

 

 

 

 

 

 

 

 

posted on 2022-09-09 16:11  gamedaybyday  阅读(1762)  评论(0编辑  收藏  举报