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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 书接上回: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
相关文章
|
20天前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
62 6
|
19天前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
44 3
|
19天前
|
图形学 开发者
透视与正交之外的奇妙视界:深入解析Unity游戏开发中的相机与视角控制艺术,探索打造沉浸式玩家体验的奥秘与技巧
【8月更文挑战第31天】在Unity中,相机不仅是玩家观察游戏世界的窗口,更是塑造氛围和引导注意力的关键工具。通过灵活运用相机系统,开发者能大幅提升游戏的艺术表现力和沉浸感。本文将探讨如何实现多种相机控制,包括第三人称跟随和第一人称视角,并提供实用代码示例。
35 0
|
19天前
|
图形学 开发者
【独家揭秘】Unity游戏开发秘籍:从基础到进阶,掌握材质与纹理的艺术,打造超现实游戏视效的全过程剖析——案例教你如何让每一面墙都会“说话”
【8月更文挑战第31天】Unity 是全球领先的跨平台游戏开发引擎,以其高效性能和丰富的工具集著称,尤其在提升游戏视觉效果方面表现突出。本文通过具体案例分析,介绍如何利用 Unity 中的材质与纹理技术打造逼真且具艺术感的游戏世界。材质定义物体表面属性,如颜色、光滑度等;纹理则用于模拟真实细节。结合使用两者可显著增强场景真实感。以 FPS 游戏为例,通过调整材质参数和编写脚本动态改变属性,可实现自然视觉效果。此外,Unity 还提供了多种高级技术和优化方法供开发者探索。
35 0
|
19天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
80 0
|
19天前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
43 0
|
20天前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
42 0
|
20天前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
33 0
|
19天前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
40 4
|
20天前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
38 5

热门文章

最新文章

推荐镜像

更多