Unity3D ECS框架 Entitas入门学习4 ReactiveSystem原理总结

来源:互联网 发布:schema.org 知乎 编辑:程序博客网 时间:2024/06/05 19:27

版本:unity 5.6  语言:C#

 

总起:

距离上一篇的Entitas文章已经有段时间了,现在Entitas的最新版本是0.46,而我这篇文章使用的是0.39的HelloWorld例子,区别不是很大,不过在实际生产中应该使用最新版。

 

如果还不怎么了解Entitas,请戳这里(在阅读本章时,至少能独立写出第一篇文章的Demo)。

 

这篇文章主要是我使用过一段时候后对ReactiveSystem的一些思考以及总结,主要用于记录,以便以后有机会在项目中用到时能够快速回忆起这些知识。

 

触发ReativeSystem的条件:

在第一篇的HelloWorld例子中,改变message参数就能触发ReactiveSystem将其打印出来,所以我一直认为直接赋值就能触发该行为,到底能不能呢,我们来试试:

 

写一个记录左键按下次数的类,并通过DebugMessageSystem打印出来:

public class LeftMouseClickCountSystem : IExecuteSystem{    private GameContext gameCtx;    private int count = 0;    private GameEntity entity;    public string CountMsg {        get { return string.Format("左键被按了{0}次", count); }    }    public LeftMouseClickCountSystem(Contexts ctxs)    {        gameCtx = ctxs.game;    }    public void Execute()    {        if (Input.GetMouseButtonDown(0))        {            count++;            // 如果没有打印信息的Entity,则创建一个            if (entity == null)            {                entity = gameCtx.CreateEntity();                entity.AddDebugMessage(CountMsg);                return;            }            entity.debugMessage.message = CountMsg;        }    }}

去除所有其他干扰System,只保留以上System和DebugMessageSystem,以下是结果:



Debug信息只在添加的时候打印一次,说明entity.debugMessage.message = CountMsg这样的赋值方式并没有触发ReactiveSystem。

 

所以在Inspector界面上改值为什么就能触发呢?我们设置个断点来看看。

 

断在打印信息处,但结果令人意外,最上层的方法是我们写的_systems.Execute,其间并没有类似message赋值的操作:



那这个ReactiveSystem究竟是怎么触发的?这边先卖个关子,下一个小节我们再讨论详细的原理。

 

让我先公布触发ReactiveSystem方式的答案:

 

只需要将代码中赋值的部分entity.debugMessage.message = CountMsg改成entity.ReplaceDebugMessage(CountMsg)就可以了。

 

我们来看看效果:


 

成功打印了,所以在使用Entitas时千万别使用直接赋值的方式。

 

ReativeSystem触发的原理:

在编写DebugMessageSystem时,我们复写了三个方法:GetTrigger、Filter、Execute。

 

我们来看看其父类ReactiveSystem的Execute函数实现:

public void Execute() {    if(_collector.collectedEntities.Count != 0) {        foreach(var e in _collector.collectedEntities) {            if(Filter(e)) {                e.Retain(this);                _buffer.Add(e);            }        }        _collector.ClearCollectedEntities();        if(_buffer.Count != 0) {            Execute(_buffer);            for(int i = 0; i < _buffer.Count; i++) {                _buffer[i].Release(this);            }            _buffer.Clear();        }    }}

这边就清楚的说明了子类的Filter和Execute的作用了,Filter在执行前过滤一下Entity群组,最后由Execute实现真正的执行。

 

而GetTrigger方法的调用是在构造函数中:

protected ReactiveSystem(IContext<TEntity> context) {    _collector = GetTrigger(context);    _buffer = new List<TEntity>();}

我们大概可以这么理解:创建了一个收集器,用于过滤Entity,将需要的Entity放入Group中(作用其实和Filter有点类似,Filter算是最终检查,完全可以不写直接返回true)。

 

而在复写时,由Context来创建该Collector:

/// Creates an Collector.public static Collector<TEntity> CreateCollector<TEntity>(this IContext<TEntity> context, IMatcher<TEntity> matcher, GroupEvent groupEvent = GroupEvent.Added)    where TEntity : class, IEntity, new() {    return new Collector<TEntity>(context.GetGroup(matcher), groupEvent);}

在看到GroupEvent groupEvent = GroupEvent.Added是不是突然感到一阵激动?终于看到事件相关的东西了,看来触发的原因就是出在Collector类上面,让我们来打开看看。

 

在Collector的构造函数中,调用了一个Activate的函数,内容是这样的:

/// Activates the Collector and will start collecting/// changed entities. Collectors are activated by default.public void Activate() {    for(int i = 0; i < _groups.Length; i++) {        var group = _groups[i];        var groupEvent = _groupEvents[i];        switch(groupEvent) {            case GroupEvent.Added:                group.OnEntityAdded -= _addEntityCache;                group.OnEntityAdded += _addEntityCache;                break;            case GroupEvent.Removed:                group.OnEntityRemoved -= _addEntityCache;                group.OnEntityRemoved += _addEntityCache;                break;            case GroupEvent.AddedOrRemoved:                group.OnEntityAdded -= _addEntityCache;                group.OnEntityAdded += _addEntityCache;                group.OnEntityRemoved -= _addEntityCache;                group.OnEntityRemoved += _addEntityCache;                break;        }    }}

看到这里,对于ReactiveSystem的触发也能猜个七七八八了吧,在创建Collector中,首先获取了Context指定的Group,在Group中添加了OnEntityAdded事件,从而使每次有新的相关Entity出现就会将它收集到缓存中,然后ReactiveSystem检测到当前收集缓存不为空,便先将收集缓存Filter过滤,后执行子类实现的Execute。

 

还有一个疑问没有解决,在Inspector中改变message时为何会触发ReactiveSystem,我们将断点断在Collector类的addEntity函数中,然后改变值,查看调用堆栈:



调用堆栈的第四个已经很明显了,就是调用了ReplaceComponent才触发了ReactiveSystem。

 

主动在Group中监听:

了解ReactiveSystem触发的原理,我们可以在Contexts中主动获取Group,并设置监听事件:

// 添加显示UI的事件var groupUIShow = Contexts.sharedInstance.input.GetGroup(InputMatcher.UIShowCommand);groupUIShow.OnEntityAdded -= showUI;groupUIShow.OnEntityAdded += showUI;var groupUIFreeze = Contexts.sharedInstance.input.GetGroup(InputMatcher.UIFreezeCommand);groupUIFreeze.OnEntityAdded -= freezeUI;groupUIFreeze.OnEntityAdded += freezeUI;var groupUIUnfreeze = Contexts.sharedInstance.input.GetGroup(InputMatcher.UIUnfreezeCommand);groupUIUnfreeze.OnEntityAdded -= unfreezeUI;groupUIUnfreeze.OnEntityAdded += unfreezeUI;var groupUIClose = Contexts.sharedInstance.input.GetGroup(InputMatcher.UICloseCommand);groupUIClose.OnEntityAdded -= closeUI;groupUIClose.OnEntityAdded += closeUI;

以上是我项目中不使用ReactiveSystem,触发事件的方法。

 

个人:

当时自己在研究Entitas框架原理时,卡在触发原理上好久,想了一整了晚上,没有想出了所以然来,最后问了组内的大神,大神一点拨我就懂了,不过当时他让我暂时可以不用研究Entitas的原理。

 

嗯,关于研究这件事情确实有坏处也好坏处吧,坏处是会消耗大量的时间,有时甚至影响项目的编写,好处是能更加深刻的理解框架,而且会学到很多新的知识,C#的事件、partial class、扩展方法等,都是在学习这个框架时有了深刻的理解。

 

有利有弊,如何权衡是一个值得深思的问题。

 

Entitas的研究暂时就到这里了,我其实算是纠结了比较久的,因为这个框架确实很不错,只是现在用的人比较少,而且学习起来确实有一定的难度,在项目组内也难以推广。

 

如果以后遇到比较好的团队,运用到该框架,会继续研究相关的知识,暂时我就将它放在一边了。

阅读全文
'); })();
1 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 qq小型车 凌志小型车 两箱小型车 小型车油耗排行榜 小型车有哪些 什么叫小型车 好看的小型车 精品小车推荐 3到5万的新车 3万至5万新车图片 适合女生开的小型车 适合女性开的小型车 我的小公主城堡森林 我的小公主城堡2 嘟嘟小巴士之汉字城堡 小公主城堡 我的小镇我的小公主城堡 小城市 创建文明城市小诗歌 小城市做什么生意好 城市小医生罗子凌 在小城市开什么店好 大城市好还是小城市好 城市小铺 小城谣歌词 小城谣舞蹈 小城谣原唱 小城镇 小城镇建设论文 小城镇规划 小城镇做什么生意好 芭芭拉基恩 达芙妮基恩 基恩士电话 基恩士光纤放大器说明书 基恩士静电消除器 基恩士光纤放大器 基恩士价格查询 基恩士中国有限公司 基恩士传感器说明书 5226首页小鱼儿玄2基站4311