天天看點

MegaCity1MegaCity1更新計劃ECS系列目錄

基于Unity2019最新ECS架構開發MMO遊戲筆記12

  • MegaCity1
      • 開始之前的準備工作:
    • Megacity的交通系統
      • 小結
  • 更新計劃
    • 作者的話
  • ECS系列目錄
    • ECS官方示例1:ForEach
    • ECS官方案例2:IJobForEach
    • ECS官方案例3:IJobChunk
    • ECS官方案例4:SubScene
    • ECS官方案例5:SpawnFromMonoBehaviour
    • ECS官方案例6:SpawnFromEntity
    • ECS官方案例7:SpawnAndRemove
    • ECS進階:FixedTimestepWorkaround
    • ECS進階:Boids
    • ECS進階:場景切換器
    • ECS進階:MegaCity0
    • ECS進階:MegaCity1
    • UnityMMO資源整合&伺服器部署
    • UnityMMO選人流程
    • UnityMMO主世界

MegaCity1

關于MegaCity,昨天簡單學習了SubScene流加載系統,非常适合應用在大環境場景,大環境場景也是一種大趨勢,從現在流行的遊戲可以看得出來!例如吃雞,刺客信條,賽博朋克2077等等,未來大場景的遊戲會越來越多,給玩家帶來更加豐富的遊戲體驗。是以ECS技術也是以大有可為,SubScene的加載速度令人驚歎,建議小夥伴們嘗試一下。

開始之前的準備工作:

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的交通系統

如果還沒有下載下傳運作過Megacity的場景,或觀看過示範視訊,可能對我接下來要說的并不感冒,Whatever,我們還是繼續今天的學習吧。

關于TrafficSystem,從遊戲菜單就可以看出來,分為兩種模式:

  1. 自動駕駛(On-Rails Flyover);
  2. 玩家控制(Player Controller)。

下面直接看代碼,E:

/// <summary>
    /// 交通設定
    /// </summary>
    public class TrafficSettings : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    {
        /// <summary>
        /// 路段,把路徑分成100個片段
        /// </summary>
        public float pathSegments=100;
        public float globalSpeedFactor = 1.0f;//全局速度參數
        public int maxCars = 2000;//最大車量

        public float[] speedMultipliers;//速度乘數數組

        public List<GameObject> vehiclePrefabs;//車輛預設清單

        /// <summary>
        /// 聲明預設
        /// </summary>
        /// <param name="gameObjects">預設對象</param>
        public void DeclareReferencedPrefabs(List<GameObject> gameObjects)
        {
            for (int i = 0; i < vehiclePrefabs.Count; i++)
            {
                gameObjects.Add(vehiclePrefabs[i]);
            }
        }
        /// <summary>
        /// 轉化:把資料交給C儲存
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="dstManager"></param>
        /// <param name="conversionSystem"></param>
        public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
        {
            for (int j = 0; j < vehiclePrefabs.Count; j++)
            {
                // A primary entity needs to be called before additional entities can be used
                //在附加實體可以被調用之前,主實體需要被提前調用
                Entity vehiclePrefab = conversionSystem.CreateAdditionalEntity(this);
                var prefabData = new VehiclePrefabData
                {
                    VehiclePrefab = conversionSystem.GetPrimaryEntity(vehiclePrefabs[j]),
                    VehicleSpeed = j < speedMultipliers.Length ? speedMultipliers[j] : 3.0f
                };
                dstManager.AddComponentData(vehiclePrefab, prefabData);
            }
            
            var trafficSettings = new TrafficSettingsData
            {
                GlobalSpeedFactor = globalSpeedFactor,
                PathSegments = pathSegments,
                MaxCars = maxCars
            };

            dstManager.AddComponentData(entity, trafficSettings);
        }
    }
           

和E相關的有兩個C,這裡一起列出來:

