unity大地形加载(1)

大地形加载学习教程1

参考网址:
https://blog.uwa4d.com/archives/1919.html
https://blog.csdn.net/jxw167/article/details/82455746
https://blog.csdn.net/jxw167/article/details/81869949
https://blog.csdn.net/jxw167/article/details/81483685
https://zhuanlan.zhihu.com/p/26884671
https://blog.csdn.net/qq_14914623/article/details/83789812

https://github.com/AsehesL/SceneSeparateDemo 代码
https://wenku.baidu.com/view/9706e8e59b89680203d825a4.html
https://www.cnblogs.com/AZ-ZK/p/4219981.html
http://www.lsngo.net/2018/01/20/unity_quadtreescenemanage/

本文代码所有权属于:http://www.lsngo.net/2018/01/20/unity_quadtreescenemanage/
感谢大牛。

下面是我自己的读取代码的笔记:
树节点的定义:
数据+孩子节点
数据:就是当前节点包含的区域
孩子节点,可以使用链表或者是数组。

树节点的构造函数:

	public SceneTreeNode(Bounds bounds, int depth, int childCount)
	{
		m_Bounds = bounds; //数据
		m_CurrentDepth = depth; //当前节点的深度
		m_ObjectList = new LinkedList<T>(); //如果孩子使用的是链表则是用LinkedList
		m_ChildNodes = new SceneTreeNode<T>[childCount]; //如果孩子使用的是数组

		if (childCount == 8) //八叉树,后面会分析到
			m_HalfSize = new Vector3(m_Bounds.size.x / 2, m_Bounds.size.y / 2, m_Bounds.size.z / 2);
		else
			m_HalfSize = new Vector3(m_Bounds.size.x / 2, m_Bounds.size.y, m_Bounds.size.z / 2); //四叉树,xz平面取bound中心点

		m_ChildCount = childCount; //孩子的个数
	}

树节点的clear函数:

	public void Clear()
	{
		for (int i = 0; i < m_ChildNodes.Length; i++) //遍历所有孩子节点,然后进行清空
		{
			if (m_ChildNodes[i] != null)
				m_ChildNodes[i].Clear();
		}
		if (m_ObjectList != null) //如果使用链表,则清空链表即可
			m_ObjectList.Clear();
	}

树节点的查找函数:

	public bool Contains(T obj)
	{
		for (int i = 0; i < m_ChildNodes.Length; i++)//遍历所有孩子是否包含给定的节点,如果包含返回true
		{
			if (m_ChildNodes[i] != null && m_ChildNodes[i].Contains(obj))
				return true;
		}

		if (m_ObjectList != null && m_ObjectList.Contains(obj))//同上
			return true;
		return false;
	}

树节点的插入:

	public SceneTreeNode<T> Insert(T obj, int depth, int maxDepth)
	{
		if (m_ObjectList.Contains(obj)) //如果链表中包含了obj,则直接返回即可
			return this;
		if (depth < maxDepth) //如果插入节点的深度小于最大深度
		{
			SceneTreeNode<T> node = GetContainerNode(obj, depth); //转入下面的分析
			if (node != null)
				return node.Insert(obj, depth + 1, maxDepth);
		}
		var n = m_ObjectList.AddFirst(obj);
		obj.SetLinkedListNode(0, n);
		return this;
	}

树节点的GetContainerNode方法:

protected SceneTreeNode<T> GetContainerNode(T obj, int depth)
	{
		SceneTreeNode<T> result = null;
		int ix = -1;
		int iz = -1;
		int iy = m_ChildNodes.Length == 4 ? 0 : -1; //如果是四叉树,则iy=0

		int nodeIndex = 0;

		for (int i = ix; i <= 1; i += 2)
		{
			for (int k = iy; k <= 1; k += 2)
			{
				for (int j = iz; j <= 1; j += 2)
				{
					result = CreateNode(ref m_ChildNodes[nodeIndex], depth,
						m_Bounds.center + new Vector3(i * m_HalfSize.x * 0.5f, k * m_HalfSize.y * 0.5f, j * m_HalfSize.z * 0.5f),
						m_HalfSize, obj);
					if (result != null)
					{
						return result;
					}

					nodeIndex += 1; //累加索引,看看四个节点中的位置是哪个?
				}
			}
		}
		return null;
	}

