在游戏地图中有很多对象,根据视野范围内的区域,并把这些区域的对象显示出来,其它不在视野范围的不显示。
效果如下:
在数据结构中,树常常用于层级管理,就像我们国家行政单位一样,从国家-->省-->市-->县(区)-->街道(村)
这样每个人属于哪个地方就很清楚了。
同样我们在游戏场景中也可以对游戏地图做类似的分类,基于地图的形状样式,使用四分法会比较方便,且层次又不会太多,所以我们选用四叉树来对场景进行管理。
1.先定义一个完整的树
maxChildCount 即为使用四叉树,maxDepth 为我们树划分层数,同时使用Bounds把划分的区域绘制出来。
public class Tree : INode
{
public Bounds bound { get; set; }//包围盒
private Node root;
public int maxDepth { get; set; } //最大深度
public int maxChildCount { get; set; }//最大叶子数
public Tree(Bounds bound)//构造函数 构造树
{
this.bound = bound;
this.maxDepth = 5;
this.maxChildCount = 4;
root = new Node(bound, 0, this);
}
public void InsertObj(ObjData obj)
{
root.InsertObj(obj);
}
public void TriggerMove(Camera camera)
{
root.TriggerMove(camera);
}
public void DrawBound()
{
root.DrawBound();
}
}
2.定义节点
objList用来管辖当前节点的物体
这里使用InsertObj() 的递归来把物体逐步分到底层节点,当一个物体同时属于两个或多个子节点,则划到当前节点。
public class Node : INode
{
public Bounds bound { get; set; }//包围盒
private int depth;//深度
private Tree belongTree;//所属子树
private Node[] childList;//当前树节点的子节点
private List objList;//节点中的物体
public Node(Bounds bound, int depth, Tree belongTree)
{
this.belongTree = belongTree;
this.bound = bound;
this.depth = depth;
//childList = new Node[belongTree.maxChildCount];
objList = new List();
}
public void InsertObj(ObjData obj)//插入对象
{
Node node = null;
bool bChild = false;
if(depth < belongTree.maxDepth && childList == null)//如果深度小于数的最大子节点数 并且没有子节点
{
//如果还没到叶子节点,可以拥有儿子且儿子未创建,则创建儿子
CerateChild();
}
if(childList != null)
{
for (int i = 0; i < childList.Length; ++i)
{
Node item = childList[i];
if (item == null)
{
break;
}
if (item.bound.Contains(obj.pos))
{
if (node != null)
{
bChild = false;
break;
}
node = item;
bChild = true;
}
}
}
if (bChild)
{
//只有一个儿子可以包含该物体,则该物体
node.InsertObj(obj);
}
else
{
objList.Add(obj);
}
}
public void TriggerMove(Camera camera)
{
//刷新当前节点
for(int i = 0; i < objList.Count; ++i)
{
//进入该节点中意味着该节点在摄像机内,把该节点保存的物体全部创建出来
ResourcesManager.Instance.LoadAsync(objList[i]);
}
if(depth == 0)
{
ResourcesManager.Instance.RefreshStatus();
}
//刷新子节点
if (childList != null)
{
for(int i = 0; i < childList.Length; ++i)
{
if (childList[i].bound.CheckBoundIsInCamera(camera))
{
childList[i].TriggerMove(camera);
}
}
}
}
private void CerateChild()//创建孩子
{
childList = new Node[belongTree.maxChildCount];
int index = 0;
for(int i = -1; i <= 1; i+=2)
{
for(int j = -1; j <= 1; j+=2)
{
Vector3 centerOffset = new Vector3(bound.size.x / 4 * i, 0, bound.size.z / 4 * j);
Vector3 cSize = new Vector3(bound.size.x / 2, bound.size.y, bound.size.z / 2);
Bounds cBound = new Bounds(bound.center + centerOffset, cSize);
childList[index++] = new Node(cBound, depth + 1, belongTree);
}
}
}
public void DrawBound()//绘制包围盒
{
if(objList.Count != 0)//范围内有物体就画蓝色
{
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(bound.center, bound.size - Vector3.one * 0.1f);
}
else//没有就画红色
{
Gizmos.color = Color.green;
Gizmos.DrawWireCube(bound.center, bound.size - Vector3.one * 0.1f);
}
if(childList != null)//孩子节点也是一样的绘制
{
for(int i = 0; i < childList.Length; ++i)
{
childList[i].DrawBound();
}
}
}
}
3.场景启动类
objList为场景中所有的物体
[System.Serializable]
public class Main : MonoBehaviour
{
[SerializeField]
public List objList = new List();//可观察物体列表
public Bounds mainBound;//主包围盒(最大的范围)
private Tree tree;//构建树
private bool bInitEnd = false;//是否初始化完成
private Role role;//人物(主角)
public bool isDebug;
public void Awake()
{
tree = new Tree(mainBound);//创建树根节点
for(int i = 0; i < objList.Count; ++i)//把可观察的这些物体都插入到对应的树节点去
{
tree.InsertObj(objList[i]);
}
role = GameObject.Find("Role").GetComponent();
bInitEnd = true;
}
private void Update()
{
if (role.bMove)
{
tree.TriggerMove(role.mCamera);
}
}
private void OnDrawGizmos()
{
if (!isDebug)
{
return;
}
if (bInitEnd)
{
tree.DrawBound();
}
else
{
Gizmos.DrawWireCube(mainBound.center, mainBound.size);//画出一个最大的包围盒
}
}
}
对bound来说,它有8个点,当它的8个点同时处于摄像机裁剪块上方/下方/前方/后方/左方/右方,那么该bound不与摄像机可视范围交叉
public static class Expand
{
public static bool CheckBoundIsInCamera(this Bounds bound, Camera camera)//检测物体是否在摄像机范围内
{
System.Func ComputeOutCode = (projectionPos) =>
{
int _code = 0;
if (projectionPos.x < -projectionPos.w) _code |= 1;
if (projectionPos.x > projectionPos.w) _code |= 2;
if (projectionPos.y < -projectionPos.w) _code |= 4;
if (projectionPos.y > projectionPos.w) _code |= 8;
if (projectionPos.z < -projectionPos.w) _code |= 16;
if (projectionPos.z > projectionPos.w) _code |= 32;
return _code;
};
Vector4 worldPos = Vector4.one;
int code = 63;
for (int i = -1; i <= 1; i += 2)
{
for (int j = -1; j <= 1; j += 2)
{
for (int k = -1; k <= 1; k += 2)
{
worldPos.x = bound.center.x + i * bound.extents.x;
worldPos.y = bound.center.y + j * bound.extents.y;
worldPos.z = bound.center.z + k * bound.extents.z;
code &= ComputeOutCode(camera.projectionMatrix * camera.worldToCameraMatrix * worldPos);
}
}
}
return code == 0 ? true : false;
}
}