/// <summary>
    /// 汽車預設資料
    /// </summary>
    public struct VehiclePrefabData : IComponentData
    {
        public Entity VehiclePrefab;
        public float VehicleSpeed;
    }

    /// <summary>
    /// 交通設定資料
    /// </summary>
    public struct TrafficSettingsData : IComponentData
    {
        public float GlobalSpeedFactor;
        public float PathSegments;
        public float MaxCars;
    }
           

不得不說,用Data作為命名字尾比Component直白多了,就應該這樣才對。我之前就吐槽過了,ECS應該更名為EDS更好一些,而DOTS裡面也用到了D,說明D的描述更準确。也許官方是考慮到Unity元件式開發的架構吧,想要繼續保留這個傳統。既然要改變,何不徹底一些?Anyway,還是看看S吧:

/// <summary>
    /// 交通系統
    /// </summary>
    [AlwaysUpdateSystem]//總是更新
    public sealed partial class TrafficSystem : JobComponentSystem
    {
        public NativeArray<RoadSection> roadSections;//路段
        bool doneOneTimeInit = false;//一次性初始化是否完成
        private ComponentGroup m_CarGroup;//車相關元件組

        private int numSections = 0;//段數

        public TrafficSettingsData trafficSettings;//交通設定資料
        public NativeArray<VehiclePrefabData> vehiclePool;//車輛緩存池

        // This is not the best way for ECS to store the player, it would be better to have component data for it
        //這不是ECS緩存玩家的最佳方式,因為它是遊戲對象,感受到了過渡期的尴尬
        private GameObject _player;
        /// <summary>
        /// ECS的實體引擎還在開發中,原來的實體系統隻能用在遊戲對象(GameObject)上
        /// </summary>
        private Rigidbody _playerRidigbody; // Will only be on the player controlled car
        /// <summary>
        /// 一次性設定
        /// </summary>
        void OneTimeSetup()
        {
            //擷取所有路段
            var allRoads = GetComponentGroup(typeof(RoadSection)).ToComponentDataArray<RoadSection>(Allocator.TempJob);
            //擷取交通設定
            var settings = GetComponentGroup(typeof(TrafficSettingsData)).ToComponentDataArray<TrafficSettingsData>(Allocator.TempJob);
            //擷取車輛預設
            var vehicles = GetComponentGroup(typeof(VehiclePrefabData)).ToComponentDataArray<VehiclePrefabData>(Allocator.TempJob);
            
            
            if (settings.Length == 0 || vehicles.Length == 0 || allRoads.Length == 0)
            {
                allRoads.Dispose();
                vehicles.Dispose();
                settings.Dispose();
                return;
            }

            trafficSettings = settings[0];

            // Copy the vehicle pool for prefabs
            //複制車輛池作為預設,為其添加必要元件
            vehiclePool = new NativeArray<VehiclePrefabData>(vehicles.Length, Allocator.Persistent);
            for (int v = 0; v < vehicles.Length; v++)
            {
                if (!EntityManager.HasComponent<VehiclePathing>(vehicles[v].VehiclePrefab))
                {
                    EntityManager.AddComponentData(vehicles[v].VehiclePrefab, new VehiclePathing());
                }

                if (!EntityManager.HasComponent<VehicleTargetPosition>(vehicles[v].VehiclePrefab))
                {
                    EntityManager.AddComponentData(vehicles[v].VehiclePrefab, new VehicleTargetPosition());
                }

                if (!EntityManager.HasComponent<VehiclePhysicsState>(vehicles[v].VehiclePrefab))
                {
                    EntityManager.AddComponentData(vehicles[v].VehiclePrefab, new VehiclePhysicsState());
                }

                vehiclePool[v] = vehicles[v];
            }
            
            // for now just copy everything 暫時複制所有
            roadSections = new NativeArray<RoadSection>(allRoads.Length, Allocator.Persistent);

            for (int a = 0; a < allRoads.Length; a++)
            {
                roadSections[allRoads[a].sortIndex] = allRoads[a];
            }

            numSections = roadSections.Length;
          
            
#if UNITY_EDITOR && USE_OCCUPANCY_DEBUG

            OccupancyDebug.queueSlots = new NativeArray<Occupation>(numSections * Constants.RoadIndexMultiplier, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            OccupancyDebug.roadSections = new NativeArray<RoadSection>(numSections, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
            for (int a = 0; a < roadSections.Length; a++)
            {
                OccupancyDebug.roadSections[a] = roadSections[a];
            }

#endif
            doneOneTimeInit = true;
            allRoads.Dispose();
            vehicles.Dispose();
            settings.Dispose();
        }
        /// <summary>
        /// 在建立管理器時執行
        /// </summary>
        protected override void OnCreateManager()
        {
            base.OnCreateManager();
            //擷取所有含車輛實體狀态元件的都加入車組
            m_CarGroup = GetComponentGroup(ComponentType.ReadOnly<VehiclePhysicsState>());
            //生成車輛的阻塞 = 開始模拟實體指令緩存系統
            _SpawnBarrier = World.GetOrCreateManager<BeginSimulationEntityCommandBufferSystem>();
            //回收阻塞  = 結束模拟實體指令緩存系統
            _DespawnBarrier = World.GetOrCreateManager<EndSimulationEntityCommandBufferSystem>();

            // TODO: Should size this dynamically 應該采用動态擴容
            _Cells = new NativeMultiHashMap<int, VehicleCell>(30000, Allocator.Persistent);
            _VehicleMap = new NativeMultiHashMap<int, VehicleSlotData>(30000, Allocator.Persistent);
        }
        /// <summary>
        /// 更新:每幀執行
        /// </summary>
        /// <param name="inputDeps">依賴</param>
        /// <returns></returns>
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (!doneOneTimeInit)//第一次進入時先進行設定
            {
                OneTimeSetup();

                return inputDeps;
            }
            
            if (vehiclePool.Length == 0 || roadSections.Length == 0)
                return inputDeps;

            #if UNITY_EDITOR && USE_DEBUG_LINES
            var debugLines = _DebugLines.Lines.ToConcurrent();
            #endif
            //車位隊列  場景中的車輛沿着隊列行進,這個原生數組應該就是一個車隊,每個車隊中每輛車的位置稱為車位
            var queueSlots = new NativeArray<Occupation>(numSections * Constants.RoadIndexMultiplier, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            // Setup job dependencies//設定任務依賴
            //先清理依賴
            JobHandle clearDeps = new ClearArrayJob<Occupation>
            {
                Data = queueSlots,
            }.Schedule(queueSlots.Length, 512);
            //清理哈希
            var clearHash2Job = new ClearHashJob<VehicleSlotData> {Hash = _VehicleMap}.Schedule();

            // Move vehicles along path, compute banking
            //路徑依賴:沿着路徑,計算的彎道移動車輛
            JobHandle pathingDeps = new VehiclePathUpdate { RoadSections = roadSections, DeltaTimeSeconds = Time.deltaTime * trafficSettings.GlobalSpeedFactor }.Schedule(this, JobHandle.CombineDependencies(clearDeps, inputDeps));
            // Move vehicles that have completed their curve to the next curve (or an off ramp)
            //完成轉彎的車輛移動到下一個彎道(或一個下坡)
            JobHandle pathLinkDeps = new VehiclePathLinkUpdate { RoadSections = roadSections }.Schedule(this, pathingDeps);
            // Move from lane to lane. PERF: Opportunity to not do for every vehicle.
            //從車道到車道。性能優化:不必每一輛車都更新
            JobHandle lanePositionDeps = new VehicleLanePosition { RoadSections = roadSections, DeltaTimeSeconds = Time.deltaTime }.Schedule(this, pathLinkDeps);

            float3 playerPosition = default;
            float3 playerVelocity = default;
            if (_player != null)
            {
                playerPosition = _player.transform.position;
                if (_playerRidigbody != null)
                {
                    playerVelocity = _playerRidigbody.velocity;
                }
            }

            // Compute what cells (of the 16 for each road section) is covered by each vehicle
            //計算每輛車覆寫的單元(每段路16個單元)
            JobHandle occupationAliasingDeps = new OccupationAliasing {OccupancyToVehicleMap = _VehicleMap.ToConcurrent(), RoadSections = roadSections}.Schedule(this, JobHandle.CombineDependencies(clearHash2Job, clearDeps, lanePositionDeps));
            JobHandle occupationFillDeps = new OccupationFill2 {Occupations = queueSlots}.Schedule(_VehicleMap, 32, occupationAliasingDeps);

            // Back-fill the information:填充資訊
            // |   A      B     |
            // |AAAABBBBBBB     |
            JobHandle occupationGapDeps = new OccupationGapFill { Occupations = queueSlots }.Schedule(roadSections.Length, 16, occupationFillDeps);
            occupationGapDeps = new OccupationGapAdjustmentJob {Occupations = queueSlots, RoadSections = roadSections}.Schedule(roadSections.Length, 32, occupationGapDeps);
            occupationGapDeps = new OccupationGapFill2 {Occupations = queueSlots}.Schedule(roadSections.Length, 16, occupationGapDeps);

            // Sample occupation ahead of each vehicle and slow down to not run into cars in front
            // Also signal if a lane change is wanted.
            //避免追尾,在需要換道時發送信号
            JobHandle moderatorDeps = new VehicleSpeedModerate { Occupancy = queueSlots, RoadSections = roadSections, DeltaTimeSeconds = Time.deltaTime}.Schedule(this, occupationGapDeps);

            // Pick concrete new lanes for cars switching lanes
            //在車換道時選擇具體的新車道
            JobHandle laneSwitchDeps = new LaneSwitch { Occupancy = queueSlots, RoadSections = roadSections}.Schedule(this, moderatorDeps);

            // Despawn cars that have run out of road
            //回收跑完全程的車輛
            JobHandle despawnDeps = new VehicleDespawnJob { EntityCommandBuffer = _DespawnBarrier.CreateCommandBuffer().ToConcurrent() }.Schedule(this, laneSwitchDeps);
            _DespawnBarrier.AddJobHandleForProducer(despawnDeps);

            JobHandle spawnDeps;

            var carCount = m_CarGroup.CalculateLength();
            if (carCount < trafficSettings.MaxCars)
            {
                // Spawn new cars//生成新車
                spawnDeps = new VehicleSpawnJob
                {
                    VehiclePool = vehiclePool,
                    RoadSections = roadSections,
                    Occupation = queueSlots,
                    EntityCommandBuffer = _SpawnBarrier.CreateCommandBuffer().ToConcurrent()
                }.Schedule(this,occupationGapDeps);
                
                _SpawnBarrier.AddJobHandleForProducer(spawnDeps);
            }
            else
            {
                spawnDeps = occupationGapDeps;
            }
            
            

#if UNITY_EDITOR && USE_OCCUPANCY_DEBUG

            spawnDeps.Complete();
            laneSwitchDeps.Complete();

            for (int a = 0; a < queueSlots.Length; a++)
            {
                OccupancyDebug.queueSlots[a] = queueSlots[a];
            }
#endif
            JobHandle finalDeps = default;

            float3 camPos = default;
            Camera mainCamera = Camera.main;
            if (mainCamera != null)
            {
                camPos = mainCamera.transform.position;
            }

            JobHandle movementDeps = JobHandle.CombineDependencies(spawnDeps, despawnDeps);
            
            int stepsTaken = 0;
            float timeStep = 1.0f / 60.0f;

            _TransformRemain += Time.deltaTime;

            while (_TransformRemain >= timeStep)
            {
                var clearHashJob = new ClearHashJob<VehicleCell> {Hash = _Cells}.Schedule(movementDeps);

                var hashJob = new VehicleHashJob {CellMap = _Cells.ToConcurrent()}.Schedule(this, clearHashJob);

                hashJob = new PlayerHashJob {CellMap = _Cells, Pos = playerPosition, Velocity = playerVelocity}.Schedule(hashJob);

                movementDeps = new VehicleMovementJob
                {
                    TimeStep = timeStep,
                    Cells = _Cells,
#if UNITY_EDITOR && USE_DEBUG_LINES
                    DebugLines = debugLines
#endif
                }.Schedule(this, hashJob);

                _TransformRemain -= timeStep;
                ++stepsTaken;
            }

            JobHandle finalPosition;

            if (stepsTaken > 0)
            {
                JobHandle finalTransform = new VehicleTransformJob {dt = timeStep, CameraPos = camPos}.Schedule(this, movementDeps);

                finalPosition = finalTransform;
            }
            else
            {
                finalPosition = movementDeps;
            }

            finalDeps = finalPosition;


            // Get rid of occupation data
            //清除緩存
            JobHandle disposeJob = new DisposeArrayJob<Occupation>
            {
                Data = queueSlots
            }.Schedule(JobHandle.CombineDependencies(spawnDeps, laneSwitchDeps));

            return JobHandle.CombineDependencies(disposeJob, finalDeps);
        }

        private float _TransformRemain;
        private NativeMultiHashMap<int, VehicleCell> _Cells;
        private NativeMultiHashMap<int, VehicleSlotData> _VehicleMap;

        private BeginSimulationEntityCommandBufferSystem _SpawnBarrier;
        private EndSimulationEntityCommandBufferSystem _DespawnBarrier;

#if UNITY_EDITOR && USE_DEBUG_LINES
        [Inject] private DebugLineSystem _DebugLines;
#endif

        protected override void OnDestroyManager()
        {
            if (doneOneTimeInit)
            {
                roadSections.Dispose();
                vehiclePool.Dispose();
#if UNITY_EDITOR && USE_OCCUPANCY_DEBUG

                OccupancyDebug.queueSlots.Dispose();
                OccupancyDebug.roadSections.Dispose();

#endif
            }

            _VehicleMap.Dispose();
            _Cells.Dispose();
        }

        public void SetPlayerReference(GameObject player)
        {
            _player = player;
            var rigid = _player.GetComponent<Rigidbody>();
            if (rigid != null)
            {
                _playerRidigbody = rigid;
            }
        }
    }
           

交通系統比較複雜,下面小結中我會嘗試畫一幅圖來解釋一些概念。

小結

畫了半天,畫得不滿意,因為太複雜了,實在畫不明白。

更新計劃

作者的話

MegaCity1MegaCity1更新計劃ECS系列目錄
MegaCity1MegaCity1更新計劃ECS系列目錄

如果喜歡我的文章可以點贊支援一下,謝謝鼓勵!如果有什麼疑問可以給我留言,有錯漏的地方請批評指證!

如果有技術難題需要讨論,可以加入開發者聯盟:566189328(付費群)為您提供有限的技術支援,以及,心靈雞湯!

當然,不需要技術支援也歡迎加入進來,随時可以請我喝咖啡、茶和果汁!( ̄┰ ̄*)

ECS系列目錄

ECS官方示例1:ForEach

ECS官方案例2:IJobForEach

ECS官方案例3:IJobChunk

ECS官方案例4:SubScene

ECS官方案例5:SpawnFromMonoBehaviour

ECS官方案例6:SpawnFromEntity

ECS官方案例7:SpawnAndRemove

ECS進階:FixedTimestepWorkaround

ECS進階:Boids

ECS進階:場景切換器

ECS進階:MegaCity0

ECS進階:MegaCity1

UnityMMO資源整合&伺服器部署

UnityMMO選人流程

UnityMMO主世界

繼續閱讀