动态格子算法常用于弹幕游戏的碰撞检测优化,可减少遍历开销。
这是我之前做的小游戏就用到了此算法,当后期满屏子弹时,优化效果非常明显。
思路
- 每个点只与当前所处的格子的点检测碰撞
- 当大格子内的点>格子内点限制 && 大格子的深度 < 最大深度则大格子分裂出四个小格子,把点放到小格子里。
- 当大格子内的点 <= 格子内点限制 并且存在四个小格子时,删除小格子,把点放回大格子。
示例
示例代码使用C#语言,可视化工具使用Unity
GridNode
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GridRect
{
public GridRect(float in_x,float in_y,float in_w, float in_h)
{
x = in_x;
y = in_y;
w = in_w;
h = in_h;
}
public float x;
public float y;
public float w;
public float h;
}
public class GridNode
{
List<GridNode> children;
public GridRect rect;
//最大深度
const int max_deep = 3;
//每个格子最大有多少个待检测物体
const int max_cnt = 4;
int deep;
int cnt;
List<GameObject> points = new List<GameObject>();
public GameObject grid;
// 添加一个点
public void Add(GameObject go)
{
++cnt;
points.Add(go);
if(children == null)
{
// 到达叶子格子,待检测物体保存当前格子 point.grid = this
if (deep <= max_deep && cnt > max_cnt)
{
Grow();
}
}
else //若是孩子存在,判断点在哪个子格子里,把点放进子格子
{
foreach (var item in children)
{
if(item.Evaluate(go))
{
item.Add(go);
break;
}
}
}
}
//移除点
public void Remove(GameObject go)
{
--cnt;
points.Remove(go);
if (children != null)
{
foreach (var item in children)
{
if (item.Evaluate(go))
{
item.Remove(go);
break;
}
}
if (cnt <= max_cnt)
{
Shrink();
}
}
else
{
}
}
// 树生长,生成四个子格子,在把点放在子格子里
public void Grow()
{
children = new List<GridNode>();
var rects = new List<GridRect>();
var half_w = rect.w / 2;
var half_h = rect.h / 2;
// 计算子格子的区域
rects.Add(new GridRect(rect.x, rect.y, half_w, half_h));
rects.Add(new GridRect(rect.x + half_w, rect.y, half_w, half_h));
rects.Add(new GridRect(rect.x, rect.y + half_h, half_w, half_h));
rects.Add(new GridRect(rect.x + half_w, rect.y + half_h, half_w, half_h));
for (int i = 0; i < 4; i++)
{
var child = new GridNode();
var r = rects[i];
child.Init(r.x, r.y, r.w, r.h, deep + 1);
foreach (var item in points)
{
if(child.Evaluate(item))
{
child.Add(item);
break;
}
}
children.Add(child);
}
}
// 收紧,删除子格子
public void Shrink()
{
foreach (var item in children)
{
item.Clear();
}
children = null;
}
// 判断点是否在此格子区域内
public bool Evaluate(GameObject go)
{
var pos = go.transform.position;
var ret = pos.x >= rect.x && pos.x < (rect.x + rect.w) &&
pos.y >= rect.y && pos.y < (rect.y + rect.h);
return ret;
}
// 初始化
public void Init(float x, float y, float w, float h, int in_deep)
{
rect = new GridRect(x, y, w, h);
deep = in_deep;
grid = GameObject.Instantiate(GridState.Inst.grid_prefab);
grid.transform.SetParent(GridState.Inst.grid_parent);
var tr = grid.GetComponent<RectTransform>();
tr.position = new Vector3(x, y, 0);
tr.sizeDelta = new Vector2(w, h);
}
public void Clear()
{
if(grid != null)
{
GameObject.Destroy(grid);
}
}
}
- 组织结构可以视为4叉树
- 视情况合理调整最大深度max_deep
- 注释叶子节点处,待检测物体保存当前格子
GridState
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GridState : MonoBehaviour
{
// Start is called before the first frame update
static public GridState Inst;
public GameObject grid_prefab;
public GameObject point_prefab;
public Transform grid_parent;
public Transform point_parent;
GridNode root;
Queue<GameObject> point_que = new Queue<GameObject>();
bool is_create_mode;
private void Awake()
{
Inst = this;
}
void Start()
{
is_create_mode = true;
root = new GridNode();
root.Init(0, 0, 1334, 750, 0);
}
float cnt;
float max = 0.1f;
private void FixedUpdate()
{
var t = Time.fixedDeltaTime;
cnt += t;
if (cnt > max)
{
cnt -= max;
if (is_create_mode)
{
var go = GameObject.Instantiate(point_prefab, point_parent);
go.transform.position = new Vector3(Random.Range(0, 1334), Random.Range(0, 750), 0);
root.Add(go);
point_que.Enqueue(go);
if (point_que.Count > 50)
{
is_create_mode = false;
}
}
else
{
var go = point_que.Dequeue();
root.Remove(go);
GameObject.Destroy(go);
if (point_que.Count == 0)
{
is_create_mode = true;
}
}
}
}
}
- 保存维护动态格子4叉树的根节点
- 动态格子算法测试,运行结果如思路上的图所示
备注
- 3D空间也适用,需Evaluate变为正方体检测
- 当检测物体太大甚至比格子还大此方法不适用