基於四叉樹的場景管理

一、解決的問題

在遊戲地圖中有很多對象,根據視野範圍內的區域,並把這些區域的對象顯示出來,其它不在視野範圍的不顯示。

效果如下:

二、四叉樹原理

在數據結構中,樹常常用於層級管理,就像我們國家行政單位一樣,從國家-->省-->市-->縣(區)-->街道(村)

這樣每個人屬於哪個地方就很清楚了。

同樣我們在遊戲場景中也可以對遊戲地圖做類似的分類,基於地圖的形狀樣式,使用四分法會比較方便,且層次又不會太多,所以我們選用四叉樹來對場景進行管理。

三、使用四叉樹創建地圖分區

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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章