基于四叉树的场景管理

一、解决的问题

在游戏地图中有很多对象,根据视野范围内的区域,并把这些区域的对象显示出来,其它不在视野范围的不显示。

效果如下:

二、四叉树原理

在数据结构中,树常常用于层级管理,就像我们国家行政单位一样,从国家-->省-->市-->县(区)-->街道(村)

这样每个人属于哪个地方就很清楚了。

同样我们在游戏场景中也可以对游戏地图做类似的分类,基于地图的形状样式,使用四分法会比较方便,且层次又不会太多,所以我们选用四叉树来对场景进行管理。

三、使用四叉树创建地图分区

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

 

你可能感兴趣的:(Unity3D,四叉树)