#分帧加载和分块加载
在我们实际做项目的时候,往往会遇见需要创建大量数据的时候,这时如果在一帧里面大量创建数据,那我们的游戏就会发生卡顿从而降低了用户的体验。
为了解决这种情况,可以使用使用分帧加载使得每帧只加载固定数量的数据来解决,也可以使用分块加载来实现加载当前场景所需的数据。
我们得到的信息是来自于服务器的,当我们需要显示信息的时候,不可能从服务器拿了一条显示一条信息。我们应该定义一个数组先把所有的信息进行保存,通过遍历来显示信息。
public class fpsload : MonoBehaviour
{
List InfoList;
int InfoListIndex = 0;
void start(){
StratCoroutine(GetFromInfoList() );
}
IEnumerator GetFromInfoList()
{
foreach(string item in InfoList)
{
InfoListIndex++;
if(InfoListIndex % 5 == 0) //每一帧提取5条数据
Debug.Log("Load");
yield return null; //在下一帧执行
}
}
}
假设设计一个场景Scene把它分为两个部分LoadScene1和LoadScene2。
//假设当前离开场景LoadScene1
public class blockload : MonoBehaviour
{
void start(){
}
IEnumerator LoadScene2()
{
//通过SceneManager.LoadSceneAsync实现分块加载不同场景,5.x以前为Application.LoadLevelAdditiveAsync
AsyncOperation async = SceneManager.LoadSceneAsyn("LoadScene2", LoadSceneMode.Additive);
}
}
首先分割地图的分块加载的核心思想是:将整个大地形,分割为 n x n 的正方形小块chunk(我们在接下来的内容里,把这些小块统称为chunk)。在这里呢,我们为了效率,就使用一个免费开源的分割地形的小工具Terrain Slicing,具体使用大致如下图:
具体的分块加载思路
这里我们首先把地形分割成16x16的形式(具体你的地形多大,可根据地形具体大小分割成合理的数量),后续我们会用一个chunk对象去管理每一个chunk实体,用具有键值对的数据结构(例如字典)把chunk对象按照chunk所处的行列位置去保存起来(这里我们就以下图这种排列方式去保存,键为位置ChunkVector2,值为Chunk对象),这样方便我们后面对chunk对象的获取以及对chunk对象的操作(例如chunk对象中chunk实体的加载、卸载、缓存)。
public class Chunk
{
///
/// 在块列表中所处的位置
///
ChunkVector2 m_position;
///
/// 块的实体
///
GameObject m_body;
///
/// 块的资源路径
///
string m_resPath;
///
/// 块当前的状态
///
ChunkState m_currentState = ChunkState.UnLoad;
///
/// 创建一个块对象
///
/// 在块列表中的第几行
/// 在块列表中的第几列
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(){}; --块卸载
///
/// 更新自身状态
///
///
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;
}
}
}
图片中绿色区域的9个chunk代表展示在场景中的地形,而玩家则处于区域中心位置为A的chunk,红色区域中的25个chunk代表缓存区域,这个区域会根据玩家的远离被卸载掉,也可能因玩家的靠近则呈现出来。
玩家从chunk A移动到chunk E时,红色区域标记为U区域的chunk被卸载(unload)、标记为S的chunk被展示(show),蓝色区域标记为L的chunk则被加载(load),绿色区域标记为H的chunk被隐藏(hide)。
首先根据玩家位置获取玩家所在chunk的位置,这里chunk的位置指的是在整个地图的第几行第几列。
///
/// 获取块坐标
///
/// 玩家的具体vector3位置
///
ChunkVector2 GetCurrentChunkVector(Vector3 position)
{
int col = (int)(position.x / m_chunkLength); --取整
int row = (int)(position.z / m_chunkLength);
return new ChunkVector2(row, col);
}
然后通过当前的chunk位置来获得周围其他的chunk,并把这些chunk加入列表,此时我们就获得了实际要操作但状态未更新的chunk列表。
///
/// 获取实际块列表
///
/// 当前中心块位置
///
List GetActualChunkList(ChunkVector2 currentVector)
{
List expectChunkPosList = new List(); //expec为实际要操作chunk列表
//当前中心点的行列
int currentRow = currentVector.rowNum;
int currentCol = currentVector.colNum;
//-2——>2,实际要操作区域,即绿红组成的区域
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;
}
然后将实际的chunk列表与当前的chunk列表做对比并更新当前的chunk列表,在更新当前列表的过程中,则对相应的chunk做相应的处理,最终使得当前chunk列表与实际chunk列表保持一致。
///
/// 对比当前块列表与实际块列表,并更新当前块列表
///
/// 实际块列表
/// 当前中心块位置
private void UpdateCurrentChunkList(List 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); //移除实际块列表和当前块列表中相同的块元素,注:移除完毕后,实际块列表中的元素
ChunkState actualState = GetChunkStateByRelativePosition(pos, currentPos); //先获取chunk的实际状态,GetChunkStateByRelativePosition()作用是获取实际状态,在后面定义
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();
}
获得实际chunk的位置列表后,通过以当前chunk位置为参照位置,判读出周围chunk对象应该属于具体哪种状态。
///
/// 获取指定块的相对状态
///
/// 指定块坐标
/// 参照块坐标
/// 相对块状态
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; //如果不在处理距离内,说明距离参照块很远
}
至此就初步完成指定chunk的加载、卸载、缓存。
参考:https://blog.csdn.net/weixin_33835690/article/details/87446398
参考:https://blog.csdn.net/qq_30825027/article/details/89324097