MegaCity1

基于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:

/// 
    /// 交通设置
    /// 
    public class TrafficSettings : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    {
        /// 
        /// 路段,把路径分成100个片段
        /// 
        public float pathSegments=100;
        public float globalSpeedFactor = 1.0f;//全局速度参数
        public int maxCars = 2000;//最大车量

        public float[] speedMultipliers;//速度乘数数组

        public List<GameObject> vehiclePrefabs;//车辆预设列表

        /// 
        /// 声明预设
        /// 
        /// 预设对象
        public void DeclareReferencedPrefabs(List<GameObject> gameObjects)
        {
            for (int i = 0; i < vehiclePrefabs.Count; i++)
            {
                gameObjects.Add(vehiclePrefabs[i]);
            }
        }
        /// 
        /// 转化:把数据交给C储存
        /// 
        /// 
        /// 
        /// 
        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,这里一起列出来:

    /// 
    /// 汽车预设数据
    /// 
    public struct VehiclePrefabData : IComponentData
    {
        public Entity VehiclePrefab;
        public float VehicleSpeed;
    }

    /// 
    /// 交通设置数据
    /// 
    public struct TrafficSettingsData : IComponentData
    {
        public float GlobalSpeedFactor;
        public float PathSegments;
        public float MaxCars;
    }

不得不说,用Data作为命名后缀比Component直白多了,就应该这样才对。我之前就吐槽过了,ECS应该更名为EDS更好一些,而DOTS里面也用到了D,说明D的描述更准确。也许官方是考虑到Unity组件式开发的架构吧,想要继续保留这个传统。既然要改变,何不彻底一些?Anyway,还是看看S吧:

    /// 
    /// 交通系统
    /// 
    [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;
        /// 
        /// ECS的物理引擎还在开发中,原来的物理系统只能用在游戏对象(GameObject)上
        /// 
        private Rigidbody _playerRidigbody; // Will only be on the player controlled car
        /// 
        /// 一次性设置
        /// 
        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();
        }
        /// 
        /// 在创建管理器时执行
        /// 
        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);
        }
        /// 
        /// 更新:每帧执行
        /// 
        /// 依赖
        /// 
        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;
            }
        }
    }

交通系统比较复杂,下面小结中我会尝试画一幅图来解释一些概念。

小结

画了半天,画得不满意,因为太复杂了,实在画不明白。

更新计划

Mon 12 Mon 19 Mon 26 1. ForEach 2. IJobForEach 3. IJobChunk 4. SubScene 5. SpawnFromMonoBehaviour 6. SpawnFromEntity 7. SpawnAndRemove 休息 修正更新计划 参加表哥婚礼 进阶:FixedTimestepWorkaround 进阶:BoidExample 初级:SceneSwitcher 我是休息时间 资源整合 部署服务器 启动流程 登录流程 MegaCity 游戏主世界 待计划 待计划 待计划 待计划 我是休息时间 待计划 待计划 待计划 待计划 待计划 我是休息时间 读取Excel自动生成Entity 读取Excel自动生成Component 读取数据库自动生成Entity 读取数据库自动生成Component ESC LuaFrameWork Skynet DOTS 官方示例学习笔记 -----休息----- 基于ECS架构开发MMO学习笔记 LuaFrameWork学习笔记 -----休息----- 基于Skynet架构开发服务器学习笔记 制作代码自动生成工具 总结 基于Unity2019最新ECS架构开发MMO游戏笔记

作者的话

AltAlt

如果喜欢我的文章可以点赞支持一下,谢谢鼓励!如果有什么疑问可以给我留言,有错漏的地方请批评指证!
如果有技术难题需要讨论,可以加入开发者联盟: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主世界

你可能感兴趣的:(ECS,Unity,DOTS)