Unity 之 Pure版Entity Component System【ECS】 案例【GalacticConquest】解析【下】

本文涉及的产品
云服务器 ECS,每月免费额度280元 3个月
云服务器ECS,u1 2核4GB 1个月
简介: 书接上回:Unity 之 Pure版Entity Component System 案例【GalacticConquest】解析【上】点击下载工程我们已经完成飞船的实例化,下面就是让飞船动起来~~~创建脚本ShipMovementSyste...

书接上回:Unity 之 Pure版Entity Component System 案例【GalacticConquest】解析【上】

点击下载工程

我们已经完成飞船的实例化,下面就是让飞船动起来~~~

创建脚本ShipMovementSystem飞船的运动系统,作用:

  • 让飞船朝着目标移动
  • 达到目标是添加标签
using Data;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
using Unity.Mathematics;

namespace Systems
{
    [UpdateAfter(typeof(ShipArrivalSystem))]
    public class ShipMovementSystem : JobComponentSystem
    {
        //所有飞船
        struct Ships
        {
            public readonly int Length;
            public ComponentDataArray<Position> Positions;
            public ComponentDataArray<Rotation> Rotations;
            public ComponentDataArray<ShipData> Data;
            public EntityArray Entities;
        }
        //所有星球
        struct Planets
        {
            public readonly int Length;
            public ComponentDataArray<PlanetData> Data;
        }
        /// <summary>
        /// 计算飞船位移 多核心运算
        /// </summary>
        [BurstCompile]
        struct CalculatePositionsJob : IJobParallelFor
        {
            public float DeltaTime;
            [ReadOnly]
            public ComponentDataArray<ShipData> Ships;
            [ReadOnly] public EntityArray Entities;
            public ComponentDataArray<Position> Positions;
            public ComponentDataArray<Rotation> Rotations;

            [ReadOnly] public ComponentDataArray<PlanetData> Planets;
            [ReadOnly] public ComponentDataFromEntity<PlanetData> TargetPlanet;

            //并发版本 因为使用的是IJobParallelFor
            public NativeQueue<Entity>.Concurrent ShipArrivedQueue;

            public void Execute(int index)
            {
                //飞船的数据
                var shipData = Ships[index];
                //对应需要攻击星球的位置
                var targetPosition = TargetPlanet[shipData.TargetEntity].Position;
                //飞船的位置
                var position = Positions[index];
                //飞船的角度
                var rotation = Rotations[index];

                //逐渐靠近需要攻击的星球
                var newPos = Vector3.MoveTowards(position.Value, targetPosition, DeltaTime * 4.0f);
                //逐一遍历所有找到的星球
                for (var planetIndex = 0; planetIndex < Planets.Length; planetIndex++)
                {
                    var planet = Planets[planetIndex];
                    //如果与星球的距离小于半径
                    if (Vector3.Distance(newPos, planet.Position) < planet.Radius)
                    {
                        //判断这个是不是需要攻击的星球
                        if (planet.Position == targetPosition)
                        {
                            //添加到飞船队列里面
                            ShipArrivedQueue.Enqueue(Entities[index]);
                        }
                        //防止飞船进入到星球内部,重新计算。很精确啊~
                        var direction = (newPos - planet.Position).normalized;
                        newPos = planet.Position + (direction * planet.Radius);
                        break;
                    }
                }
                //计算飞船朝向
                var shipCurrentDirection = math.normalize((float3)newPos - position.Value);
                rotation.Value = quaternion.LookRotation(shipCurrentDirection, math.up());
                //将计算完的结果赋值
                position.Value = newPos;
                Positions[index] = position;
                Rotations[index] = rotation;
            }
        }
        //单核心运算 IJob允许您安排与其他作业和主线程并行运行的单个作业
        struct ShipArrivedTagJob : IJob
        {
            public EntityCommandBuffer EntityCommandBuffer;
            public NativeQueue<Entity> ShipArrivedQueue;

            public void Execute()
            {
                Entity entity;
                while (ShipArrivedQueue.TryDequeue(out entity))
                {
                    //添加已经到达指定星球的标记
                    EntityCommandBuffer.AddComponent(entity, new ShipArrivedTag());
                }
            }
        }

        [Inject]
        EndFrameBarrier m_EndFrameBarrier;

        [Inject]
        Ships m_Ships;    //所有飞船
        [Inject]
        Planets m_Planets;//所有星球

