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的研究暂时就到这里了,我其实算是纠结了比较久的,因为这个框架确实很不错,只是现在用的人比较少,而且学习起来确实有一定的难度,在项目组内也难以推广。
如果以后遇到比较好的团队,运用到该框架,会继续研究相关的知识,暂时我就将它放在一边了。