当前位置:   article > 正文

Unity大地型分块加载的初步实现_unity 大地图的分块加载

unity 大地图的分块加载

为什么地形要分块加载

要谈到地形为什么要分块加载,很明显,是为了实现对性能上的提升,如果说场景里的地形非常大,加上里面的各种模型、贴图、碰撞、渲染等等 这将是一笔很庞大的cpu、gpu、内存开销,所以我们需要对地形进行分割成多个地形块,再进行合理的加载卸载来达到性能开销上的平衡。

具体效果

话不多说,我们先来上图,来看看具体的效果

实际运行效果
虽然看起来简陋一些,但是基本的加载地形功能是初步完成了。


分割地形

首先分块加载的核心思想是:将整个大地形,分割为 n x n 的正方形小块chunk(我们在接下来的内容里,把这些小块统称为chunk)。在这里呢,我们为了效率,就使用一个免费开源的分割地形的小工具叫做Terrain Slicing,如果大家有更多的需求,可以尝试对这个小工具的源码进行优化和扩展或者重写,这里具体如何用代码去分割地形就不再深究了,具体使用大致如下图:
在这里插入图片描述


具体的分块加载思路

这里我们首先把地形分割成16x16的形式(具体你的地形多大,可根据地形具体大小分割成合理的数量),后续我们会用一个chunk对象去管理每一个chunk实体,用具有键值对的数据结构(例如字典)把chunk对象按照chunk所处的行列位置去保存起来(这里我们就以下图这种排列方式去保存,键为位置ChunkVector2,值为Chunk对象),这样方便我们后面对chunk对象的获取以及对chunk对象的操作(例如chunk对象中chunk实体的加载、卸载、缓存)。

public class Chunk
{
    /// <summary>
    /// 在块列表中所处的位置
    /// </summary>
    ChunkVector2 m_position;

    /// <summary>
    /// 块的实体
    /// </summary>
    GameObject m_body;

    /// <summary>
    /// 块的资源路径
    /// </summary>
    string m_resPath;

    /// <summary>
    /// 块当前的状态
    /// </summary>
    ChunkState m_currentState = ChunkState.UnLoad;


    /// <summary>
    /// 创建一个块对象
    /// </summary>
    /// <param name="rowNum">在块列表中的第几行</param>
    /// <param name="colNum">在块列表中的第几列</param>
    public Chunk(int rowNum, int colNum)
    {
        m_position = new ChunkVector2(rowNum, colNum);
        m_resPath = string.Format("TerrainPrefab/Terrain_Slice_{0}_{1}", (rowNum + 1), (colNum + 1));
    }
    public Chunk(ChunkVector2 position)
        : this(position.rowNum, position.colNum)
    {
    }

    public void Display() {};
    public void Cache(){};
    public void Unload(){};
    