从上面的那段代码,我们应该明白作者的意图是什么?可以使用下图展示出来:
在这里插入图片描述
注释:
正如上图所示,中心小白点,表示当前树的节点,其bounds为蓝、粉、浅蓝、红色四个矩形框。
然后分别将其bounds划分为四个小矩形框,作为其子节点。
从上图可以看出,根节点的bounds是所有子节点的bounds的和。

创建节点的函数:CreateNode

protected SceneTreeNode<T> CreateNode(ref SceneTreeNode<T> node, int depth, Vector3 centerPos, Vector3 size, T obj)
	{
		SceneTreeNode<T> result = null;

		if (node == null) //如果当前根节点为null,则进行创建
		{
			Bounds bounds = new Bounds(centerPos, size);
			if (bounds.IsBoundsContainsAnotherBounds(obj.Bounds))//如果这个节点的bounds能够圈住obj的bounds则创建这个节点
			{
				SceneTreeNode<T> newNode = new SceneTreeNode<T>(bounds, depth + 1, m_ChildNodes.Length);
				node = newNode;
				result = node;
			}
		}
		else if (node.Bounds.IsBoundsContainsAnotherBounds(obj.Bounds)) //如果已经创建并且当前节点的bounds能圈住obj的bounds则直接返回
		{
			result = node;
		}
		return result;
	}

节点创建函数的总结:如果当前节点就已经是包含了这个bounds,那么直接返回,否则要去创建一个新的node,而两者的检测条件都是是否当前节点的bounds包含了obj的bounds。

ok,到这里我相信所有的人都会懵了,到底是如何构建四叉树的呢?下面我们通过实例的数据和图的形式展示下源代码的构建过程。

首先以6个数据进行构建,并且树的高度最大为3,6个数据足以说明四叉树的构建过程了。
哪6个数据,在unity中找到:
在这里插入图片描述
6个element的bounds,就是我们要构建的数据。

首先确定根节点,根节点应该包含了整个场景的大小,也就是bounds为:
在这里插入图片描述

如上图所示,地形的俯视图。
在这里插入图片描述

6个节点的数据表如下:
在这里插入图片描述

构建图如下:
在这里插入图片描述
解释下上图:
可以看到节点的颜色有红色,有黑色,红色表示这个节点有数据,黑色表示这个节点为null。
因为是四叉树,所以节点最多为四个。
对于最终element落在哪个树节点,遵循下面的流程,如果没有达到树的最大高度则继续往下插入,直到不能插入为止。
对于某个树节点的bounds不能在细分的情况下,则说明只能在本节点进行存储了。
比如下面:
在这里插入图片描述
这两个bounds是不能继续往下分了,所以就存储在当前的节点数据中。
以上为四叉树为高度为3的情况的构建过程。

也就是说,我们的大地形是一开始全部加载出来的,而每个块的物件(静态物件)是一个个添加然后构建树的节点的。

