笨木头  2019-12-05 14:12     ECS入门     阅读(6499)     评论(2)
转载请注明,原文地址: http://www.benmutou.com/archives/2786
文章来源:笨木头与游戏开发
上一篇我们迅速地编写了一个超厉害的HelloWorld,然后大家什么都没学到。

对,我要的就是这个效果

因为这样很酷(旁白:GUN!)

这次,我们还是用上一篇的代码,然后给大家介绍最简单的概念。

1.组件(Component

在介绍实体之前,我想先介绍组件。

 

我们对组件肯定不陌生,Transform就是我们最常见的Unity组件。

而ECS的组件和我们所理解的组件不一样,ECS的组件是纯组件,仅包含数据结构,不包含任何其他功能。

 

我们再来回忆一下组件的代码:



RotationSpeed_ForEach继承了IComponentData接口,这是ECS的组件接口。

 

通过代码可以发现,这个类,实际上是一个结构体。它只包含属性,没有任何其他函数。这是和传统组件的区别,传统组件,如Transform,它有SetParent等函数,这是不太"纯"的组件。

 

ECS的组件就是单纯地只包含1个或多个字段(唔,0个也行)的结构体。

2.实体(Entity

传统的Unity开发,实体的概念不太明确,大致可以理解为,一个GameObject就是一个实体,实体上面挂载了一堆MonoBehaviour的子类,比如Transform。这个就不多说了,大家都很熟悉。

 

传统Unity开发,我们通常是把游戏逻辑都写在实体里。

而ECS的实体和我们传统的实体不太一样,它是一个纯实体,它没有任何逻辑,我们可以把ECS的实体理解为:由若干个组件组成的大组件。(非官方定义,只是帮助理解)

 

实际上,在用ECS这种模式的开发过程中,我们很少会直接对实体进行操作,我们都是在操作实体的组件。

甚至,我不得不告诉大家一个事实——ECS里没有实体,只有实体ID,不同的组件引用了不同的实体ID。所以,在ECS里,实体是一个比较虚无的东西,但它很重要。

3.系统(System

ECS的System是用于处理逻辑的,并且只处理逻辑,它不应该包含组件或其他东西,它是纯的,整个游戏中,可能会存在很多很多的System。

 

没错,大家已经发现了,ECS里的所有东西,都是纯的。

而大家以前用的那些东西,都是不纯的(不怀好意)。

 

我们再来回忆一下System的代码:
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里写我们的逻辑,比如这里的逻辑是修改实体的旋转值。

4.实体在哪?

ForEach里传递的参数是一个Action,Action可以带有1到6个参数,都是组件类型(用于筛选实体)。

很奇怪是吗?我刚刚明明说的是修改实体的旋转值,但是,代码里并没有什么实体对象,只有RotationSpeed_ForEach和Rotation两个组件。

 

没错,我们再来理解一下。

问:Entities的ForEach是干什么用的?

答:用来筛选实体的。

 

问:通过什么筛选?

答:通过组件筛选——把包含指定组件的实体筛选出来。

 

等等,筛选出来的实体在哪?

这就是关键的地方,既然我们要的是包含某些组件的实体,我们要操作的也肯定是实体的这些组件对象。

 

所以,干脆直接把实体的组件返回给我们,我们直接对实体的组件进行操作就可以了。

因此,我们会看到,System里,其实只对组件进行了操作。我们修改了组件的值,那么,包含这个组件的实体就会发生变化。

比如我们的代码,是修改了组件旋转值,那么,实体就会发生旋转。

7.结束

ForEach这个Demo的主要作用是,向大家展示最简单的ECS程序,同时,让大家知道,可以通过Entities.ForEach来遍历查找实体(的组件)。

这就意味着,还有其他的方式查找定位实体(的组件),这个在后续的Demo里会说到。

 

接下来的几篇都会给大家介绍ECS中获取/筛选/修改实体组件数据的几种方式。

是的,ECS需要做决策的地方有点多,筛选实体的方式有好几种、创建实体的方式也有好几种,对于刚入门ECS的新手来说,真的是非常懵的(比如我)。

 

好了,本篇到此结束。

 

注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。

转发本系列文章的朋友请注意,带上原文链接和原文日期,避免误导未来使用正式版的开发者。

2 条评论
  • 唐宇 2021-06-30 14:39:20

    坚持学习的第一天 内容写的通俗易懂 很容易让人理解
    0回复
    • 博主 笨木头 2021-07-02 08:43:44

      谢谢,不过,毕竟我这文章是1年多以前的了,可能官方有些内容发生了变化,我的文章仅供参考哈
      0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发