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

本文主要是介绍Unity 之 Pure版Entity Component System【ECS】 案例【GalacticConquest】解析【上】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以往没有了解过Unity ECS的小伙伴建议先看看我写过的两篇ECS文章
  • Unity之浅析 Entity Component System (ECS)
  • Unity 之 Pure版Entity Component System (ECS) 官方Rotation示例解析

最近两月Unity官方一直在更新ECS的版本,有一些原来的工程在新的版本中是无法运行的,所以今天再写一篇示例解析,虽然ECS目前是测试版本,可能还会有很多的改变,正式版本上线的日期也没有明确说明,但还是希望能帮助喜欢新技术的小伙伴,互相帮助,互相学习~


有说的不准确和不正确的地方欢迎留言指正

大家的帮助是我写下去最有效的动力


点击下载工程

示例效果展示如下

7643202-5fabe0122eb08e22.gif

这个示例的规则是这样的,启动时随机生成大小位置不同的球体,然后从球体周围发射小飞船去攻击其他的星球,飞船分为红绿两队,占领后星球变成指定队伍的颜色


此次使用的Unity版本为 2018.2.9f1 Entities版本为0.0.12-preview.15。而且在启动Unity加载Entities的时候保持网路畅通,因为有朋友反映在内网无法使用Entities的情况。【最好能科学上网】

7643202-9a6e815682df2886.png
7643202-79312a4e5ab1a765.png

下面还是按照老规矩,分布逐渐创建工程

我们先创建一个PlanetSpawner脚本(产卵器)并添加到空物体Spawners上

7643202-12f8496c7ecbc6e2.png
作用如下:
  • 创建指定的数量的星球
  • 使星球位置随机分布
  • 向这些星球上动态添加数据
    • 星球旋转的数据
    • 星球旋所在的队伍、飞船数量、位置、半径数据(用于后期生产飞船使用)
  • 更改对应星球队伍的颜色
using System.Collections.Generic;
using System.Linq;
using Data;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;public class PlanetSpawner : MonoBehaviour
{/// <summary>/// 星球预制体/// </summary>[SerializeField]GameObject _planetPrefab;/// <summary>/// 初始化星球的个数/// </summary>[SerializeField]int _initialCount = 20;/// <summary>/// 产生星球的随机半径/// </summary>[SerializeField] readonly float radius = 100.00f;/// <summary>/// 场景中所有的Entity的控制者、容器/// </summary>EntityManager _entityManager;/// <summary>/// 灰色 红色 绿色对应材质数组/// </summary>[SerializeField]public Material[] _teamMaterials;/// <summary>/// 飞船Entity对应的GameObject 的字典/// </summary>static Dictionary<Entity, GameObject> entities = new Dictionary<Entity, GameObject>();public static Material[] TeamMaterials;void Awake(){TeamMaterials = _teamMaterials;}void OnEnable(){_entityManager = World.Active.GetOrCreateManager<EntityManager>();//初始化Instantiate(_initialCount);}/// <summary>/// 初始化/// </summary>/// <param name="count">产生星球的数量</param>void Instantiate(int count){//产生飞船队伍列表 1绿色 2 红色var planetOwnership = new List<int>{1, 1,2, 2};for (var i = 0; i < count; ++i){//获取星球对应的半径var sphereRadius = UnityEngine.Random.Range(5.0f, 20.0f);var safe = false;float3 pos;int attempts = 0;do{if (++attempts >= 500){Debug.Log("新创建的行星找不到合适的位置");return;}//在半径为1的范围内返回一个随机点(只读)var randomValue = (Vector3)UnityEngine.Random.insideUnitSphere;randomValue.y = 0;//星球的实际位置pos = (randomValue * radius) + new Vector3(transform.position.x, transform.position.z);//检测星球是否有重合的物体var collisions = Physics.OverlapSphere(pos, sphereRadius);//如果没有重合的地方就是安全地if (!collisions.Any())safe = true;} while (!safe);//在半径为1的范围内返回一个随机点(只读)var randomRotation = UnityEngine.Random.insideUnitSphere;//实例化星球var go = GameObject.Instantiate(_planetPrefab, pos, quaternion.identity);go.name = "Sphere_" + i;//获取星球上对应的 GameObjectEntityvar planetEntity = go.GetComponent<GameObjectEntity>().Entity;//获取渲染星球的对应的子物体var meshGo = go.GetComponentsInChildren<Transform>().First(c => c.gameObject != go).gameObject;//获取碰撞体var collider = go.GetComponent<SphereCollider>();//获取渲染星球的对应的子物体的 GameObjectEntityvar meshEntity = meshGo.GetComponent<GameObjectEntity>().Entity;//把碰撞体的半径设置和圆球一直collider.radius = sphereRadius;//半径*2等于实际扩大的倍数meshGo.transform.localScale = new Vector3(sphereRadius * 2.0f, sphereRadius * 2.0f, sphereRadius * 2.0f);var planetData = new PlanetData{//星球所在的队伍TeamOwnership = 0,//星球的半径Radius = sphereRadius,//星球的位置Position = pos};var rotationData = new RotationData{RotationSpeed = randomRotation};//队伍列表是否有任何元素 没有元素的划分为灰色星球if (planetOwnership.Any()){//给星球分队 【红队或者绿色队伍】planetData.TeamOwnership = planetOwnership.First();//移除对应的队伍planetOwnership.Remove(planetData.TeamOwnership);}else{//设定飞船数量planetData.Occupants = UnityEngine.Random.Range(1, 100);}//设置字典对应的GameObjectentities[planetEntity] = go;//设置对于队伍的颜色 1绿色 2红色SetColor(planetEntity, planetData.TeamOwnership);//动态添加对应的数据 减少了拖拖拽拽_entityManager.AddComponentData(planetEntity, planetData);_entityManager.AddComponentData(meshEntity, rotationData);}}/// <summary>/// 设置对应星球的颜色/// </summary>/// <param name="entity">对应字典</param>/// <param name="team"></param>public static void SetColor(Entity entity, int team){var go = entities[entity];go.GetComponentsInChildren<MeshRenderer>().First(c => c.gameObject != go).material = TeamMaterials[team];}
}

