关于UnityMMO的启动流程和登录流程的,作者大鹏已经非常详细地写出来了,这里是大鹏写的启动流程和大鹏写的登录流程,感兴趣的朋友可以先了解一下,我们将深入这个项目进行学习。
原本今天计划要讲启动流程的,可是发现已经没有什么可以说的了,大鹏已经写得非常详细了,于是就补充说一下官方的案例MegaCity好了,因为我才找到MegaCity的源码,原来官方早就开源了,只是资源非常大,所以没有放在Github上!
0下载Unity编辑器(Unity 2019.1.0 Beta 7 or 更新的版本),if(已经下载了)continue;
1点击Megacity源代码下载Zip压缩包;if(已经下载了)continue;
2这个包有7.11G,解压后17.6 GB,打开Unity Hub->项目->添加,把MegaCity_GDC2019_Release_OC添加到项目中;
3用Unity Hub打开官方开源项目:MegaCity_GDC2019_Release_OC,等待Unity进行编译工作;
4光编译工作就用了两个多小时,打开Scenes/Megacity场景后,整个电脑都变得卡顿起来,这是接触过的最宏大的场景。
在Megacity的场景中,我们可以看到大量的SubScene,如下图所示:
在之前的学习笔记中,我们已经研究过SubScene了,详细参考基于Unity2019最新ECS架构开发MMO游戏笔记3。
总结一下SubScene的特性好了:
SubScene的加载和卸载是由一套系统控制,否则数以万计的实体全部加载太浪费性能了,毕竟大部分实体其实都不在视野范围内,所以SubScene是采用这套动态加载系统来控制的,我们先看看这套系统的逻辑吧,先从StreamingLogicConfigComponent开始,这里取名为StreamingLogicConfigComponent 令我感到困惑:
///
/// E:流加载逻辑实体
///
public class StreamingLogicConfigComponent : MonoBehaviour, IConvertGameObjectToEntity
{
///
/// 实例化配置组件
///
public StreamingLogicConfig Config = new StreamingLogicConfig
{
DistanceForStreamingIn = 600,//进场距离,达到该距离的SubScene流将被快速加载
DistanceForStreamingOut = 800//离场距离
};
void OnDrawGizmosSelected()
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, Config.DistanceForStreamingIn);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, Config.DistanceForStreamingOut);
}
///
/// 转化时将组件以及数据添加到实体上
///
/// 实体
/// 目标实体管理器
/// 转化系统
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, Config);
}
}
我之所以认定StreamingLogicConfigComponent 是实体E,是因为它实现了IConvertGameObjectToEntity接口。
下面是最简单的C部分StreamingLogicConfig:
[Serializable]
public struct StreamingLogicConfig : IComponentData
{
public float DistanceForStreamingIn;
public float DistanceForStreamingOut;
}
没啥好说的,看StreamingLogicSystem吧:
///
/// 加载流逻辑系统
///
[UpdateInGroup(typeof(InitializationSystemGroup))]//初始化系统组
[ExecuteAlways]//总是执行
public class StreamingLogicSystem : JobComponentSystem
{
///
/// 实体命令缓存系统
///
EntityCommandBufferSystem m_EntityCommandBufferSystem;
NativeList<Entity> m_AddRequestList;//添加请求的原生列表
NativeList<Entity> m_RemoveRequestList;//移除请求的原生列表
///
/// 子场景进场
///
[BurstCompile]
[ExcludeComponent(typeof(RequestSceneLoaded))]
struct StreamSubScenesIn : IJobProcessComponentDataWithEntity<SceneData>
{
public NativeList<Entity> AddRequestList;//添加请求列表
public float3 CameraPosition;//摄像机位置
public float MaxDistanceSquared;//最大距离方体
///
/// 执行:小于最大距离则将实体加入请求列表
///
///
///
///
public void Execute(Entity entity, int index, [ReadOnly]ref SceneData sceneData)
{
var distanceSq = sceneData.BoundingVolume.DistanceSq(CameraPosition);
if (distanceSq < MaxDistanceSquared)
AddRequestList.Add(entity);
}
}
///
/// 子场景出场
///
[BurstCompile]
[RequireComponentTag(typeof(RequestSceneLoaded))]
struct StreamSubScenesOut : IJobProcessComponentDataWithEntity<SceneData>
{
public NativeList<Entity> RemoveRequestList;
public float3 CameraPosition;
public float MaxDistanceSquared;
///
/// 执行:大于最大距离则将实体加入移除列表
///
///
///
///
public void Execute(Entity entity, int index, [ReadOnly]ref SceneData sceneData)
{
if (sceneData.SubSectionIndex == 0)
return;
var distanceSq = sceneData.BoundingVolume.DistanceSq(CameraPosition);
if (distanceSq > MaxDistanceSquared)
RemoveRequestList.Add(entity);
}
}
///
/// 构建命令缓存任务
///
struct BuildCommandBufferJob : IJob
{
///
/// 实体命令缓存
///
public EntityCommandBuffer CommandBuffer;
public NativeArray<Entity> AddRequestArray;
public NativeArray<Entity> RemoveRequestArray;
///
/// 执行:遍历添加列表和移除列表
/// 通过对列表中的实体添加和移除组件来标记子场景是否加载
/// 后面进行管理的时候会利用该组件来进行刷选
///
public void Execute()
{
foreach (var entity in AddRequestArray)
{
CommandBuffer.AddComponent(entity, default(RequestSceneLoaded));
}
foreach (var entity in RemoveRequestArray)
{
CommandBuffer.RemoveComponent<RequestSceneLoaded>(entity);
}
}
}
///
/// 在创建管理器的时候对实体命令缓存系统进行获取
/// 这里相当于初始化工作
///
protected override void OnCreateManager()
{
m_EntityCommandBufferSystem = World.GetOrCreateManager<EndPresentationEntityCommandBufferSystem>();
m_AddRequestList = new NativeList<Entity>(Allocator.Persistent);
m_RemoveRequestList = new NativeList<Entity>(Allocator.Persistent);
RequireSingletonForUpdate<StreamingLogicConfig>();
}
///
/// 在摧毁管理器时释放内存
///
protected override void OnDestroyManager()
{
m_AddRequestList.Dispose();
m_RemoveRequestList.Dispose();
}
///
/// 每帧更新
///
///
///
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
//这是个单例实体,没看出有什么特别的
var configEntity = GetSingletonEntity<StreamingLogicConfig>();
//缓存配置,方便下面使用
var config = EntityManager.GetComponentData<StreamingLogicConfig>(configEntity);
//该组件就挂在摄像机上,所以通过Position来获取其位置
var cameraPosition = EntityManager.GetComponentData<LocalToWorld>(configEntity).Position;
m_AddRequestList.Clear();//每次更新先清空
var streamInHandle = new StreamSubScenesIn
{
AddRequestList = m_AddRequestList,
CameraPosition = cameraPosition,
MaxDistanceSquared = config.DistanceForStreamingIn * config.DistanceForStreamingIn
}.ScheduleSingle(this, inputDeps);//预约
m_RemoveRequestList.Clear();
var streamOutHandle = new StreamSubScenesOut
{
RemoveRequestList = m_RemoveRequestList,
CameraPosition = cameraPosition,
MaxDistanceSquared = config.DistanceForStreamingOut * config.DistanceForStreamingOut
}.ScheduleSingle(this, inputDeps);
var combinedHandle = JobHandle.CombineDependencies(streamInHandle, streamOutHandle);
var commandHandle = new BuildCommandBufferJob
{
CommandBuffer = m_EntityCommandBufferSystem.CreateCommandBuffer(),
AddRequestArray = m_AddRequestList.AsDeferredJobArray(),
RemoveRequestArray = m_RemoveRequestList.AsDeferredJobArray()
}.Schedule(combinedHandle);
m_EntityCommandBufferSystem.AddJobHandleForProducer(commandHandle);
return commandHandle;
}
}
逻辑很简单,进入一定视野范围则加载,超出视野则移除。
今天外出参加了两场宴席,晚上回来又是中元节祭祖,所以写了非常少,明天多补几篇吧!
如果喜欢我的文章可以点赞支持一下,谢谢鼓励!如果有什么疑问可以给我留言,有错漏的地方请批评指证!
如果有技术难题需要讨论,可以加入开发者联盟:566189328(付费群)为您提供有限的技术支持,以及,心灵鸡汤!
当然,不需要技术支持也欢迎加入进来,随时可以请我喝咖啡、茶和果汁!( ̄┰ ̄*)