Development of MMO Game Notes 12-MegaCity 1 Based on Unity 2019's Latest ECS Architecture

Posted by Enlightened on Wed, 14 Aug 2019 07:55:51 +0200

Development of MMO Game Notes 12 Based on Unity 2019's Latest ECS Architecture

MegaCity1

Regarding MegaCity, I learned the SubScene streaming loading system simply yesterday, which is very suitable for application in large environment scenarios. Large environment scenarios are also a trend, as can be seen from the popular games now! For example, chicken, assassin creed, cyberpunk 2077 and so on, there will be more and more games in big scenes in the future, which will bring more rich game experience to players. So ECS technology has great potential. SubScene's loading speed is amazing. I suggest that small partners try it.

Preparations before commencement:

Download Unity Editor (Unity 2019.1.0 Beta 7 or updated version), if (downloaded) continue;
1 Click Megacity source code Download Zip compressed package; if (downloaded) continue;
2 This package has 7.11G, 17.6GB after decompression, open Unity Hub - > Project - > Add, add MegaCity_GDC2019_Release_OC to the project;
3 Open the official open source project with Unity Hub: MegaCity_GDC2019_Release_OC, waiting for Unity to compile;
Open Scenes/Megacity scenario.

Megacity's Traffic System

If you haven't downloaded or watched a scene running Megacity yet Demo Video Maybe I don't catch a cold about what I'm going to say next, Whatever, let's continue our study today.
As far as Traffic System is concerned, it can be seen from the game menu that there are two modes:

  1. On-Rails Flyover;
  2. Player Controller.

Look directly at the code below, E:

/// <summary>
    /// Traffic Settings
    /// </summary>
    public class TrafficSettings : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    {
        /// <summary>
        /// Section, divide the path into 100 segments
        /// </summary>
        public float pathSegments=100;
        public float globalSpeedFactor = 1.0f;//Global velocity parameters
        public int maxCars = 2000;//Maximum traffic volume

        public float[] speedMultipliers;//Velocity multiplier array

        public List<GameObject> vehiclePrefabs;//Vehicle Preset List

        /// <summary>
        /// Declarative Presupposition
        /// </summary>
        /// <param name="gameObjects">default object </param>
        public void DeclareReferencedPrefabs(List<GameObject> gameObjects)
        {
            for (int i = 0; i < vehiclePrefabs.Count; i++)
            {
                gameObjects.Add(vehiclePrefabs[i]);
            }
        }
        /// <summary>
        /// Conversion: Hand over data to C for storage
        /// </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
                //Primary entities need to be called in advance before additional entities can be invoked
                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);
        }
    }

There are two C's related to E, listed here:

    /// <summary>
    /// Automobile Presupposition Data
    /// </summary>
    public struct VehiclePrefabData : IComponentData
    {
        public Entity VehiclePrefab;
        public float VehicleSpeed;
    }

    /// <summary>
    /// Traffic Setup Data
    /// </summary>
    public struct TrafficSettingsData : IComponentData
    {
        public float GlobalSpeedFactor;
        public float PathSegments;
        public float MaxCars;
    }

