环境
unity: 2020.3.18f1
Entities: 0.17.0
Jobs: 0.8.0
SimpleECS
一个简单的ECS应该包括Entity、Component和System,接下来笔者就带着大家写一个简单的ECS。
Entity
unity的Entity是已经封装好的,不需要自己创建。笔者一开始也没有理解,每个实例不一样,为啥不是自己制作Entity,笔者目前的理解是Entity就像一个没有任何属性的GameObject,我们是通过给这个空的GameObject添加Component来组成我们理解的实体对象。
下面我们来分析一下Entity,源码如下:
using System;
namespace Unity.Entities
{
public struct Entity : IEquatable<Entity>, IComparable<Entity>
{
public int Index;
public int Version;
public static bool operator==(Entity lhs, Entity rhs)
{
return lhs.Index == rhs.Index && lhs.Version == rhs.Version;
}
public static bool operator!=(Entity lhs, Entity rhs)
{
return !(lhs == rhs);
}
public int CompareTo(Entity other)
{
return Index - other.Index;
}
public override bool Equals(object compare)
{
return this == (Entity)compare;
public override int GetHashCode()
{
return Index;
}
public static Entity Null => new Entity();
public bool Equals(Entity entity)
{
return entity.Index == Index && entity.Version == Version;
}
public override string ToString()
{
return Equals(Entity.Null) ? "Entity.Null" : $"Entity({Index}:{Version})";
}
}
}
上面的代码重要部分是两个字段,Index和Version。
Index其实不难理解,笔者在上一篇文章提起过,Entity绑定的component是统一放在一个Chunk上的,为了提高访问的速度,当我们想要操作一个Entity的Component的时候,我们是通过Index在EntityDataManager中查找所在的Chunk和IndexInChunk。
Version是一个类似于版本号的东西,就像版本号会递增一样,Entity每次被回收的时候这个值会+1。用途的话,笔者猜测可能是当我们回调一个实例的时候,可以判断实例是否已经被回收了或者改变了。
Component
[GenerateAuthoringComponent]
public struct SimpleEcsComp : IComponentData
{
public float m_MoveSpeed;
}
Component可以理解为Entity具有的属性或者组件,一个Entity是可以绑定多个Component的,就像一个GameObject上挂很多组件一样。
Component一般定义成struct,并且继承IComponentData或者ISharedComponentData这两个接口。
System
在以前的UnityECS版本里存在ComponentSystem和JobSystem两种System,但是目前已经被Unity弃用,这里就不介绍了,在新的ECS版本里,System采用的是SystemBase。
public class SimpleEcsSystem : SystemBase
{
protected override void OnUpdate()
{
var deltaTime = Time.DeltaTime;
Entities.ForEach((ref Translation translation, ref SimpleEcsComp simpleEcsComp) =>
{
translation.Value.y += simpleEcsComp.m_MoveSpeed * deltaTime;
if (translation.Value.y > 5f)
{
simpleEcsComp.m_MoveSpeed = -math.abs(simpleEcsComp.m_MoveSpeed);
}
else if (translation.Value.y < -5f)
{
simpleEcsComp.m_MoveSpeed = math.abs(simpleEcsComp.m_MoveSpeed);
}
}).ScheduleParallel();
}
}
如上述代码,在System的中,在进行Entities.ForEach的时候,最后需要执行一下ScheduleParallel这个方法,这里是一种扩展方法,这里常用的不仅仅是ScheduleParallel,还有Run和Schedul两个方法。下面介绍一下方法的用途:
Run() : evaluates the entity query and invokes the lambda function for each selected entity immediately on the main thread. Calling Run() completes the system Dependency JobHandle before running, blocking the main thread, if necessary, while it waits for those jobs to finish.
Schedul(): schedules the work to be done in a single job (no matter how many entities are selected).
ScheduleParallel() – schedules the work to be done in parallel using the C# Job system. Each parallel job instance processes at least one chunk of entities at a time. In other words, if all the selected entities are in the same chunk, then only one job instance is spawned.
运行结果如下
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 841774407@qq.com