Bootstrap

最浅显易懂的 Unity 八叉树场景管理(保姆级)

在这里插入图片描述

Unity 八叉树场景管理

在网友四叉树场景管理的基础上,升级到支持八叉树,感谢网游的开源精神,下面我贴上修改后的代码

八叉树场景管理

  1. 八叉树是一种树形数据结构,用于在三维空间中划分和管理场景。
  2. 每个八叉树节点表示一个空间范围,通常是一个立方体或长方体。
  3. 八叉树将空间递归地划分为八个子节点,直到达到最大深度或其他停止条件。
  4. 插入对象时,根据对象的位置和大小将其分配到适当的八叉树节点中。
  5. 八叉树常用于碰撞检测、视锥体剔除和场景渲染等应用。
  6. 通过遍历八叉树,可以高效地查找和处理与特定空间范围相关的对象。

Unity 中使用八叉树进行场景管理通常用于优化游戏的性能。八叉树是一种树状数据结构,可以将三维空间分割成八个等分的立方体,每个立方体可以继续进行递归分割,直到达到某种条件为止。在游戏中,我们可以使用八叉树来管理场景中的物体,以便更高效地进行碰撞检测、视图裁剪和其他空间相关的操作。八叉树常用于场景管理的性能优化。还需要配合如批处理、静态/动态合并、LOD等技术来提高游戏的性能。



    using UnityEngine;

    public class ObjData//数据集合
    {
        public string uid;//id标识
        public GameObject prefab;//预制体
        public Vector3 pos;//坐标点
        public Vector3 ang;//欧拉角度

        public ObjData(GameObject prefab, Vector3 pos,Vector3 ang)
        {
            this.uid = System.Guid.NewGuid().ToString();
            this.prefab = prefab;
            this.pos = pos;
            this.ang = ang;
        }
    }
using System.Collections.Generic;
using UnityEngine;

public class OctreeNode
{
	public Bounds bound; // 包围盒  
	public int myDepth; // 当前层数  
	public Octree tree;
	public List<ObjData> datas = new List<ObjData>(); // 集合列表  
	public OctreeNode[] childs;

	// 分割点  
	public Vector3[] bit = new Vector3[]
	{
		new Vector3(-1, 1, 1),
		new Vector3(1, 1, 1),
		new Vector3(-1, -1, 1),
		new Vector3(1, -1, 1),
		new Vector3(-1, 1, -1),
		new Vector3(1, 1, -1),
		new Vector3(-1, -1, -1),
		new Vector3(1, -1, -1)
	};

	public OctreeNode(Bounds bound, int myDepth, Octree tree)
	{
		this.bound = bound;
		this.myDepth = myDepth;
		this.tree = tree;
	}

	public void InstanceData(ObjData data)
	{
		if (myDepth < tree.maxDepth && childs == null) // 判定 小于树的最大深度 子集为空  
		{
			CreatChild(); // 创建 子树  
		}

		if (childs != null) // 判断子节点不为空  
		{
			for (int i = 0; i < childs.Length; i++) // 遍历所有子节点  
			{
				if (childs[i].bound.Contains(data.pos)) // 判断子节点的包围盒 是否在里面  
				{
					childs[i].InstanceData(data); // 循环创建放入下一级  
					break; // 跳出  
				}
			}
		}
		else // 判空就放入  
		{
			datas.Add(data); // 放入集合列表中 等待空的包围盒  
		}
	}

	private void CreatChild()
	{
		childs = new OctreeNode[tree.maxChildCount]; // 创建八叉树  
		for (int i = 0; i < tree.maxChildCount; i++) // 循环遍历  
		{
			// 计算分割点 包围盒中心点位置的点位坐标  
			Vector3 center = new Vector3(bit[i].x * bound.size.x / 4, bit[i].y * bound.size.y / 4, bit[i].z * bound.size.z / 4);
			// 计算盒体尺寸大小  size.x 为盒体的宽度 size.y 为盒体的高度 size.z 为盒体的深度  
			Vector3 size = new Vector3(bound.size.x / 2, bound.size.y / 2, bound.size.z / 2);
			// 创建包围盒 分割点+包围盒中心点 ,size该盒体的总大小  
			Bounds childbound = new Bounds(center + bound.center, size);
			// 创建节点 一个节点对应一个子树  
			childs[i] = new OctreeNode(childbound, myDepth + 1, tree);
		}
	}

