Unity ECS 之 Entitas 的简单介绍,插件下载、导入,和简单使用(一篇文入门搞懂ECS是什么)
Unity ECS 之 Entitas 的简单介绍,插件下载、导入,和简单使用(一篇文入门搞懂ECS是什么)
目录
一、简单介绍
Entitas是一个运行效率高的轻量级C# Entity-Component-System(ECS)框架,专门为unity订制。提供内部缓存和快速的组件访问。它经过精心设计,可以在垃圾收集环境中发挥最佳作用。
面向对象思想强调对象,通过对象自身属性等完成具体实现。ECS则强调过程,通过并无实际意义的实体来收集作为数据的容器来完成具体实现。
E:Entity无实际意义,仅作为收集组合C的容器。
C:Component包含数据的组件,无方法实现。
S:处理数据的系统,自身无任何数据,通过方法来实现。
目前的优点:
遵循这个框架的规则去写代码,代码结构清晰。
ECS这种模式,耦合度就很低,所有的游戏物体都是组件的组合而已,可扩展性强,通过合理组合component就能配置出一个新的游戏物体。
很方便的管理所有实体状态,entitas提供了类似状态机的功能,当感兴趣的某个属性发生变化时,能在System中很方便的做出响应,不管什么状态,都能很方便的做出对应处理。
unity本身的开发模式就类似ECS,unity2018更是推出了最新的ECS框架,entitas很符合这种开发模式
entitas自开源以来,一直在更新维护,并受到了unity官方的认可,在unite大会上都有提到。所以,这个框架还是很靠谱的。
目前的缺点:
国内资料少,上手难度高。国内用这个框架开发的特别少,遇到问题需要自己爬坑。
不适合小项目。小项目用entitas反而麻烦
entitas更新太快,官方wiki文档更新没有跟上,比如,我在看官方Demo-MatchOne的时候,有个Event的Attribute, wiki上暂时还没有这个的
代码热更方面是个问题, entitas基本对unity开发定了一套完整的规则,特别是有Code Generate,如果项目发布后想要更新加入新的代码会很麻烦,官方对此也没有说明,目前好像也没有人分享在entitas中加入lua热更的功能
二、一些专有项简介
Entity
entity是一个存储数据的容器,用以表现程序中存在的对象。你可以添加,替换和移除数据通过IComponent。Entitas也有相应的事件event来通知你这些变化。
Entitas通过代码生成器可以自然的产生很多易读的代码如下文中的方法便是代码生成器自动产生的API调用。
Entitas中的Atrributes
Code Generator(代码生成器)目前支持与类、接口和结构一起使用的以下特性:
- Context: 可以使用此特性使组件仅在指定的context中可用;例如 [MyContextName], [Enemies], [UI]....提高内存效率。它还可以创建组件。
- Unique: 代码生成器将提供额外的方法,以确保最多存在一个具有该组件的实体,相当于单例。
- FlagPrefix:仅可用于支持标记组件的自定义前缀。
- PrimaryEntityIndex: 可用于将实体限制为唯一的组件值。
- EntityIndex: 可用于搜索具有组件值的实体。
- CustomComponentName: 为一个类或接口生成具有不同名称的多个组件。
- DontGenerate]: 代码生成器不会使用此属性处理组件。
- Cleanup: 代码生成器将生成删除组件或销毁实体的系统。
Context
Context是一个让你可以创建和销毁实体entities的工厂。通过它可以过滤感兴趣的实体。
Group
通过组Group,可以对上下文中的实体进行超快速过滤。 当实体更改时,它们会不断更新,并且可以立即返回实体组。 想象一下,您有成千上万个实体,而您只需要那些具有PositionComponent的实体-只需询问该组Group的上下文,它的结果就已经被筛选完成。
Group和获取的entities都被缓存下来,所以该方法运行速度非常高。尽可能的优先使用Group。gameContext.GetEntities(GameMatcher.Moveble)同样可以内部地使用groups。
Groups有OnEntityAdded,OnEntityRemoved,OnEntityUpdated来对group变化作出响应。
Collector
Collector提供了便捷的方法来对group的变化作出反应。比如你想汇总和处理所有添加或替换PositionComponent的实体entities。
Matcher
Matcher匹配器由代码生成器生成,可以组合。匹配器通常用于从感兴趣的上下文中获取实体组。需要在匹配器前加上你感兴趣的上下文名称(例如GameMatcher, InputMatcher等)。
System
entitas中有四种Systems:
IInitializeSystem: 只执行一次 (system.Initialize())
IExecuteSystem: 每帧执行 (system.Execute())
ICleanupSystem: 在其他系统完成后每一帧执行(system.Cleanup())
ReactiveSystem: 当观察的group改变时执行(system.Execute(Entity[]))
最后需要注意的是,需要创建一个管理System的System,因为一个游戏开发过程中,不可能只有一个System的,为了方便管理,便有了[Feature]System的概念。这个类要继承Feature,在构造器里Add所有System进去。Feature就像一个管理System的SystemManager。
Feature
entitas为提供了Features来组织你的system。使用Features将相关system组合在一起。这有一个额外的好处,就是可以在Unity层次结构中为你的system分离可视化调试对象。现在可以在逻辑组中检查它们,而不是一次检查所有。
Feature还可以帮助你在项目中执行更广泛的范例规则。功能的执行顺序由添加它们的顺序决定,然后按照这个顺序初始化它们,确保游戏逻辑不会被干扰。
Feature要求实现构造函数。使用Add()方法向Feature添加system。这里添加它们的顺序定义了它们在运行时的执行顺序。可以在Controller中使用Feature将systems组实例化。
三、注意实现
1、因为 ECS 系统会根据脚本的名称生成对应的框架代码,所以脚本的命名最好根据功能什么的取一个合适的名称,必要的时候添加命名空间
2、Compoent 组件代码有更新,注意需要重新 Generate 生成一次,其他部分代码更改可以不需要重新 Generate
四、ECS 的下载
Github 上下载
1、在github 上输入 Entitas 可以搜到 ECS 框架
Releases 下载地址:
Github下载地址:
2、往下拉,可以看到一些介绍,适用平台,一些学习资料等信息
3、可以在旁边的 Release 下载最新版本,或者以前版本
4、点击 Entitas.zip 下载即可
6、或者在 Download 点击下载
7、最后下载保存即可
Unity Asset Store 中下载
1、进入 Unity Asset Store,搜索 Entitas,即可找到 ECS 框架
2、Unity Asset Store 中 Entitas 与Github 的差不多(其实应该是一样的)
Unity 的 Package Manager 中下载
1、同理打开 Unity ,在 Window-Package Manager 中搜索 Entities 即可搜到
(目前 Entities 为预览版本,为 DOTs 布局使用的 )
五、ECS 框架导入
这里讲解 github 上下载的解压包的方法
1、解压 github 上下载的解压包
2、打开 Unity ,直接把解压的文件拖入 Unity 即可
3、之后就会看到菜单栏多了 Tools 菜单,内容如下
六、ECS 的配置
1、选择 Tools - Jenny - Preferences ...
2、打开 Jenny ,然后店家 Auto Import 进行一些自动配置
3、之后就会有新的栏目,和配置好的信息
4、一些关键信息的说明
5、Unity 下的 Assembly-CSSharp.csproj 就是 配置信息中的 Project Path ,如果名称与配置信息不一致,进行更改即可
七、ECS 的简单使用( Hello ECS Entitas)
1、在 Unity 中新建一个 文件夹 Scripts ,管理脚本,并且 ECS 生成的文件夹也将放在这里,然后编写一个ECS component 组件
2、ECS component 中的组件需要生成代码,所以 Tools - Jenny - Preferences ...,并配置 Target Directory 为 Scripts
3、代码就自动生成在了 Scripts 下
4、编写其他System、Featrue、Controller 实现 打印 Hello ECS Entitas……
(每个文件夹下的功能可以参见上面的专有项介绍)
5、把 Controller 脚本挂在到场景中
6、运行脚本,打印出现,ECS 中就有了对应的 component 组件
八、关键代码
1、HelloECSComponent
using Entitas;
/// <summary>
/// HelloECSComponent 组件继承 IComponent
/// 注入属性为 Game (目前 Entitas 只有 Game,Input)
/// </summary>
[Game]
public class HelloECSComponent : IComponent
{
// 变量
public string message;
}
2、HelloECSSystem
using System.Collections.Generic;
using UnityEngine;
using Entitas;
public class HelloECSSystem : ReactiveSystem<GameEntity>
{
/// <summary>
/// 构建函数,传入上下文
/// 目前上下文只有 Game,Input
/// 这里 HelloECSComponent 组件是 Game,故 contexts.game
/// </summary>
/// <param name="contexts"></param>
public HelloECSSystem(Contexts contexts) : base(contexts.game) {
}
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
{
return context.CreateCollector(GameMatcher.HelloECS);
}
/// <summary>
/// 过滤器,判断上下文是否包含该组件 Component
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
protected override bool Filter(GameEntity entity)
{
return entity.hasHelloECS;
}
/// <summary>
/// 具体执行的代码
/// </summary>
/// <param name="entities"></param>
protected override void Execute(List<GameEntity> entities)
{
foreach (GameEntity entity in entities)
{
Debug.Log(entity.helloECS.message);
}
}
}
3、InitSystem
using Entitas;
/// <summary>
/// 初始化系统
/// </summary>
public class InitSystem : IInitializeSystem
{
private readonly GameContext mGameContext;
/// <summary>
/// 构造函数,获取上下文
/// </summary>
/// <param name="contexts"></param>
public InitSystem(Contexts contexts) {
mGameContext = contexts.game;
}
/// <summary>
/// 初始化
/// </summary>
public void Initialize()
{
mGameContext.CreateEntity().AddHelloECS("Hello ECS Entitas……");
}
}
4、AddSystemsFeature
/// <summary>
/// 用于添加需要的系统 System
/// </summary>
public class AddSystemsFeature : Feature
{
// "AddSystemsFeature" 名字自取
public AddSystemsFeature(Contexts contexts):base("AddSystemsFeature") {
// 添加系统
Add(new HelloECSSystem(contexts));
Add(new InitSystem(contexts));
}
}
5、GameController
using Entitas;
using UnityEngine;
/// <summary>
/// 控制器,挂在在Unity场景中执行的
/// </summary>
public class GameController : MonoBehaviour
{
private Systems mSystems;
// Start is called before the first frame update
void Start()
{
// 获取上下文
var context = Contexts.sharedInstance;
// "Systems" 名称自取即可
mSystems = new Feature("Systems").Add(new AddSystemsFeature(context));
// 初始化
mSystems.Initialize();
}
// Update is called once per frame
void Update()
{
// 执行
mSystems.Execute();
// 清除
mSystems.Cleanup();
}
}