        NativeQueue<Entity> m_ShipArrivedQueue;

        protected override void OnCreateManager()
        {
            m_ShipArrivedQueue = new NativeQueue<Entity>(Allocator.Persistent);
        }

        protected override void OnDestroyManager()
        {
            m_ShipArrivedQueue.Dispose();
            base.OnDestroyManager();
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (m_Ships.Length == 0)
                return inputDeps;

            //在 IJobParallelFor 中的逻辑
            var handle = new CalculatePositionsJob
            {
                Ships = m_Ships.Data,
                Planets = m_Planets.Data,
                TargetPlanet = GetComponentDataFromEntity<PlanetData>(),
                DeltaTime = Time.deltaTime,
                Entities = m_Ships.Entities,
                Positions = m_Ships.Positions,
                Rotations = m_Ships.Rotations,
                ShipArrivedQueue = m_ShipArrivedQueue.ToConcurrent()//并发
            }.Schedule(m_Ships.Length, 32, inputDeps);

            //在 IJob 中执行的逻辑
            handle = new ShipArrivedTagJob
            {
                //自动执行的队列
                EntityCommandBuffer = m_EndFrameBarrier.CreateCommandBuffer(),
                ShipArrivedQueue = m_ShipArrivedQueue
            }.Schedule(handle);

            return handle;
        }
    }
}

解析一

筛选指定的飞船和星球实体

img_8afdcdcd2f70586d16ee433dd9392e2f.png

解析二

因为是IJobParalleFor,所以队列用的事并行版本

img_a37152fe916c1140cef3032d8ffbccef.png

解析三

通过多核并发计算出飞船的位移,然后对接触到目标的飞船添加到已经到达的队列中

img_8a4e51bea085d4dfa9013cc125cc65a6.png

解析四

单核心处理需要添加到达标记的队列

img_3ac7928f355c04b2d8a323d7c06724a3.png

解析五

需要注意的事声明时不是NativeQueue<Entity>.Concurrent这种带有【Concurrent】类型需要多核心并行执行时需要在赋值时用【.ToConcurrent()】转换下。

EntityCommandBuffer我理解的是这种数据结构,适合并发且安全的不断改变数据长度的缓冲。且可有效较少因为不断的改变造成GC。

img_a1af015c5b56eb529137a25c3df2dfa8.png

创建脚本 ShipMovementSystem ,作用:

  • 处理被添加ShipArrivedTag的飞船
  • 处理星球被攻击到达一定程度时的转换颜色逻辑
using Data;
using Unity.Collections;
using Unity.Entities;
using Unity.Rendering;

namespace Systems
{
    public class ShipArrivalSystem : ComponentSystem
    {
        EntityManager _entityManager;

        //初始化
        public ShipArrivalSystem()
        {
            _entityManager = World.Active.GetOrCreateManager<EntityManager>();
        }

        //到达星球的飞船
        struct Ships
        {
            public readonly int Length;
            public ComponentDataArray<ShipData> Data;
            public EntityArray Entities;
            public ComponentDataArray<ShipArrivedTag> Tag;
        }
        [Inject]
        Ships _ships;