	// 绘制盒  
	public void DrawBound()
	{
		if (datas.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 (childs != null) // 如果子节点不为空  
		{
			for (int i = 0; i < childs.Length; i++)
			{
				childs[i].DrawBound(); // 递归调用  
			}
		}
	}

	public void TriggerMove(Plane[] planes)//视锥体剔除
	{
		if (childs != null)//如果子节点不为空
		{
			for (int i = 0; i < childs.Length; i++)//便利所有子节点对象
			{
				childs[i].TriggerMove(planes);//递归下一节点计算
			}
		}

		for (int i = 0; i < datas.Count; i++)//便利所有集合列表
		{
			datas[i].prefab.SetActive(GeometryUtility.TestPlanesAABB(planes, bound));//控制对象预制体开关
		}
	}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Octree
{
    public Bounds bound; // 包围盒  
    private OctreeNode root; // 根节点  
    public int maxDepth = 6; // 最大深度层级  
    public int maxChildCount = 8; // 最大子节点数  

    public Octree(Bounds bound) // 创建八叉树  
    {
        this.bound = bound;
        this.root = new OctreeNode(bound, 0, this); // 创建根节点  
    }

    // 插入数据  
    public void Insert(ObjData data)
    {
        root.InstanceData(data); // 放到根节点  
    }

    public void DrawBound()
    {
        root.DrawBound(); // 根节点调用绘制盒  
    }

    public void TriggerMove(Plane[] planes)
    {
        root.TriggerMove(planes); // 根节点传递视锥体面  
    }
}

测试代码

using UnityEngine;
using Random = UnityEngine.Random;


public class CreatCube8 : MonoBehaviour
{
    public GameObject cube;//对象

    public Bounds mainBound;//包围盒
    private Octree tree;//树

    private bool startEnd = false;//控制结束
    public Camera cam;//相机
    private Plane[] _planes;//存储视锥体六个面

    void Start()
    {
        _planes = new Plane[6];//初始化
        Bounds bounds = new Bounds(transform.position, new Vector3(200, 200, 200));//生成包围盒
        tree = new Octree(bounds);//初始化行为树

        for (int x = -100; x < 100; x+=10)//随机生成对象放到树中
        {
            for (int z = -100; z < 100; z+=10)
            {
                for (int y = -100; y < 100; y+=10)
                {
                    if (Random.Range(0, 20) < 1)
                    {
                        GameObject c = Instantiate(cube, transform);
                        c.transform.localScale = new Vector3(Random.Range(5, 25), Random.Range(5, 25), Random.Range(5, 25));
                        c.transform.position = new Vector3(x, y, z);
                        c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);

                        tree.Insert(new ObjData(c, c.transform.position, c.transform.eulerAngles));
                    }
                }

            }
        }

        startEnd = true;

    }


    void Update()
    {
        if (startEnd)
        {
            //获取摄像机视锥体六个面 
            GeometryUtility.CalculateFrustumPlanes(cam, _planes);
            // 更新_planes的数值
            tree.TriggerMove(_planes);//传六个面
        }
    }

    private void OnDrawGizmos()
    {
        if (startEnd)
        {
            tree.DrawBound();//开始绘制包围盒 用树组绘制盒
        }
        else
        {
            //初始化盒体
            //使用 center 和 size 绘制一个线框盒体。
            //mainBound.center中心节点   盒体大小
            Gizmos.DrawWireCube(mainBound.center, mainBound.size);
        }
    }
}
;