最近在学习unity的ecs框架,转载几篇写的比较好的文章帮助理解
原文日期 2019-12-5 避免误导未来使用正式版的开发者。
在介绍实体之前,我想先介绍组件。
我们对组件肯定不陌生,Transform就是我们最常见的Unity组件。
而ECS的组件和我们所理解的组件不一样,ECS的组件是纯组件,仅包含数据结构,不包含任何其他功能。
我们再来回忆一下组件的代码:
RotationSpeed_ForEach继承了IComponentData接口,这是ECS的组件接口。
通过代码可以发现,这个类,实际上是一个结构体。它只包含属性,没有任何其他函数。这是和传统组件的区别,传统组件,如Transform,它有SetParent等函数,这是不太”纯”的组件。
ECS的组件就是单纯地只包含1个或多个字段(唔,0个也行)的结构体。
传统的Unity开发,实体的概念不太明确,大致可以理解为,一个GameObject就是一个实体,实体上面挂载了一堆MonoBehaviour的子类,比如Transform。这个就不多说了,大家都很熟悉。
传统Unity开发,我们通常是把游戏逻辑都写在实体里。
而ECS的实体和我们传统的实体不太一样,它是一个纯实体,它没有任何逻辑,我们可以把ECS的实体理解为:由若干个组件组成的大组件。(非官方定义,只是帮助理解)
实际上,在用ECS这种模式的开发过程中,我们很少会直接对实体进行操作,我们都是在操作实体的组件。
甚至,我不得不告诉大家一个事实——ECS里没有实体,只有实体ID,不同的组件引用了不同的实体ID。所以,在ECS里,实体是一个比较虚无的东西,但它很重要。
RotationSpeed_ForEach:我们会把方块旋转的速度通过这里传递给Component数据组件,该数据会在那里储存,并在System中被调用。
ECS的System是用于处理逻辑的,并且只处理逻辑,它不应该包含组件或其他东西,它是纯的,整个游戏中,可能会存在很多很多的System。
没错,大家已经发现了,ECS里的所有东西,都是纯的。
而大家以前用的那些东西,都是不纯的(不怀好意)。
我们再来回忆一下System的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms;
// This system updates all entities in the scene with both a RotationSpeed_ForEach and Rotation component.
public class RotationSpeedSystem_ForEach : ComponentSystem { protected override void OnUpdate () { Entities.ForEach((ref RotationSpeed_ForEach rotationSpeed, ref Rotation rotation) => { var deltaTime = Time.DeltaTime;
/* 旋转Cube,代码逻辑不用管,这里可以是其他任何逻辑 */ rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime)); }); } } |
继承了ComponentSystem的类,就是系统类,看起来有点复杂(官方原本的Demo更复杂,我简化了),先别慌,慢慢来。
System可以重写OnUpdate函数,大家可以把它看成是MonoBehaviour的Update函数。这就是之前提到的,ECS把实体的逻辑专门放到System里处理了。OnUpdate函数也是会每帧调用一次的。
很奇怪是不是?System是怎么单独处理逻辑的?它怎么知道自己要处理的是哪个实体的逻辑?
这时候,组件的作用就体现出来了。
代码中的Entities可以理解为是所有实体的缓存(实际上可能不是,先这么理解),对Entities调用ForEach函数,相当于是在所有实体中搜索符合条件的实体。
那么,什么是符合条件的实体?
比如,上面的代码,ForEach的参数是一个Action,我们这个Action有两个参数:RotationSpeed_ForEach rotationSpeed和Rotation rotation。
那么,ForEach就会把所有同时包含RotationSpeed_ForEach组件和Rotation组件的实体查出来,遍历一次。(Rotation是系统自带的组件,我们的Cube在转换为实体时,也会自动附加Rotation组件)
然后,我们就可以在Action里写我们的逻辑,比如这里的逻辑是修改实体的旋转值。
ForEach里传递的参数是一个Action,Action可以带有1到6个参数,都是组件类型(用于筛选实体)。
很奇怪是吗?我刚刚明明说的是修改实体的旋转值,但是,代码里并没有什么实体对象,只有RotationSpeed_ForEach和Rotation两个组件。
没错,我们再来理解一下。
问:Entities的ForEach是干什么用的?
答:用来筛选实体的。
问:通过什么筛选?
答:通过组件筛选——把包含指定组件的实体筛选出来。
等等,筛选出来的实体在哪?
这就是关键的地方,既然我们要的是包含某些组件的实体,我们要操作的也肯定是实体的这些组件对象。
所以,干脆直接把实体的组件返回给我们,我们直接对实体的组件进行操作就可以了。
因此,我们会看到,System里,其实只对组件进行了操作。我们修改了组件的值,那么,包含这个组件的实体就会发生变化。
比如我们的代码,是修改了组件旋转值,那么,实体就会发生旋转。
ForEach这个Demo的主要作用是,向大家展示最简单的ECS程序,同时,让大家知道,可以通过Entities.ForEach来遍历查找实体(的组件)。
这就意味着,还有其他的方式查找定位实体(的组件),这个在后续的Demo里会说到。
接下来的几篇都会给大家介绍ECS中获取/筛选/修改实体组件数据的几种方式。
是的,ECS需要做决策的地方有点多,筛选实体的方式有好几种、创建实体的方式也有好几种,对于刚入门ECS的新手来说,真的是非常懵的(比如我)。
好了,本篇到此结束。
注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。
下图出自同样不错的官方案例讲解:https://blog.csdn.net/qq_30137245/article/details/98959135