目录
多物体的异步生成:
使用协程来生成大量的物体可以避免主线程阻塞,从而保证游戏的流畅性。如果直接生成物体,可能会因为生成大量物体导致游戏卡顿、卡死等问题。协程可以将物体的生成放在后台线程中处理,等到物体生成完毕后再将其添加到游戏中。这样可以让主线程继续执行,提高游戏的响应速度。
协程、线程和进程的区别:
-
进程(Process): 进程是操作系统分配资源的最小单位。一个进程包含了程序的代码、数据和执行状态。每个进程都有独立的内存空间和系统资源,如文件句柄、网络连接等。进程之间相互独立,彼此隔离,各自运行在自己的地址空间中。进程之间通信需要使用特定的机制,如管道、共享内存等。每个进程都有自己的上下文切换开销,因此进程间切换的代价相对较高。
-
线程(Thread): 线程是进程内的执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,它们共享进程的内存空间和系统资源。多个线程之间可以并发执行,通过共享数据来进行通信。线程间的切换相对于进程来说代价较低,因为线程共享相同的地址空间,切换时不需要切换上下文。
-
协程(Coroutine): 协程是一种轻量级的线程,也被称为用户态线程。它是一种特殊的函数或代码块,可以在特定的时机暂停执行,并在需要时恢复执行。与线程相比,协程的切换不需要操作系统介入,切换的开销非常小。协程通常运行在单个线程中,利用协程调度器来管理协程的执行顺序。协程可以有效地处理大量的并发任务,适用于IO密集型操作和高并发的网络编程。
总结: 进程是操作系统分配资源的最小单位,具有独立的内存空间和系统资源。线程是进程内的执行单元,共享进程的资源,切换开销相对较低。协程是一种特殊的函数或代码块,可以在特定的时机暂停和恢复执行,切换开销非常小。线程和进程适用于CPU密集型任务,而协程适用于IO密集型任务和高并发的网络编程。
进程之间的共享资源的方式
1.消息队列
2.共享内存
3.管道(有名管道。无名管道)
4.信号
5.套接字
同一个进程的不同线程之间可以共享的资源
1.堆,由于堆是在进程启动的时候开辟的空间,因此由进程new出来的不同的线程会共享堆空间(16位平台上分全局堆和局部堆,局部堆是独享的)
2.全局变量,全局变量不受具体的函数以及不受具体的线程所拥有,所以全局变量属于共享资源
3.静态变量,在内存中存放在静态存储区,其地位与全局变量是等同的,在堆中开辟,因此其也属于共享资源
4.文件资源,文件资源由系统管理,因此在多线程之间是共享的,但是对于写操作,需要进行不同线程之间的同步,用到的方法包括信号、临界区、事件和互斥,
5.栈;不是线程之间的共享,每个线程有自己独立的栈空间
线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
Resources
资源的异步加载
把实例化物体的操作放到协程中,同时通过 Resources
资源的异步加载来实例化物体。
private IEnumerator LoadGameObjects(GameObjectData gameObjectData)
{
SceneGameObjectData sceneGameObjectData = new SceneGameObjectData(gameObjectData);
sceneGameObjectData.status = SceneGameObjectsStatus.Loading;
activeSceneGameObjectDatas.Add(gameObjectData.uid, sceneGameObjectData);
GameObject resourceObj = null;
ResourceRequest request = Resources.LoadAsync<GameObject>(gameObjectData.resPath);
yield return request;
resourceObj = request.asset as GameObject;
yield return new WaitUntil(() => resourceObj != null);
sceneGameObjectData.status = SceneGameObjectsStatus.Loaded;
SetGameObjectTransform(resourceObj, sceneGameObjectData);
}
第一个yield return等待资源目录的异步加载结束,第二个yield return
WaitUntil(Func<bool> predicate) 接受一个返回布尔值的委托作为参数,该委托用于判断协程是否应该继续执行。只有在委托返回 true 时,协程才会继续执行,否则会一直暂停等待。
包围盒可见性检测
检测包围盒的八个顶点,通过位计算来记录每个顶点是否在视锥空间内,有一个点不在视锥空间内,就视为整个Bound不在视锥空间内。
首先需要将世界空间坐标转换为相机空间坐标,再转换为投影空间下的坐标
camera.projectionMatrix * camera.worldToCameraMatrix * worldPos
之后我们要判断转换后的坐标是否落在视锥的区域内
private static int ComputeOutCode(Vector4 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;
}
物体的剔除
在视锥空间内就通过异步生成物体,不在就删除物体;写起来很简单,但是频繁的删除以及生成物体会造成性能的浪费。
因此有两个思路,一个是先生成全部物体,之后用摄像机的裁剪空间通过Layer来进行渲染剔除,只渲染摄像机镜头中的物体;
第二个是使用对象池技术,对象不在视锥空间内就先取消激活不销毁,放入对象池中,等又需要了就再次激活并且重新设置位置。
四叉树的构建
每个节点都要记录当前深度,所属树,该节点的四个子节点,该节点存储的所有物体信息队列,以及该节点是否在相机视锥内。
public class TreeNode
{
public Bounds bound { get; set; }
private int depth;
private Tree belongTree;
private TreeNode[] childList;
//每个节点用队列来存储物体的信息
private List<GameObjectData> objDataList;
private bool isInside = false;
private int leftIndex;
private int rightIndex;
public TreeNode(Bounds bound, int depth, Tree belongTree)
{
this.belongTree = belongTree;
this.bound = bound;
this.depth = depth;
objDataList = new List<GameObjectData>();
}
}
场景中物体信息的存储;
可以在节点中用队列来记录当前所记录的物体信息;
同时物体在划分时可能出现占据两个划分区块的情况,比如上图的12,13,14,16,16,17,18,21,23,出现这种情况的话,就把物体信息存储在父节点中。
继续划分的条件
用四叉树或者八叉树进行划分是,当节点到达一定的要求后就可以不必再细分,一些常见的要求如下:
- 1.节点内物体或三角形数目已经很少,进一步剖分没有意义。
- 2.子节点足够小,不能再剖分。当然,没有理由不能将小节点细分为更小的节点,我们只是防止节点小于一定的尺寸。
- 3.达到树的最大深度。
-
public void InsertObjData(GameObjectData objData) { TreeNode node = null; bool bChild = false; if (depth < belongTree.maxDepth && childList == null) { CreateChild(); } if (childList != null) { for (int i = 0; i < childList.Length; ++i) { TreeNode item = childList[i]; if (item.bound.Intersects(objData.GetBounds())) { if (node != null) { bChild = false; break; } node = item; bChild = true; } } } //物体只占用一个空间划分区块时则放入子节点里 if (bChild) { node.InsertObjData(objData); } //物体占用多个空间划分区块放入父节点 //达到最大深度 else { objDataList.Add(objData); } }
- github链接 https://github.com/DonnQuixote/DivideSpaceDemo/tree/master
Unity 协程(Coroutine)原理与用法详解https://blog.csdn.net/xinzhilinger/article/details/116240688