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

相关文章

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提