本章节为大家解析ECS案例,资源来自,大家自行获取:
https://github.com/Unity-Technologies/EntityComponentSystemSamples
在该案例中有两个组件,一个是RotationSpeedSystem_ForEach,还有一个是ConvertToEntity
这个组件的作用就是:将Inspector面板的GameObject从根节点开始,将整个GameObject(包括子物体)全部转成Entity,并且将所有现有的、能转化的Component转换成ECS的Component
在ConvertToEntity代码中,执行了GameObjectConversionUtility.Convert()函数,而"能转化"的四亿是只有当该组件实现了接口IConvertGameObjectToEntity的Convert()函数,才会根据Convert()函数产生作用
最后把原本的GameObject销毁掉
using System;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[RequiresEntityConversion]
public class RotationSpeedAuthoring_ForEach : MonoBehaviour, IConvertGameObjectToEntity
{
public float DegreesPerSecond;
//那么这里就是实现了1.1中的Convert函数,在这个Convert函数中
//将MonoBehaviour的参数作为RotationSpeed_ForEach组件的参数传入
//并且从角度制变为弧度制
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var data = new RotationSpeed_ForEach { RadiansPerSecond = math.radians(DegreesPerSecond) };
dstManager.AddComponentData(entity, data);
}
}
ECS中的组件继承IComponentData,是struct类型,而在这个结构体中只保存了数据
using System;
using Unity.Entities;
// Serializable属性是为了支持编辑器
[Serializable]
//记住IComponentData是struct类型
public struct RotationSpeed_ForEach : IComponentData//首先要继承IComponentData
{
public float RadiansPerSecond;//组件中的数据
}
这部分内容,我已经在我的ECS(七)中介绍过了,这里使用ComponentSystem处理数据,ComponentSystem在主线程上运行,因此不利用多个CPU内核,一般而言不推荐使用
ComponentSystem提供了Entities.ForEach函数,在系统的OnUpdate()函数中调用ForEach,传入一个lambda函数,该函数会将相关组件作为参数并执行必要的工作
再次就是为具有RotationQuaternion和RotationSpeed组件的实体设置旋转动画
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
//这个系统会更新场景中所有RotationSpeed_ForEach和Rotation组件的实体
public class RotationSpeedSystem_ForEach : ComponentSystem
{
protected override void OnUpdate()
{
// Entities.ForEach会在主线程中运行(没有用到Job),但是为了更好的性能,不推荐这么使用
// 这边只是作为一个案例为大家介绍ECS,并且能够快速入门
Entities.ForEach((ref RotationSpeed_ForEach rotationSpeed, ref Rotation rotation) =>
{
var deltaTime = Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
});
}
}
那么我们总结一下最简单的ECS使用步骤:
为了看上去方便,我们将代码分隔开
public class Spawner_ForEachWithEntityChanges : MonoBehaviour
{
public GameObject Prefab;
public int CountX = 100;
public int CountY = 100;
这里都是基本写法
void Start()
{
Entity prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, World.Active);
EntityManager entityManager = World.Active.EntityManager;
在这一步,我们做了两件事:
for (int x = 0; x < CountX; x++)
{
for (int y = 0; y < CountX; y++)
{
Entity instance = entityManager.Instantiate(prefab);
float3 position = transform.TransformPoint(new float3(x - CountX/2, noise.cnoise(new float2(x, y) * 0.21F) * 10, y - CountY/2));
entityManager.SetComponentData(instance, new Translation(){ Value = position });
entityManager.AddComponentData(instance, new MoveUp_ForEachWithEntityChanges());
entityManager.AddComponentData(instance, new MovingCube_ForEachWithEntityChanges());
}
}
}
}
那么最后就是利用已经转化成Entity的prefab为原型来创建更多的Entity
position的位置是随机生成的,然后我们通过SetComponentData来修改Translation(类似原本的Transform)该Entity的位置
然后通过AddComponentData为Entity添加MoveUp_ForEachWithEntityChanges和MovingCube_ForEachWithEntityChanges这两个自定义的组件
这两个组件为空,它们只是作为Tag方便后续的处理
为了看上去方便,我们将代码分隔开
public class MovementSystem_ForEachWithEntityChanges : ComponentSystem
{
protected override void OnUpdate()
{
同样在这里用到的是ComponentSystem,用法已经在上面说过了
Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges, MoveUp_ForEachWithEntityChanges>().ForEach(
(Entity id, ref Translation translation) =>
{
var deltaTime = Time.deltaTime;
translation = new Translation()
{
Value = new float3(translation.Value.x, translation.Value.y + deltaTime, translation.Value.z)
};
if (translation.Value.y > 10.0f)
EntityManager.RemoveComponent<MoveUp_ForEachWithEntityChanges>(id);
}
);
与之前不同的是,这次我们使用了WithAllReadOnly函数来获取具有组件的实体——显而易见,这两个组件中没有数据,因此只读相较于读/写可以更加节省性能(还有其他的优势,请参考之前的文章)
那么,现在我们已经获取到了所有有这两个组件(MovingCube和MoveUp) 的实体,然后让他们上升,直到高度大于10之后,就移除MoveUp_ForEachWithEntityChanges组件
Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges>().WithNone<MoveUp_ForEachWithEntityChanges>().ForEach(
(Entity id, ref Translation translation) =>
{
translation = new Translation()
{
Value = new float3(translation.Value.x, -10.0f, translation.Value.z)
};
EntityManager.AddComponentData(id, new MoveUp_ForEachWithEntityChanges());
}
);
那么在下面这个遍历中,我们获得了有MovingCube_ForEachWithEntityChanges组件,但是没有MoveUp_ForEachWithEntityChanges组件的实体——由于上一次循环,高度大于10的实体的该组件都被益处了——因此,在这个循环中,我们需要为这些组件重新设置高度,然后重新为它们添加MoveUp_ForEachWithEntityChanges组件,那么在下个循环,它们又能被上面那个循环遍历到,继续向上移动
}
}
效果如下
在这个案例中,我们可以将Prefab转化成Entity,然后将其作为原型生成更多的Entity,具体步骤如下: