版本:2.4.10
参考:
一 演示效果
碰撞红色,未碰撞蓝色。

二 二叉树、四叉树、八叉树
二叉树:树形结构,每个节点最多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;
}
}
浙公网安备 33010602011771号