解读一

初始化获取场景中所有的Entity的控制者、容器

7643202-8f01139b41bda0f8.png

解读二

创建并初始化一个队伍列表,数组就是中的数字就是【_teamMaterials】中的索引,更改颜色会用到

7643202-123e600012c642ca.png

解读三

尝试为一个星球找到一个不与其他星球重叠的位置,最多尝试500,还找不到的化就会出现Log信息,其中一个与以往不同,也是ECS中大量使用的float3而不是原来常用的Vector3,这是因为float3更小巧,没有多余的信息数据占用内存。

7643202-27f6ed7c1aaf7235.png

解读四

常规实例化,不在熬述


7643202-422f87db88a5c478.png

解读五

7643202-3b4e3f647039c89e.png

这不部分就是ECS这种套路对数据相关的操作,事先准备好初始化的数据,然后用_entityManager对相应的实体添加纯数据,有点原来AddComponent的意思,也避免了拖拖拽拽。这里的数据分别为【PlanetData】【RotationData】

效果如下

7643202-3a796347dba66e18.gif

创建OccupantsTextUpdater脚本并添加到对应的Text上,他的作用是

  • 更新所在星球上含有的飞船数量并显示
7643202-878be111eb0dfd45.png
using Data;
using Unity.Entities;
using UnityEngine;namespace Other
{/// <summary>/// Just updates the text on the planets to represent the occupant count from the attached PlanetData/// 更新含有飞船的数量/// </summary>public class OccupantsTextUpdater : MonoBehaviour{Entity _planetEntity;TextMesh _text;int LastOccupantCount = -1;[SerializeField]EntityManager _entityManager;void Start(){_entityManager = World.Active.GetOrCreateManager<EntityManager>();_planetEntity = transform.parent.GetComponent<GameObjectEntity>().Entity;_text = GetComponent<TextMesh>();}void Update(){if(!_entityManager.Exists(_planetEntity))return;//获取所在星球上的PlanetData数据var data = _entityManager.GetComponentData<PlanetData>(_planetEntity);if (data.Occupants == LastOccupantCount)return;LastOccupantCount = data.Occupants;_text.text = LastOccupantCount.ToString();}}
}
7643202-89bdae157940570c.png

创建一个旋转系统,作用

  • 让每一个星球转动起来
