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

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

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

猜你喜欢

转载自blog.csdn.net/u012632851/article/details/78895750