        protected override void OnUpdate()
        {
            if (_ships.Length == 0)
                return;

            var arrivingShipTransforms = new NativeList<Entity>(Allocator.Temp);
            var arrivingShipData = new NativeList<ShipData>(Allocator.Temp);

            for (var shipIndex = 0; shipIndex < _ships.Length; shipIndex++)
            {
                var shipData = _ships.Data[shipIndex];
                var shipEntity = _ships.Entities[shipIndex];
                arrivingShipData.Add(shipData);
                arrivingShipTransforms.Add(shipEntity);
            }

            HandleArrivedShips(arrivingShipData, arrivingShipTransforms);

            arrivingShipTransforms.Dispose();
            arrivingShipData.Dispose();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="arrivingShipData">到达飞船的数据集合</param>
        /// <param name="arrivingShipEntities">到达飞船的实体集合</param>
        void HandleArrivedShips(NativeList<ShipData> arrivingShipData, NativeList<Entity> arrivingShipEntities)
        {
            //逐一遍历所有飞船数据
            for (var shipIndex = 0; shipIndex < arrivingShipData.Length; shipIndex++)
            {

                var shipData = arrivingShipData[shipIndex];
                //获取对应飞船需要攻击的星球
                var planetData = _entityManager.GetComponentData<PlanetData>(shipData.TargetEntity);

                //不同队伍减少
                if (shipData.TeamOwnership != planetData.TeamOwnership)
                {
                    planetData.Occupants = planetData.Occupants - 1;
                    if (planetData.Occupants <= 0)
                    {
                        //本地飞船没有时转换队伍
                        planetData.TeamOwnership = shipData.TeamOwnership;
                        PlanetSpawner.SetColor(shipData.TargetEntity, planetData.TeamOwnership);
                    }
                }
                else//相同队伍相加
                {
                    planetData.Occupants = planetData.Occupants + 1;
                }
                //星球重新赋值
                _entityManager.SetComponentData(shipData.TargetEntity, planetData);
            }
            //删除这些已经到达的飞船
            _entityManager.DestroyEntity(arrivingShipEntities);
        }
    }
}

解析一

获取所有到达飞船的实体集合 与对应的数据集合

img_958cb497c45e6b96926c03b2a8fb4ccb.png

解析二

根据得到的数据集合判断到达的星球是否为同一队伍,相同增加飞船,不同减少飞船,星球的飞船小于0变换队伍和颜色

img_404be2e4ddeb67e21936d8ef64eb9acc.png

为了增加互动性,添加脚本UserInputSystem

  • 左键点击需要发射的星球
  • 右键点击需要攻击的星球
  • 点击空白处取消
using System.Collections.Generic;
using System.Linq;
using Data;
using Other;
using Unity.Entities;
using UnityEngine;

namespace Systems
{

    public class UserInputSystem : ComponentSystem
    {
        struct Planets
        {
            public readonly int Length;
            public ComponentDataArray<PlanetData> Data;
        }
        [Inject] Planets planets;

        //添加?是表示可空类型,可自行Google
        Dictionary<GameObject, PlanetData?> FromTargets = new Dictionary<GameObject, PlanetData?>();
        GameObject ToTarget = null;

        EntityManager _entityManager;

        /// <summary>
        /// 构造函数中初始化 EntityManager
        /// </summary>
        public UserInputSystem()
        {
            _entityManager = World.Active.GetOrCreateManager<EntityManager>();
        }

        protected override void OnUpdate()
        {
            if (Input.GetMouseButtonDown(0))
            {
                var planet = GetPlanetUnderMouse();
                if (planet == null)
                {
                    FromTargets.Clear();
                    Debug.Log("点击外部,我们清除了选择");
                }
                else
                {
                    if (FromTargets.ContainsKey(planet))
                    {
                        Debug.Log("取消选择的目标Deselecting from target " + planet.name);
                        FromTargets.Remove(planet);
                    }
                    else
                    {
                        var data = PlanetUtility.GetPlanetData(planet, _entityManager);
                        //原来的目标
                        var previousTarget = FromTargets.Values.FirstOrDefault();
                        if ((previousTarget == null || previousTarget.Value.TeamOwnership == data.TeamOwnership) && data.TeamOwnership != 0)
                        {
                            Debug.Log("选择的目标 " + planet.name);
                            FromTargets[planet] = data;
                            Debug.Log("数量:" + FromTargets.Count);
                        }
                        else
                        {
                            if (data.TeamOwnership == 0)
                            {
                                Debug.LogWarning("不能设置中性行星");
                            }
                            else
                            {
                                Debug.Log("从目标中添加行星,但是清除之前的列表,因为它是一个不同的团队");
                                FromTargets.Clear();
                                FromTargets[planet] = data;
                            }
                        }

                    }
                }

            }
            if (Input.GetMouseButtonDown(1))
            {
                var planet = GetPlanetUnderMouse();
                if (planet == null)
                {
                    Debug.Log("取消选中目标 ");
                    ToTarget = null;
                }
                else
                {
                    if (!FromTargets.Any())
                    {
                        Debug.Log("没有任何选中的发射星球");
                        return;
                    }
                    Debug.Log($"需要攻击的星球名称为{planet.name}" );
                    ToTarget = planet;
                    foreach (var p in FromTargets.Keys)
                    {
                        if (p == ToTarget)
                            continue;
                        PlanetUtility.AttackPlanet(p, ToTarget, _entityManager);

                    }
                }
            }
        }