using Data;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;namespace Systems
{/// <summary>/// 星球自转系统/// </summary>public class RotationSystem : JobComponentSystem{//筛选实体(符合星球规则的)struct Planets{public readonly int Length;public ComponentDataArray<RotationData> Data;public TransformAccessArray Transforms;}//继承为 IJobParallelForTransform 而非 IJobParallelFor 或  IJob 这是一个专门为transform操作的接口struct RotationJob : IJobParallelForTransform{public ComponentDataArray<RotationData> Rotations;public void Execute(int index, TransformAccess transform){//设定旋转transform.rotation = transform.rotation * Quaternion.Euler( Rotations[index].RotationSpeed);}}[Inject]Planets _planets;protected override JobHandle OnUpdate(JobHandle inputDeps){var job = new RotationJob{Rotations = _planets.Data};return job.Schedule(_planets.Transforms, inputDeps);}}
}

解析一

套路还是和以往IJobParallelFor类似,但是此次需要注意的是里面的继承是【IJobParallelForTransform】,一个专门实现transform并行执行的接口


创建一个增加飞船系统,作用

  • 接下来我们需要往红色和绿色的星球上添加飞船,每0.1s增加一次【可指定】
using Data;
using Unity.Collections;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;namespace Systems
{/// <summary>/// 增加可以从行星上发送的船只的数量/// </summary>// [UpdateAfter(typeof(ShipSpawnSystem))]public class OccupantIncreaseSystem : JobComponentSystem{float spawnCounter = 0.0f;float spawnInterval = 0.1f;//每次增加100个飞船int occupantsToSpawn = 100;//筛选含有PlanetData数据的实体struct Planets{public readonly int Length;public ComponentDataArray<PlanetData> Data;}struct PlanetsOccupantsJob : IJobParallelFor{public ComponentDataArray<PlanetData> Data;[ReadOnly]public int OccupantsToSpawn;public void Execute(int index){//向除了中立星球意外的星球添加飞船 每次OccupantsToSpawn个var data = Data[index];if (data.TeamOwnership == 0)return;data.Occupants += OccupantsToSpawn;Data[index] = data;}}[Inject]Planets planets;protected override JobHandle OnUpdate(JobHandle inputDeps){//指定时间运行一次 Executevar deltaTime = Time.deltaTime;spawnCounter += deltaTime;if (spawnCounter < spawnInterval)return inputDeps;spawnCounter = 0.0f;var job = new PlanetsOccupantsJob{Data = planets.Data,OccupantsToSpawn = occupantsToSpawn};return job.Schedule(planets.Length, 32, inputDeps);}}
}

解析一

设置步接参数和筛选的实体

7643202-dfc30a076e8118aa.png

解析二

每次更改数据相关业务逻辑,根据获取的PlanetData每次增加OccupantsToSpawn数量的飞船

7643202-f121af82cf0759cf.png

解析三

赋值Data并按照规定时间执行Execute内先关的逻辑

7643202-fbdfc5fff6074073.png

效果如下

7643202-813d53d08b33e394.gif

下面就开始写飞船有关的业务逻辑了。在写之前我先说下示例的设计思想,星球本地的飞船数量数据在不断增加,然后通过脚本,每一帧把红色或者绿色的飞船数量数据提取出来,然后放在等待实例化的数据中,再为每个飞船添加出生点、目标等信息。

创建脚本 AutoPlay添加到任意物体上 作用

  • 红色或者绿色星球寻找除自己以外的攻击目标