    /// <summary>
    /// 更新自身状态
    /// </summary>
    /// <param name="state"></param>
    public void Update(ChunkState state)
    {
        if (m_currentState == state)
        {
            Debug.LogErrorFormat(" {0} is already {1} ", m_position, m_currentState);
            return;
        }
        switch (state)
        {
            case ChunkState.Display:
                Display();
                break;
            case ChunkState.Cache:
                Cache();
                break;
            case ChunkState.UnLoad:
                Unload();
                break;

        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

在这里插入图片描述
图片取自于https://blog.csdn.net/jxw167/article/details/81483685这里
我的思路主要来源于这个图片,图片中天蓝色区域的9个chunk代表展示在场景中的地形,而玩家则处于区域中心位置为A的chunk,红色区域中的25个chunk代表缓存区域,这个区域会根据玩家的远离被卸载掉,也可能因玩家的靠近则呈现出来,就比如

在这里插入图片描述
从玩家从chunk A移动到chunk E的时候,红色区域标记为U区域的chunk被卸载(unload),蓝色区域标记为L区域的chunk则被加载(load),标记为H区域的chunk被隐藏(hide),标记为S区域的chunk被展示出来(show)


首先根据玩家位置获取玩家所在chunk的位置,这里chunk的位置指的是在整个地图的第几行第几列

    /// <summary>
    /// 获取块坐标
    /// </summary>
    /// <param name="position">玩家的具体vector3位置</param>
    /// <returns></returns>
    ChunkVector2 GetCurrentChunkVector(Vector3 position)
    {
        int col = (int)(position.x / m_chunkLength);
        int row = (int)(position.z / m_chunkLength);

        return new ChunkVector2(row, col);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

然后通过当前的chunk位置来获得周围其他的chunk,并把这些chunk加入列表,此时我们就获得了实际要操作的chunk列表。

    /// <summary>
    /// 获取实际块列表
    /// </summary>
    /// <param name="currentVector">当前中心块位置</param>
    /// <returns></returns>
    List<ChunkVector2> GetActualChunkList(ChunkVector2 currentVector)
    {
        List<ChunkVector2> expectChunkPosList = new List<ChunkVector2>();
        int currentRow = currentVector.rowNum;
        int currentCol = currentVector.colNum;

        for (int i = -2; i <= 2; i++)
        {
            for (int j = -2; j <= 2; j++)
            {
                int expRow = currentRow + i;
                int expCol = currentCol + j;
                if (expRow < 0 || expCol < 0 || expRow > m_row-1 || expCol > m_col-1)
                    continue;
                expectChunkPosList.Add(new ChunkVector2(expRow, expCol));
            }
        }
        return expectChunkPosList;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

然后将实际的chunk列表与当前的chunk列表做对比并去更新当前的chunk列表,在更新当前块列表的过程中,则对相应的chunk做相应的处理,最终使得当前chunk列表与实际chunk列表保持一致

 
    /// <summary>
    /// 对比当前块列表与实际块列表,并更新当前块列表
    /// </summary>
    /// <param name="actulChunkList">实际块列表</param>
    /// <param name="currentPos">当前中心块位置</param>
    private void UpdateCurrentChunkList(List<ChunkVector2> actulChunkList, ChunkVector2 currentPos)
    {
        for (int i = 0; i < m_currentChunkList.Count; i++)
        {
            ChunkVector2 pos = m_currentChunkList[i];
            Chunk chunk = m_chunkMap[pos];
            if (!actulChunkList.Contains(pos))//实际块列表里若不存在当前列表的指定元素 则卸载删除
            {
                chunk.Unload();//卸载不存在于实际块列表的块

                m_currentChunkList.RemoveAt(i);//移除当前块列表中不存在与实际块列表的块

                i--;//在遍历列表时删除列表元素 记得索引-1 否则无法正确遍历
            }
            else
            {
                actulChunkList.Remove(pos);//实际块列表移除和当前块列表中相同的元素 注:移除完毕后,实际块列表中的元素
                //先获取chunk的实际状态
                ChunkState actualState = GetChunkStateByRelativePosition(pos, currentPos);

                chunk.Update(actualState);
                
            }

        }

        for (int i = 0; i < actulChunkList.Count; i++)
        {
            ChunkVector2 pos = actulChunkList[i];
            Chunk chunk = m_chunkMap[pos];
            //先获取chunk的实际状态
            ChunkState actualState = GetChunkStateByRelativePosition(pos, currentPos);
            //使用实际状态去更新当前状态
            chunk.Update(actualState);

            m_currentChunkList.Add(pos);//这里添加完以后,当前块列表将与实际块列表保持一致

        }

        Resources.UnloadUnusedAssets();

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

获得实际chunk的位置列表后,通过以当前chunk位置为参照位置,判读出周围chunk对象应该属于具体哪种状态

    /// <summary>
    /// 获取指定块的相对状态
    /// </summary>
    /// <param name="specified">指定块</param>
    /// <param name="relativeVector">参照块坐标</param>
    /// <returns>相对块状态</returns>
    ChunkState GetChunkStateByRelativePosition(ChunkVector2 specified, ChunkVector2 relative)
    {
        int rowAmount = Mathf.Abs(specified.rowNum - relative.rowNum);
        int colAmount = Mathf.Abs(specified.colNum - relative.colNum);

        if (rowAmount > 2 || colAmount > 2)
        {
            return ChunkState.UnLoad;
        }
        if (rowAmount == 2 || colAmount == 2)
        {
            return ChunkState.Cache;
        }
        if (rowAmount <= 1 || colAmount <= 1)
        {
            return ChunkState.Display;
        }

        return ChunkState.UnLoad;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

至此就初步完成指定chunk的加载、卸载、缓存。

首先这是我第一次写技术博客,也希望这篇文章可以给你带来一些收获,如果有哪里写的不好,欢迎有经验的朋友可以给出一些建议和分享。
为了可以更好的让大家理解,我决定把我的代码分享给大家,
https://github.com/jiajiadelequ/Large-Terrain

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/97834
推荐阅读
相关标签
  

闽ICP备14008679号