定时刷新,检测是否有新的物体需要加载,老的物体需要预删除。

 public void RefreshDetector(IDetector detector)
    {
        if (!m_IsInitialized)
            return;
        //只有坐标发生改变才调用
        if (m_OldRefreshPosition != detector.Position)
        {
            m_RefreshTime += Time.deltaTime;
            //达到刷新时间才刷新,避免区域更新频繁
            if (m_RefreshTime > m_MaxRefreshTime)
            {
                m_OldRefreshPosition = detector.Position;
                m_RefreshTime = 0;
                m_CurrentDetector = detector;
                //进行触发检测
                m_Tree.Trigger(detector, m_TriggerHandle);
                //标记超出区域的物体
                MarkOutofBoundsObjs();
                //m_IsInitLoadComplete = true;
            }
        }

什么是进行触发检测?

	public void Trigger(IDetector detector, TriggerHandle<T> handle)
	{
		if (handle == null)
			return;
		if (detector.IsDetected(Bounds) == false)
			return;
		m_Root.Trigger(detector, handle);
	}
if (detector.IsDetected(Bounds) == false)

干嘛的?

    public override bool IsDetected(Bounds bounds)
    {
        RefreshBounds();
        return bounds.Intersects(m_Bounds);
    }
    protected override void RefreshBounds()
    {
        m_Bounds.center = Position + m_PosOffset;
        m_Bounds.size = detectorSize + m_SizeEx;
    }

计算当前位置所属的象限

public override int GetDetectedCode(float x, float y, float z, bool ignoreY)
	{
		RefreshBounds();
		int code = 0;
		if (ignoreY)
		{
			float minx = m_Bounds.min.x;
			float minz = m_Bounds.min.z;
			float maxx = m_Bounds.max.x;
			float maxz = m_Bounds.max.z;
			if (minx <= x && minz <= z)
				code |= 1;
			if (minx <= x && maxz >= z)
				code |= 2;
			if (maxx >= x && minz <= z)
				code |= 4;
			if (maxx >= x && maxz >= z)
				code |= 8;
		}
    void TriggerHandle(SceneObject data)
    {
        if (data == null)
            return;
        if (data.Flag == SceneObject.CreateFlag.Old) //如果发生触发的物体已经被创建则标记为新物体,以确保不会被删掉
        {
            data.Weight++;
            data.Flag = SceneObject.CreateFlag.New;
        }
        else if (data.Flag == SceneObject.CreateFlag.OutofBounds)//如果发生触发的物体已经被标记为超出区域,则从待删除列表移除该物体,并标记为新物体
        {
            data.Flag = SceneObject.CreateFlag.New;
            //if (m_PreDestroyObjectList.Remove(data))
            {
                m_LoadedObjectList.Add(data);
            }
        }
        else if (data.Flag == SceneObject.CreateFlag.None) //如果发生触发的物体未创建则创建该物体并加入已加载的物体列表
        {
            DoCreateInternal(data);
        }
    }
    //执行创建物体
    private void DoCreateInternal(SceneObject data)
    {
        //加入已加载列表
        m_LoadedObjectList.Add(data);
        //创建物体
        CreateObject(data, m_Asyn);
    }
 /// <summary>
    /// 标记离开视野的物体
    /// </summary>
    void MarkOutofBoundsObjs()
    {
        if (m_LoadedObjectList == null)
            return;
        int i = 0;
        while (i < m_LoadedObjectList.Count)
        {
            if (m_LoadedObjectList[i].Flag == SceneObject.CreateFlag.Old)//已加载物体标记仍然为Old,说明该物体没有进入触发区域,即该物体在区域外
            {
                m_LoadedObjectList[i].Flag = SceneObject.CreateFlag.OutofBounds;
                //m_PreDestroyObjectList.Add(m_LoadedObjectList[i]);
                if (m_MinCreateCount == 0)//如果最小创建数为0直接删除
                {
                    DestroyObject(m_LoadedObjectList[i], m_Asyn);
                }
                else
                {
                    //m_PreDestroyObjectQueue.Enqueue(m_LoadedObjectList[i]);
                    m_PreDestroyObjectQueue.Push(m_LoadedObjectList[i]);//加入待删除队列
                }
                m_LoadedObjectList.RemoveAt(i);

            }
            else
            {
                m_LoadedObjectList[i].Flag = SceneObject.CreateFlag.Old;//其它物体标记为旧
                i++;
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值