        GameObject GetPlanetUnderMouse()
        {
            RaycastHit hit;
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer("Planet")))
            {
                return hit.collider.transform.gameObject;
            }
            return null;
        }
    }
}

为了保证逻辑能够按照指定顺序执行:添加顺序特性UpdateAfter
UserInputSystem--->ShipSpawnSystem
ShipArrivalSystem--->ShipMovementSystem

img_091879237ec539473362c7f337ddfa50.png
img_ea80e1f2a99c334aed9a976e3feb0012.png

最终效果

img_2b174266e47c573b5e6957c742eb7eb1.gif

打完收工~~~

img_569463b7983f645f063dd7b1bef93e0c.png
相关文章
|
2月前
|
弹性计算 Ubuntu Windows
2024年部署幻兽帕鲁/Palworld服务器多少钱?阿里云帕鲁主机优惠价格解析
对于热爱《幻兽帕鲁》的玩家们来说,一个稳定、高效的游戏服务器是畅享游戏乐趣的关键。那么,搭建一个这样的服务器需要多少钱呢?别担心,阿里云已经为大家准备了超值的幻兽帕鲁Palworld游戏服务器!
|
2月前
|
弹性计算 固态存储 Linux
阿里云上Palworld/幻兽帕鲁服务器搭建全解析:超详细步骤,轻松掌握
想要在阿里云上轻松开服玩《幻兽帕鲁》吗?跟着我们的步骤来,简单几步就能搞定!
|
2月前
|
弹性计算 Ubuntu Linux
新手也能玩转幻兽帕鲁联机服务器:Palworld/幻兽帕鲁搭建攻略全解析
随着《幻兽帕鲁》的持续火爆,越来越多的玩家希望与好友在这款游戏中共同冒险。为了实现这一愿望,搭建一个属于自己的《幻兽帕鲁》服务器成为不少玩家的首选。今天,就为大家带来一篇关于如何轻松搭建《幻兽帕鲁》服务器的完整攻略,即使你是新手小白,也能轻松上手!
21 0
|
2月前
|
弹性计算 Ubuntu Linux
2024年Palworld/幻兽帕鲁服务器自建手册:详细步骤解析与设置指南
爆款游戏《幻兽帕鲁》是很多玩家在与好友开黑时的首选,因为《幻兽帕鲁》有着十分丰富的游戏内容,玩家在联机游玩《幻兽帕鲁》时能够获得非常多的快乐。 但在《幻兽帕鲁》进行联机时,是需要自行搭建服务器的,下面就带来,最新《幻兽帕鲁》服务器设置全步骤大全,方便玩家更好的进行联机游玩。 以下就是幻兽帕鲁服务器自建:幻兽帕鲁服务器设置全步骤大全的相关内容。
49 3
|
2月前
|
存储 弹性计算 安全
2024阿里云服务器ECS全方位解析_云主机详解
2024阿里云服务器ECS全方位解析_云主机详解,阿里云服务器是什么?云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务,云服务器可以降低IT成本提升运维效率,免去企业或个人前期采购IT硬件的成本,阿里云服务器让用户像使用水、电、天然气等公共资源一样便捷、高效地使用服务器
|
2月前
|
弹性计算 大数据 测试技术
阿里云服务器服务费怎么算的?详细解析
2024年阿里云服务器租用价格表更新,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服务器30元3个月,幻兽帕鲁4核16G和8核32G服务器配置,云服务器ECS可以选择经济型e实例、通用算力u1实例、ECS计算型c7、通用型g7、c8i、g8i等企业级实例规格
|
2月前
|
存储 弹性计算 固态存储
阿里云服务器租用费用1t空间多少钱?全面解析
阿里云服务器租用费用1t空间多少钱?1T空间如果是系统盘SSD云盘价格是3686元一年、ESSD云盘1t空间是5222元一年,ESSD Entry云盘1024G存储空间价格是2580元一年。阿里云百科整理几款不同的云盘1t空间价格
|
2月前
|
弹性计算 大数据 测试技术
阿里云服务器服务费怎么计算?详细解析2024新版
阿里云服务器服务费怎么计算?详细解析2024新版,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服务器30元3个月
47 1
|
2月前
|
前端开发 网络协议 JavaScript
|
2月前
|
域名解析 缓存 网络协议
【域名解析】如何将域名指向对应服务器IP
【域名解析】如何将域名指向对应服务器IP
360 1

推荐镜像

更多