MegaCity
關於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場景後,整個電腦都變得卡頓起來,這是接觸過的最宏大的場景。
ECS中SubScene的使用
在Megacity的場景中,我們可以看到大量的SubScene,如下圖所示:
在之前的學習筆記中,我們已經研究過SubScene了,詳細參考基於Unity2019最新ECS架構開發MMO遊戲筆記3。
總結一下SubScene的特性好了:
- 添加到SubScene的對象會變成實體,在運行的時候可快速加載。
- 可以在編輯器模式下隨時修改SubScene的資源。
- SubScene可以有效提高大場景的性能。
SubScene的加載和卸載是由一套系統控制,否則數以萬計的實體全部加載太浪費性能了,畢竟大部分實體其實都不在視野範圍內,所以SubScene是採用這套動態加載系統來控制的,我們先看看這套系統的邏輯吧,先從StreamingLogicConfigComponent開始,這裏取名爲StreamingLogicConfigComponent 令我感到困惑:
/// <summary>
/// E:流加載邏輯實體
/// </summary>
public class StreamingLogicConfigComponent : MonoBehaviour, IConvertGameObjectToEntity
{
/// <summary>
/// 實例化配置組件
/// </summary>
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);
}
/// <summary>
/// 轉化時將組件以及數據添加到實體上
/// </summary>
/// <param name="entity">實體</param>
/// <param name="dstManager">目標實體管理器</param>
/// <param name="conversionSystem">轉化系統</param>
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吧:
/// <summary>
/// 加載流邏輯系統
/// </summary>
[UpdateInGroup(typeof(InitializationSystemGroup))]//初始化系統組
[ExecuteAlways]//總是執行
public class StreamingLogicSystem : JobComponentSystem
{
/// <summary>
/// 實體命令緩存系統
/// </summary>
EntityCommandBufferSystem m_EntityCommandBufferSystem;
NativeList<Entity> m_AddRequestList;//添加請求的原生列表
NativeList<Entity> m_RemoveRequestList;//移除請求的原生列表
/// <summary>
/// 子場景進場
/// </summary>
[BurstCompile]
[ExcludeComponent(typeof(RequestSceneLoaded))]
struct StreamSubScenesIn : IJobProcessComponentDataWithEntity<SceneData>
{
public NativeList<Entity> AddRequestList;//添加請求列表
public float3 CameraPosition;//攝像機位置
public float MaxDistanceSquared;//最大距離方體
/// <summary>
/// 執行:小於最大距離則將實體加入請求列表
/// </summary>
/// <param name="entity"></param>
/// <param name="index"></param>
/// <param name="sceneData"></param>
public void Execute(Entity entity, int index, [ReadOnly]ref SceneData sceneData)
{
var distanceSq = sceneData.BoundingVolume.DistanceSq(CameraPosition);
if (distanceSq < MaxDistanceSquared)
AddRequestList.Add(entity);
}
}
/// <summary>
/// 子場景出場
/// </summary>
[BurstCompile]
[RequireComponentTag(typeof(RequestSceneLoaded))]
struct StreamSubScenesOut : IJobProcessComponentDataWithEntity<SceneData>
{
public NativeList<Entity> RemoveRequestList;
public float3 CameraPosition;
public float MaxDistanceSquared;
/// <summary>
/// 執行:大於最大距離則將實體加入移除列表
/// </summary>
/// <param name="entity"></param>
/// <param name="index"></param>
/// <param name="sceneData"></param>
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);
}
}
/// <summary>
/// 構建命令緩存任務
/// </summary>
struct BuildCommandBufferJob : IJob
{
/// <summary>
/// 實體命令緩存
/// </summary>
public EntityCommandBuffer CommandBuffer;
public NativeArray<Entity> AddRequestArray;
public NativeArray<Entity> RemoveRequestArray;
/// <summary>
/// 執行:遍歷添加列表和移除列表
/// 通過對列表中的實體添加和移除組件來標記子場景是否加載
/// 後面進行管理的時候會利用該組件來進行刷選
/// </summary>
public void Execute()
{
foreach (var entity in AddRequestArray)
{
CommandBuffer.AddComponent(entity, default(RequestSceneLoaded));
}
foreach (var entity in RemoveRequestArray)
{
CommandBuffer.RemoveComponent<RequestSceneLoaded>(entity);
}
}
}
/// <summary>
/// 在創建管理器的時候對實體命令緩存系統進行獲取
/// 這裏相當於初始化工作
/// </summary>
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>();
}
/// <summary>
/// 在摧毀管理器時釋放內存
/// </summary>
protected override void OnDestroyManager()
{
m_AddRequestList.Dispose();
m_RemoveRequestList.Dispose();
}
/// <summary>
/// 每幀更新
/// </summary>
/// <param name="inputDeps"></param>
/// <returns></returns>
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(付費羣)爲您提供有限的技術支持,以及,心靈雞湯!
當然,不需要技術支持也歡迎加入進來,隨時可以請我喝咖啡、茶和果汁!( ̄┰ ̄*)