版本:2.4.10
参考:
一 演示效果
碰撞红色,未碰撞蓝色。
二 二叉树、四叉树、八叉树
二叉树:树形结构,每个节点最多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; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!