It has to be said that using Data as a naming suffix is much more straightforward than Component, and that should be the case. I had Tucao before, ECS should be renamed EDS better, and DOTS also used D, indicating that D's description is more accurate. Maybe it's official to consider the architecture of Unity Component Development and want to continue this tradition. Since we need to change, why not be more thorough? Anyway, let's look at S:

    /// <summary>
    /// Traffic system
    /// </summary>
    [AlwaysUpdateSystem]//Always update
    public sealed partial class TrafficSystem : JobComponentSystem
    {
        public NativeArray<RoadSection> roadSections;//Road section
        bool doneOneTimeInit = false;//Is one-time initialization complete?
        private ComponentGroup m_CarGroup;//Vehicle-related Component Group

        private int numSections = 0;//Segment number

        public TrafficSettingsData trafficSettings;//Traffic Setup Data
        public NativeArray<VehiclePrefabData> vehiclePool;//Vehicle buffer pool

        // This is not the best way for ECS to store the player, it would be better to have component data for it
        //This is not the best way for ECS to cache players, because it is the object of the game and feels the embarrassment of the transition period.
        private GameObject _player;
        /// <summary>
        /// The physical engine of ECS is still under development. The original physical system can only be used on GameObject.
        /// </summary>
        private Rigidbody _playerRidigbody; // Will only be on the player controlled car
        /// <summary>
        /// One-time settings
        /// </summary>
        void OneTimeSetup()
        {
            //Get all sections
            var allRoads = GetComponentGroup(typeof(RoadSection)).ToComponentDataArray<RoadSection>(Allocator.TempJob);
            //Get traffic settings
            var settings = GetComponentGroup(typeof(TrafficSettingsData)).ToComponentDataArray<TrafficSettingsData>(Allocator.TempJob);
            //Acquiring Vehicle Preset
            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
            //Duplicate the vehicle pool as a default and add the necessary components to it
            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 temporarily copy all
            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>
        /// Execute when creating a manager
        /// </summary>
        protected override void OnCreateManager()
        {
            base.OnCreateManager();
            //Get all the physical state components of the vehicle to join the train group
            m_CarGroup = GetComponentGroup(ComponentType.ReadOnly<VehiclePhysicsState>());
            //Generating vehicle congestion = Starting to simulate entity command caching system
            _SpawnBarrier = World.GetOrCreateManager<BeginSimulationEntityCommandBufferSystem>();
            //Recovery Block = End of Simulated Entity Command Caching System
            _DespawnBarrier = World.GetOrCreateManager<EndSimulationEntityCommandBufferSystem>();

            // TODO: Should size this dynamics should adopt dynamic expansion
            _Cells = new NativeMultiHashMap<int, VehicleCell>(30000, Allocator.Persistent);
            _VehicleMap = new NativeMultiHashMap<int, VehicleSlotData>(30000, Allocator.Persistent);
        }
        /// <summary>
        /// Update: per frame execution
        /// </summary>
        /// <param name="inputDeps">dependency </param>
        /// <returns></returns>
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (!doneOneTimeInit)//Set up the first entry
            {
                OneTimeSetup();

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

            #if UNITY_EDITOR && USE_DEBUG_LINES
            var debugLines = _DebugLines.Lines.ToConcurrent();
            #endif
            //Vehicles in the parking queue scenario move along the queue. This primitive array should be a fleet. The location of each vehicle in each fleet is called parking space.
            var queueSlots = new NativeArray<Occupation>(numSections * Constants.RoadIndexMultiplier, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            // Setup job dependencies//Set task dependencies
            //Clean up dependencies first
            JobHandle clearDeps = new ClearArrayJob<Occupation>
            {
                Data = queueSlots,
            }.Schedule(queueSlots.Length, 512);
            //Clean up hash
            var clearHash2Job = new ClearHashJob<VehicleSlotData> {Hash = _VehicleMap}.Schedule();

            // Move vehicles along path, compute banking
            //Path Dependence: Computed Bend Moving Vehicles Along Path
            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)
            //The vehicle completing the turn moves to the next bend (or downhill)
            JobHandle pathLinkDeps = new VehiclePathLinkUpdate { RoadSections = roadSections }.Schedule(this, pathingDeps);
            // Move from lane to lane. PERF: Opportunity to not do for every vehicle.
            //From lane to lane. Performance optimization: Not every car needs to be updated
            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
            //Units for calculating the coverage of each vehicle (16 units per section of road)
            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: Fill in 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.
            //Avoid rear-end chasing and send signals when changing lanes are needed
            JobHandle moderatorDeps = new VehicleSpeedModerate { Occupancy = queueSlots, RoadSections = roadSections, DeltaTimeSeconds = Time.deltaTime}.Schedule(this, occupationGapDeps);

            // Pick concrete new lanes for cars switching lanes
            //Select specific new lanes when changing lanes
            JobHandle laneSwitchDeps = new LaneSwitch { Occupancy = queueSlots, RoadSections = roadSections}.Schedule(this, moderatorDeps);

            // Despawn cars that have run out of road
            //Recovery of vehicles running the entire distance
            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//Generate 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
            //Clear Cache
            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;
            }
        }
    }

Traffic system is more complex. In the following summary, I will try to draw a picture to explain some concepts.

Summary

After drawing for half a day, the painting was unsatisfactory because it was too complicated to understand.

Update plan

Mon 12Mon 19Mon 261. ForEach 2. IJobForEach 3. IJobChunk 4. SubScene 5. SpawnFromMonoBehaviour 6. SpawnFromEntity 7. SpawnAndRemove Rest Update Plan to Attend Cousin Wedding Advancement: Fixed TimestepWorkand Advancement: BoidExample Primary: Scene Swift I am the rest time resource integration deployment server startup process login process MegaCity Game Master World to be planned to be planned to be planned I am the rest time to be planned to be planned to be planned to be planned to be planned to be planned to be planned I am Rest time reading Excel automatically generates Entity reading Excel automatically generates Component Reading database automatically generates Entity Reading database automatically generates ComponentESC LuaFrameWork Skynet DOTS official example learning notes - - - Rest - - - Developing MMO learning notes LuaFrameWork learning pen based on ECS framework Note - - - Rest - - - Summarize the development of MMO game notes based on Unity2019 latest ECS architecture

Author's remarks

If you like my article, you can give me some praise and support. Thank you for your encouragement. If you have any questions, please leave me a message. If there are any mistakes or omissions, please criticize and testify.
If you have technical problems to discuss, you can join the Developer Alliance: 566189328 (pay group) to provide you with limited technical support, as well as, soul chicken soup!
Of course, you are welcome to join us without technical support. You can always invite me to coffee, tea and juice! (*)

ECS Series Catalogue

Development of MMO Game Note 0 Based on Unity2019 Latest ECS Architecture

Development of MMO Game Notes 1 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 2 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 3 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 4 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 5 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 6 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 7 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 8 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 9 Based on Unity 2019's Latest ECS Architecture

Development of MMO Game Notes 10 Based on Unity 2019's Latest ECS Architecture

MegaCity0

Topics: Unity REST Excel Database