一个空间中个体数量大的时候就需要做一些结构进行分区以减少计算量。空间划分有很多种,本片介绍一个简单的划分方式:单元空间划分。
简单来说就是将一个空间划分成很多格子,格子中有存着个体的集合。个体运动过程中,更新所有格子的集合。
下面图片演示运动过程中格子的变化,绿色格子标识其个体集合中有>0数量的个体。
核心代码如下:
SpacePartition.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace CellSpacePartition
{
public class Cell {
public int id;
public Bounds bounds;
public List<Agent> agentList;
public Cell(int id, Vector3 center, Vector3 size) {
this.id = id;
this.agentList = new List<Agent>();
this.bounds = new Bounds(center, size);
}
public void RemoveAgent(Agent agent) {
if (agentList.Contains(agent)) {
agentList.Remove(agent);
}
}
public void AddAgent(Agent agent) {
if (!agentList.Contains(agent))
{
agentList.Add(agent);
}
}
}
public static class SpacePartition
{
static float spaceX;
static float spaceZ;
static int cellCountX;
static int cellCountZ;
static List<Cell> cellList;
static Vector3 cellSize;
static Vector3 cellStartPos;
public static void Init(float spaceX, float spaceZ, Vector3 cellSize) {
cellList = new List<Cell>();
SpacePartition.spaceX = spaceX;
SpacePartition.spaceZ = spaceZ;
SpacePartition.cellSize = cellSize;
GenerateCell();
}
static void GenerateCell() {
cellCountX = (int)(spaceX / cellSize.x);
cellCountZ = (int)(spaceZ / cellSize.z);
cellStartPos = new Vector3(-spaceX * 0.5f + cellSize.x * 0.5f, 0, -spaceZ * 0.5f + cellSize.z * 0.5f);
int index = 0;
for (int i = 0; i < cellCountX; i++)
{
for (int j = 0; j < cellCountZ; j++)
{
Vector3 center = cellStartPos + new Vector3(cellSize.x * i, 0, cellSize.z * j);
Cell cell = new Cell(index, center, cellSize);
cellList.Add(cell);
}
}
}
static int PositionIntoIndex(Vector3 pos) {
float dx = pos.x - (spaceX * -1 * 0.5f);
float dz = pos.z - (spaceZ * -1 * 0.5f);
int x = (int)(dx / cellSize.x);
int z = (int)(dz / cellSize.z);
return x * cellCountX + z;
}
public static void UpdateAgentCell(Agent agent) {
int index = PositionIntoIndex(agent.transform.position);
if (index >= cellList.Count) {
return;
}
Cell tarCell = cellList[index];
if (agent.beloneCell == null)
{
agent.beloneCell = tarCell;
tarCell.AddAgent(agent);
}
else {
if (agent.beloneCell != tarCell) {
agent.beloneCell.RemoveAgent(agent);
agent.beloneCell = tarCell;
tarCell.AddAgent(agent);
}
}
}
public static List<Cell> GetCellList() {
return cellList;
}
}
}
Agent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace CellSpacePartition
{
public class Agent : MonoBehaviour
{
public Cell beloneCell;
public Vector3 moveDir;
public float speed;
bool isMove = false;
void Update()
{
if (Input.GetMouseButtonDown(1)) {
isMove = true;
}
if (Input.GetMouseButtonUp(1))
{
isMove = false;
}
if (!isMove) {
return;
}
MoveTarget();
transform.forward = moveDir;
transform.position += moveDir * speed * Time.deltaTime;
SpacePartition.UpdateAgentCell(this);
}
void MoveTarget() {
if (Input.GetMouseButton(1))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
Vector3 tarPos = new Vector3(hit.point.x, 0, hit.point.z);
moveDir = (tarPos - transform.position).normalized;
}
}
}
}
}
GameMain.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace CellSpacePartition
{
public class GameMain : MonoBehaviour
{
public GameObject agentPrefab;
public Vector2 cellSize;
float spaceX;
float spaceZ;
private void Awake()
{
Transform plane = GameObject.Find("Plane").transform;
spaceX = plane.localScale.x * 10;
spaceZ = plane.localScale.z * 10;
SpacePartition.Init(spaceX, spaceZ, new Vector3(cellSize.x, 10, cellSize.y));
}
private void Update()
{
if (Input.GetMouseButtonDown(0)) {
CreateAgent();
}
}
void CreateAgent() {
GameObject agentGO = Instantiate(agentPrefab);
agentGO.SetActive(true);
agentGO.transform.position = new Vector3(Random.Range(-spaceX, spaceX) * 0.5f, 0, Random.Range(-spaceZ, spaceZ) * 0.5f);
Agent agent = agentGO.GetComponent<Agent>();
}
private void OnDrawGizmos()
{
// 绘制格子
List<Cell> cellList = SpacePartition.GetCellList();
if (cellList == null) {
return;
}
for (int i = 0; i < cellList.Count; i++)
{
Cell cell = cellList[i];
if (cell.agentList.Count > 0)
{
Gizmos.color = new Color(0, 1f, 0, 0.2f);
Gizmos.DrawCube(cell.bounds.center, cell.bounds.size);
}
else {
Gizmos.color = Color.white;
Gizmos.DrawWireCube(cell.bounds.center, cell.bounds.size);
}
}
}
}
}