7643202-993a16639da542ad.png
using System.Collections.Generic;
using Data;
using Unity.Entities;
using UnityEngine;namespace Other
{public class AutoPlay : MonoBehaviour{/// <summary>/// 攻击间隔/// </summary>[SerializeField]float attackInterval = 0.1f;/// <summary>/// 攻击计时器/// </summary>[SerializeField]float attackCountdown = 0.1f;/// <summary>/// 所有星球物体/// </summary>GameObject[] planets;EntityManager entityManager { get; set; }public void Start(){//获取场景中所有的Planetplanets = GameObject.FindGameObjectsWithTag("Planet");entityManager = World.Active.GetOrCreateManager<EntityManager>();}public void Update(){//飞船攻击倒计时attackCountdown -= Time.deltaTime;if (attackCountdown > 0.0f)return;attackCountdown = attackInterval;if(planets.Length <= 1)Debug.LogError("没有发现任何星球!!!");//随机获取飞船索引var sourcePlanetIndex = Random.Range(0, planets.Length);var sourcePlanetEntity = planets[sourcePlanetIndex].GetComponent<GameObjectEntity>().Entity;if(!entityManager.Exists(sourcePlanetEntity)){//可以在场景卸载过程中发生enabled = false;return;}//获取对应星球的 PlanetData 数据var planetData = PlanetUtility.GetPlanetData(sourcePlanetEntity, entityManager);//防止找到的是【0队伍】的星球while (planetData.TeamOwnership == 0){//随机获取星球列表的索引sourcePlanetIndex = Random.Range(0, planets.Length);sourcePlanetEntity = planets[sourcePlanetIndex].GetComponent<GameObjectEntity>().Entity;if(!entityManager.Exists(sourcePlanetEntity)){// Can happen during scene unloadenabled = false;return;}planetData = PlanetUtility.GetPlanetData(sourcePlanetEntity, entityManager);}var targetPlanetIndex = Random.Range(0, planets.Length);//防止找到的攻击目标星球是自己while (targetPlanetIndex == sourcePlanetIndex){targetPlanetIndex = Random.Range(0, planets.Length);}PlanetUtility.AttackPlanet(planets[sourcePlanetIndex], planets[targetPlanetIndex], entityManager);}}
}

解析一

这部分逻辑主要有两部分循环遍历,第一部分是随机找到一个红色或者绿色要攻击别的人的星球
然后找到需要攻击的星球, 第二部遍历的主要作用是防止攻击的目标是自己

7643202-a9c3c6705399b1af.png

然后创建PlanetUtility脚本 作用

  • 对准备发动攻击的飞船添加相关数据
  • ** 获取星球对应的PlanetData数据**
using System.Linq;
using Data;
using Unity.Entities;
using UnityEngine;namespace Other
{/// <summary>/// Some shared functionality between AutoPlay and UserInputSystem/// 自动布局和用户输入系统之间的一些共享功能/// </summary>public static class PlanetUtility{/// <summary>/// 攻击设定/// </summary>/// <param name="fromPlanet">发射飞船的星球</param>/// <param name="toPlanet">需要攻击的星球</param>/// <param name="entityManager"></param>public static void AttackPlanet(GameObject fromPlanet, GameObject toPlanet, EntityManager entityManager){//获取发射星球的GameObjectEntityvar entity = fromPlanet.GetComponent<GameObjectEntity>().Entity;//获取渲染星球对应的GameObjectEntityvar meshComponent = fromPlanet.GetComponentsInChildren<GameObjectEntity>().First(c => c.gameObject != fromPlanet.gameObject);//获取 PlanetDatavar occupantData = entityManager.GetComponentData<PlanetData>(entity);//获取需要攻击星球的GameObjectEntityvar targetEntity = toPlanet.GetComponent<GameObjectEntity>().Entity;//飞船发射数据var launchData = new PlanetShipLaunchData{//目标星球TargetEntity = targetEntity,//设定队伍TeamOwnership = occupantData.TeamOwnership,//设定飞船数量NumberToSpawn = occupantData.Occupants,//设定产卵的位置SpawnLocation = fromPlanet.transform.position,//产卵半径(直径*0.5f)SpawnRadius = meshComponent.transform.lossyScale.x * 0.5f};//发射完飞船数量剩余0occupantData.Occupants = 0;//重新赋值entityManager.SetComponentData(entity, occupantData);//向发射星球的entity上设定 飞船发射数据 有就更改 没有则添加if (entityManager.HasComponent<PlanetShipLaunchData>(entity)){entityManager.SetComponentData(entity, launchData);return;}entityManager.AddComponentData(entity, launchData);}/// <summary>///  获取星球对应的PlanetData数据/// </summary>/// <param name="planet">对应星球</param>/// <param name="entityManager"></param>/// <returns></returns>public static PlanetData GetPlanetData (GameObject planet, EntityManager entityManager){var entity = planet.GetComponent<GameObjectEntity>().Entity;var data = GetPlanetData(entity, entityManager);return data;}/// <summary>/// 获取星球对应的PlanetData数据/// </summary>/// <param name="entity">星球对应的 entity</param>/// <param name="entityManager"></param>/// <returns></returns>public static PlanetData GetPlanetData(Entity entity, EntityManager entityManager){return entityManager.GetComponentData<PlanetData>(entity);}}
}

解析 一

根据fromPlanet与toPlanet两个GameObject获取对应的Entity和他们身上含有的纯数据

7643202-c591c7405c3bc8fe.png

解析二

根据提取出来的数据为新建纯数据 PlanetShipLaunchData 赋值,他里面含有飞船从创建到攻击用到的一切数据,然后对初始化完的PlanetShipLaunchData数据添加到对应的星球(entity)上

7643202-cc9eab74df662730.png

效果如下

7643202-a231918ca67deb9d.gif

在这里要特别说名一下,如果一个系统脚本中的Inject注入属性没有完成,是不会执行OnStartRunning函数的执行顺序如下

using System;
using Data;
using Other;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using Random = UnityEngine.Random;namespace Systems
{public class OneSystem : ComponentSystem{public OneSystem(){Debug.Log("OneSystem");}protected override void OnCreateManager(){Debug.Log("OnCreateManager");base.OnCreateManager();}protected override void OnStartRunning(){Debug.Log("OnStartRunning");base.OnStartRunning();}protected override void OnUpdate(){Debug.Log("OnUpdate");}protected override void OnStopRunning(){Debug.Log("OnStopRunning");base.OnStopRunning();}protected override void OnDestroyManager(){Debug.Log("OnDestroyManager");base.OnDestroyManager();}}
}
7643202-ee8be3016b3c1a66.png

现在我们已经明确知道了出发地点和攻击目标,接下来我们就开始实例化飞船,创建ShipSpawnSystem脚本

  • 作用实例化飞船
using System;
using Data;
using Other;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using Random = UnityEngine.Random;namespace Systems
{// [UpdateAfter(typeof(UserInputSystem))]public class ShipSpawnSystem : ComponentSystem{public ShipSpawnSystem(){_entityManager = World.Active.GetOrCreateManager<EntityManager>();}//产卵的星球struct SpawningPlanets{public readonly int Length;public ComponentDataArray<PlanetShipLaunchData> Data;}/// <summary>/// 飞船产卵需要的数据 所在的星球  和这个星球上飞船的信息/// </summary>struct ShipSpawnData{public PlanetShipLaunchData PlanetShipLaunchData;public PlanetData TargetPlanetData;public int ShipCount;}protected override void OnCreateManager(){_shipsToSpawn = new NativeList<ShipSpawnData>(Allocator.Persistent);//产生持续的数据}protected override void OnDestroyManager(){_shipsToSpawn.Dispose();}protected override void OnStartRunning(){_prefabManager = GameObject.FindObjectOfType<PrefabManager>();if (_shipRenderer != null)return;//找到飞船的渲染var prefabRenderer = _prefabManager.ShipPrefab.GetComponent<MeshInstanceRendererComponent>().Value;//找到飞船的产卵器var planetSpawner = GameObject.FindObjectOfType<PlanetSpawner>();//2种颜色的飞船_shipRenderer = new MeshInstanceRenderer[planetSpawner._teamMaterials.Length];//填充渲染具体数据  网格 自发光颜色 for (var i = 0; i < _shipRenderer.Length; ++i){_shipRenderer[i] = prefabRenderer;_shipRenderer[i].material = new Material(prefabRenderer.material){color = planetSpawner._teamMaterials[i].color};_shipRenderer[i].material.SetColor("_EmissionColor", planetSpawner._teamMaterials[i].color);}base.OnStartRunning();}/// <summary>/// 产卵星集合 注入没有完成对应的OnStartRunning不会触发/// </summary>[Inject] SpawningPlanets _planets;/// <summary>/// 飞船预制体/// </summary>PrefabManager _prefabManager;EntityManager _entityManager;//待生产飞船的队列NativeList<ShipSpawnData> _shipsToSpawn;MeshInstanceRenderer[] _shipRenderer;protected override void OnUpdate(){//遍历所有产卵星球for (var planetIndex = 0; planetIndex < _planets.Length; planetIndex++){//获取需要产卵需要的数据var planetLaunchData = _planets.Data[planetIndex];if (planetLaunchData.NumberToSpawn == 0){continue;}//获取飞船数量var shipsToSpawn = planetLaunchData.NumberToSpawn;//为了实例化飞船时出现卡顿,设定一个阈值var dt = Time.deltaTime;var deltaSpawn = Math.Max(1, Convert.ToInt32(1000.0f * dt));//设定每次释放的飞船数量的最大限量if (deltaSpawn < shipsToSpawn)shipsToSpawn = deltaSpawn;//获取需要攻击的目标星球信息var targetPlanet = _entityManager.GetComponentData<PlanetData>(planetLaunchData.TargetEntity);//添加到生产的列表_shipsToSpawn.Add(new ShipSpawnData{ShipCount = shipsToSpawn,PlanetShipLaunchData = planetLaunchData,TargetPlanetData = targetPlanet});//更新使用过的PlanetShipLaunchData数据var launchData = new PlanetShipLaunchData{TargetEntity = planetLaunchData.TargetEntity,//剩余的飞船数量NumberToSpawn = planetLaunchData.NumberToSpawn - shipsToSpawn,TeamOwnership = planetLaunchData.TeamOwnership,SpawnLocation = planetLaunchData.SpawnLocation,SpawnRadius = planetLaunchData.SpawnRadius};_planets.Data[planetIndex] = launchData;}//遍历需要生产的飞船的列表for (int spawnIndex = 0; spawnIndex < _shipsToSpawn.Length; ++spawnIndex){//生产飞船的数量var spawnCount = _shipsToSpawn[spawnIndex].ShipCount;//生成飞船位置等信息var planet = _shipsToSpawn[spawnIndex].PlanetShipLaunchData;//这批飞船的目标var targetPlanet = _shipsToSpawn[spawnIndex].TargetPlanetData;//生产飞船所在星球的位置var planetPos = planet.SpawnLocation;//与目标星球的距离var planetDistance = Vector3.Distance(planetPos, targetPlanet.Position);//生产的半径var planetRadius = planet.SpawnRadius;//实例化飞船var prefabShipEntity = _entityManager.Instantiate(_prefabManager.ShipPrefab);//添加渲染数据(哪个队伍,颜色不同)_entityManager.SetSharedComponentData(prefabShipEntity, _shipRenderer[planet.TeamOwnership]);var entities = new NativeArray<Entity>(spawnCount, Allocator.Temp);//实例化这批需要生产的飞船(一次性生产指定个数)_entityManager.Instantiate(prefabShipEntity, entities);//删掉原型_entityManager.DestroyEntity(prefabShipEntity);//对这个批次所有产生的飞船出生位置操作for (int i = 0; i < spawnCount; i++){//飞船出生点float3 shipPos;do{var insideCircle = Random.insideUnitCircle.normalized;//转换float3 因为数据更小var onSphere = new float3(insideCircle.x, 0, insideCircle.y);shipPos = planetPos + (onSphere * (planetRadius + _prefabManager.ShipPrefab.transform.localScale.x));} while (math.lengthsq(shipPos - planetPos) > planetDistance * planetDistance);var data = new ShipData{TargetEntity = planet.TargetEntity,TeamOwnership = planet.TeamOwnership};_entityManager.AddComponentData(entities[i], data);var spawnPosition = new Position{Value = shipPos};var spawnScale = new Scale{Value = new float3(1.0f, 1.0f, 1.0f)// Value = new float3(0.02f, 0.02f, 0.02f)};_entityManager.SetComponentData(entities[i], spawnScale);_entityManager.SetComponentData(entities[i], spawnPosition);}entities.Dispose();}_shipsToSpawn.Clear();}}}

解析一

准备相应的数据模板并初始化,其中NativeList是unity自定义的泛型List,且显示泛型为Struct

7643202-5427a43535dc6278.png

解析二

初始化相关属性参数,为后面的实例化做准别

7643202-5b2f9c08d6b59723.png

解析三

限制每次实例化数量并把需要实例化的数据添加到生产列表

7643202-ebb6d0644a36d17d.png

解析四

更新使用过的PlanetShipLaunchData数据

7643202-1ec23583f50c2fd0.png

解析五

遍历生产清单开始实例化

7643202-8f691256da0de013.png

解析六

设定每个飞船的位置

7643202-890d503e91c41955.png

效果如下

7643202-c9906b023a63f233.gif

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

这篇关于Unity 之 Pure版Entity Component System【ECS】 案例【GalacticConquest】解析【上】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/695419

相关文章

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

Python利用ElementTree实现快速解析XML文件

《Python利用ElementTree实现快速解析XML文件》ElementTree是Python标准库的一部分,而且是Python标准库中用于解析和操作XML数据的模块,下面小编就来和大家详细讲讲... 目录一、XML文件解析到底有多重要二、ElementTree快速入门1. 加载XML的两种方式2.

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

java解析jwt中的payload的用法

《java解析jwt中的payload的用法》:本文主要介绍java解析jwt中的payload的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解析jwt中的payload1. 使用 jjwt 库步骤 1:添加依赖步骤 2:解析 JWT2. 使用 N

Python中__init__方法使用的深度解析

《Python中__init__方法使用的深度解析》在Python的面向对象编程(OOP)体系中,__init__方法如同建造房屋时的奠基仪式——它定义了对象诞生时的初始状态,下面我们就来深入了解下_... 目录一、__init__的基因图谱二、初始化过程的魔法时刻继承链中的初始化顺序self参数的奥秘默认