ECS在各种Unity版本上表现都不一样,官方给的例子如果用Unity2018.3.1打开就会满处飘红。坑很多,官方文档像屎一样。好在有大神们在,看了很多博客,终于找到一个能用的,完成了一个小demo,十万个小立方体围着中间转,有三种不同的材质,最后跑了70帧,大功告成,可喜可贺。
一开始有点困惑,不过好在之前接触过StrangeIOC框架,用抽象的概念去理解,很快就明白了。(大部分时间都坑在ECS迷幻的版本更新上了)
ECS 就是Entity Component System的缩写
Entity 实体,只存储数据。自己写的时候继承自IComponentData,
但是这里面和StrangeIOC的数据接口不一样,要求数据不能是Class,而必须是Struct.看了别人的博客,说用struct存储会直接写入到数据栈中,是一连串紧密的数据结构,节约cpu开销,增加速度
Component 这里的Component和之前有很大区别,之前Component就是在物体上挂n个monobehavior,ECS框架里面的组件把Position,Rotation,Scale……能拆的全拆开了,用到哪个再把哪个挂上,再也不用担心一个游戏物体挂一万个Monobehavior
System 转门处理所有的运算逻辑,最好配合JobSystem开多线程,卡顿什么的再也不存在了。
[Serializable]
public struct MovementData : IComponentData
{//不允许有逻辑和对外依赖
public float theta;
public float3 center;//轴心
public float omega;//角速度
public float radius;//半径
public float p;
}
除了之前提到的地方,Entity还有一处和以往不一样,不用vector3而是使用float3,float3也是简化版的vector3,基本无缝对接到vector3,用起就对了。
public class MovementComponent : ComponentDataWrapper<MovementData> {
}
定义的Component包裹着这一组数据,这个在2018.3.1当中变成了ComponentDataProxy,这个组件相当于简易版的Monobehaivor,需要手动或者用代码挂到物体上
public class MovementSystem : JobComponentSystem
{
[BurstCompile]
struct MovementJob : IJobProcessComponentData<Position,Rotation,MovementData>
{
///
/// 中心点及半径数据
///
public quaternion targetRotation;
public float deltaTime;
public void Execute(ref Position position, ref Rotation rotation,ref MovementData data)
{
float theta = data.theta+ data.omega* deltaTime;
float x = data.center.x + data.radius* Mathf.Cos(theta)*Mathf.Sin(data.p);
float y = data.center.y+ data.radius *Mathf.Cos(data.p);
float z = data.center.z + data.radius * Mathf.Sin(theta) * Mathf.Sin(data.p);
position = new Position
{
Value = new float3(x, y, z)
};
rotation = new Rotation
{
Value = targetRotation
};
data = new MovementData
{
theta=theta,
center = data.center,
radius = data.radius,
omega = data.omega,
p=data.p
};
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
MovementJob moveJob = new MovementJob
{
deltaTime = Time.deltaTime,
targetRotation = Quaternion.LookRotation(Camera.main.transform.up)
};
moveJob.deltaTime = Time.deltaTime;
JobHandle jobHandle = moveJob.Schedule(this, inputDeps);
return jobHandle;
}
}
在这里会遍历所有Entity,挨个执行程序。因为会开多线程所以速度会非常快
最后挂一个Monobehaviour挂脚本,执行安装程序(其实这部分也可以不挂脚本用静态类来实现,Prefab,Metarial从Resources.Load当中读取等等,理论上可以实现一行脚本都不挂的跑程序。)
public class GameManager : MonoBehaviour {
public EntityManager entityManager;
public EntityArchetype entityArchetype;
public int createPrefabCout;
public GameObject prefab;
Mesh mesh;
public Material[] materials;
public Transform centerTrans;
// Use this for initialization
void Start () {
mesh = prefab.GetComponent<MeshFilter>().sharedMesh;
entityManager = World.Active.GetOrCreateManager<EntityManager>();
NativeArray<Entity> entities = new NativeArray<Entity>(createPrefabCout, Allocator.Temp);
entityManager.Instantiate(prefab, entities);
for (int i = 0; i < createPrefabCout; i++)
{
//设置组件
entityManager.SetComponentData(entities[i], new Position { Value = UnityEngine.Random.insideUnitSphere * 100 });
entityManager.SetComponentData(entities[i], new Rotation { Value = quaternion.identity });
float randRadius = UnityEngine.Random.Range(2, 1000);
float randOmega = UnityEngine.Random.Range(-10, 10);
float randTheta = UnityEngine.Random.Range(0, 360);
float randp= UnityEngine.Random.Range(0, 360);
entityManager.SetComponentData(entities[i], new MovementData
{
center = centerTrans.position,
omega = randOmega,
radius = randRadius,
theta = randTheta,
p=randp
});
int colorIndex = UnityEngine.Random.Range(0, 3);
Debug.Log(colorIndex);
//添加并设置组件
entityManager.AddSharedComponentData(entities[i], new MeshInstanceRenderer
{
mesh = this.mesh,
material = this.materials[colorIndex],
});
}
entities.Dispose();
}
}
帧率:75左右,相当不错
生成的片有10w个。
源码地址:ECS框架学习的源码地址