随笔 - 283  文章 - 0 评论 - 110 阅读 - 116万
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

版本:2.4.10

参考:

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

碰撞检测优化-四叉树

游戏编程模式-空间分区

 

一 演示效果

碰撞红色,未碰撞蓝色。

 

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

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

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

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

 

           二叉树                                                 四叉树                                            八叉树

    

 

三  普通碰撞检测

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

 

 

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

1
2
3
4
5
6
7
8
9
10
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,计算小球所在格子的行列值,将小球保存到对应的格子列表中。

1
2
3
4
5
6
7
8
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中获取同一区域的小球。每个小球只和自己同一区域的小球进行碰撞检测。

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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   gamedaybyday  阅读(2525)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示