一、解決的問題
在遊戲地圖中有很多對象,根據視野範圍內的區域,並把這些區域的對象顯示出來,其它不在視野範圍的不顯示。
效果如下:
二、四叉樹原理
在數據結構中,樹常常用於層級管理,就像我們國家行政單位一樣,從國家-->省-->市-->縣(區)-->街道(村)
這樣每個人屬於哪個地方就很清楚了。
同樣我們在遊戲場景中也可以對遊戲地圖做類似的分類,基於地圖的形狀樣式,使用四分法會比較方便,且層次又不會太多,所以我們選用四叉樹來對場景進行管理。
三、使用四叉樹創建地圖分區
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<ObjData> 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<ObjData>();
}
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<ObjData> objList = new List<ObjData>();//可觀察物體列表
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<Role>();
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<Vector4, int> 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;
}
}