版本: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 context) {
_collector = GetTrigger(context);
_buffer = new List();
}
我们大概可以这么理解:创建了一个收集器,用于过滤Entity,将需要的Entity放入Group中(作用其实和Filter有点类似,Filter算是最终检查,完全可以不写直接返回true)。
而在复写时,由Context来创建该Collector:
/// Creates an Collector.
public static Collector CreateCollector(this IContext context, IMatcher matcher, GroupEvent groupEvent = GroupEvent.Added)
where TEntity : class, IEntity, new() {
return new Collector(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的研究暂时就到这里了,我其实算是纠结了比较久的,因为这个框架确实很不错,只是现在用的人比较少,而且学习起来确实有一定的难度,在项目组内也难以推广。
如果以后遇到比较好的团队,运用到该框架,会继续研究相关的知识,暂时我就将它放在一边了。