MultiReactiveSystem Tutorial - OneYoungMean/Entitas-CSharp-OYM GitHub Wiki
前言
Entitas版本0.42引入了一个要求很高的新功能 - 能够使用单个反应系统同时对来自多个环境的实体进行操作。这对于游戏的每个部分都需要的反应系统非常有用。例如,用于销毁实体的系统在每个上下文中都是有用的。查看系统通常也非常通用,并且在多个上下文中很有用。本教程将指导您完成第一个MultiReactiveSystem的实现过程。
第一步
创建一个新的untiy项目,安装Entitas库。使用Entitas偏好设置一个文件夹来存放生成的代码,并定义三个上下文(Game, Input, Ui)。点击generate可以生成基于Context类与feature类或者独立的Game, Input 与 Ui的上下文。如果你不知道如何设置,也不知道这意味着什么。请参考早期的教材 Hello World! Tutorial.
阅读这篇教程的前提是您知道如何创建GameController以实际运行您创建的系统。如果您对此不熟悉,请再看一下 Hello World! or Simple View and Movement 等教程
简单的多重删除系统
我们将介绍的第一个示例是DestroySystem,它可以在我们所有的三种系统中运行。第一步我们需要一个component定义在其他的上下文当中。这是一个简单的component标签,如果如果缺少Ui的相关上下文,则可以通过Entitas首选项添加Ui上下文。
Components.cs
using Entitas;
using UnityEngine;
[Game, Input, Ui]
public class DestroyedComponent : IComponent
{
}
创建多重反应系统的第一步是定义一个接口,允许我们调用生成的方法,而无需知道实体来自哪个上下文。Entitas代码生成器将查找具有多个上下文标记的所有组件,并为每个组件生成一个接口。从上面的组件中,生成器将创建一个IDestroyedEntity
接口。实现IDestroyedEntity
的类必须具有.isDestroyed
属性。
从这里我们可以定义一个界面,上面写着“我是一个实体,我可以拥有一个DestroyedComponent”多重反应系统在此接口上运行,而不是直接在GameEntity,UiEntity或InputEntity上运行。
一旦定义了这个接口,我们需要告诉编译器我们所有不同的,特定于上下文的实体都将实现这个接口。然后可以将它们安全地送入系统。
MultiDestroySystem.cs
using System.Collections.Generic;
using Entitas;
using Entitas.Unity;
using UnityEngine;
// IDestroyed: "I'm an Entity, I can have a DestroyedComponent"
public interface IDestroyableEntity : IEntity, IDestroyedEntity { }
// tell the compiler that our context-specific entities implement IDestroyed
public partial class GameEntity : IDestroyableEntity { }
public partial class InputEntity : IDestroyableEntity { }
public partial class UiEntity : IDestroyableEntity { }
// inherit from MultiReactiveSystem using the IDestroyed interface defined above
public class MultiDestroySystem : MultiReactiveSystem<IDestroyableEntity, Contexts>
{
// base class takes in all contexts, not just one as in normal ReactiveSystems
public MultiDestroySystem(Contexts contexts) : base(contexts)
{
}
// return an ICollector[] with a collector from each context
protected override ICollector[] GetTrigger(Contexts contexts)
{
return new ICollector[] {
contexts.game.CreateCollector(GameMatcher.Destroyed),
contexts.input.CreateCollector(InputMatcher.Destroyed),
contexts.ui.CreateCollector(UiMatcher.Destroyed)
};
}
protected override bool Filter(IDestroyableEntity entity)
{
return entity.isDestroyed;
}
protected override void Execute(List<IDestroyableEntity> entities)
{
foreach (var e in entities)
{
Debug.Log("Destroyed Entity from " + e.contextInfo.name + " context");
e.Destroy();
}
}
}
现在运行你的游戏并在你的unity hierarchy中找到上下文观察者(在DontDestroyOnLoad下)。查找上下文,创建实体,然后将Destroyed组件添加到其中。观察控制台中的消息。在每个上下文中尝试这些操作,现在你有一个全方位控制的销毁系统来统治它们!
MultiDestroySystem with View(渲染中的多重系统)
实体破坏通常需要统一销毁View GameObject。为了展示这个用例,我们还将定义一个简单的'ViewComponent'。有关权利中的视图的更多信息,请参阅 Simple View and Movement Tutorial. 一旦 components.cs 保存,你就需要返回untiy,生成新的代码 (ctrl-shift-G).
Components.cs (接上)
[Game, Input, Ui]
public class ViewComponent : IComponent
{
public GameObject gameObject;
}
要对Views执行操作,我们需要IDestroyableEntity
接口来实现IViewEntity
以及IDestroyedEntity
。 IView
实体实现了属性hasView
和view
,以及我们用来操作它的“Add”,“Remove”和“Replace”方法。我们将其添加到接口定义中。
MultiDestroySystem.cs
using System.Collections.Generic;
using Entitas;
using Entitas.Unity;
using UnityEngine;
// IDestroyed: "I'm an Entity, I can have a DestroyedComponent AND I can have a ViewComponent"
public interface IDestroyableEntity : IEntity, IDestroyedEntity, IViewEntity { }
// tell the compiler that our context-specific entities implement IDestroyed
public partial class GameEntity : IDestroyableEntity { }
public partial class InputEntity : IDestroyableEntity { }
public partial class UiEntity : IDestroyableEntity { }
// inherit from MultiReactiveSystem using the IDestroyed interface defined above
public class MultiDestroySystem : MultiReactiveSystem<IDestroyableEntity , Contexts>
{
public MultiDestroySystem(Contexts contexts) : base(contexts)
{
}
protected override ICollector[] GetTrigger(Contexts contexts)
{
return new ICollector[] {
contexts.game.CreateCollector(GameMatcher.Destroyed),
contexts.input.CreateCollector(InputMatcher.Destroyed),
contexts.ui.CreateCollector(UiMatcher.Destroyed)
};
}
protected override bool Filter(IDestroyableEntity entity)
{
return entity.isDestroyed;
}
protected override void Execute(List<IDestroyableEntity> entities)
{
foreach (var e in entities)
{
// now we can access the ViewComponent and the DestroyedComponent
if (e.hasView)
{
GameObject go = e.view.gameObject;
go.Unlink();
Object.Destroy(go);
}
Debug.Log("Destroyed Entity from " + e.contextInfo.name + " context");
e.Destroy();
}
}
}
此示例显示如何编写接口以允许您从多个响应系统中访问多个组件。但是请注意,必须在相同的上下文中定义这些组件才能在多系统中使用它们。比方说我们仅在[Game]和[Ui]中定义了View组件,那么InputEntity
将无法实现IViewEntity
,因为它不会实现与视图组件关联的生成方法。
Performing context-specific actions in multi-reactive systems(在多反应系统中执行特定于上下文的操作)
有时我们必须在系统中使用特定的上下文 - 如果我们需要从中访问一个方法,或者将它的引用传递给另一个对象。例如,在向Unity中的实体添加ViewComponent时,通常通过调用gameObject.Link(entity,context)
来使用EntityLink功能。在上面的示例中,我们没有方便地引用进入系统的每个实体的特定上下文,因此我们无法将其传递给link方法。然而,实体带有一个contextInfo
字段,我们可以用它来查找它所属的上下文。
通过使用扩展方法,我们可以使用其名称,编写检索上下文引用的功能,我们可以在多反应系统中使用这种扩展来获得对每个实体的“IContext”的引用。请注意,IContext
并没有实现每个单独上下文所做的所有方法,我们需要在下面的系统中调用其链接的方法。
这是一个静态类,它将上下文的字符串名称中的映射存储到其“IContext”引用中。字典被懒惰地填充(首次需要时?)。 Contexts
类的扩展方法返回与传递的名称相关联的IContext
。
关于link()的补充:这段代码的作用就是把gameobject与entity内在的联系起来,如果你在sence中选中你的gameobject,这段代码会帮你在hierarchy中给出相对应的entity,这是Debug过程中必不可少的一部分。同时还有很多其他的内在联系,作者的建议是,如果你试图addview一个go,一定不要忘了给他一个link。
ContextExtentions.cs
using System.Collections.Generic;
using Entitas;
public static class ContextExtentions
{
private static readonly Dictionary<string, IContext> _contextsLookup = new Dictionary<string, IContext>();
public static IContext GetContextByName(this Contexts contexts, string name)
{
if (_contextsLookup.Count == 0) SetContextsDictionary(contexts);
return _contextsLookup[name];
}
private static void SetContextsDictionary(Contexts contexts)
{
foreach (var context in contexts.allContexts)
{
_contextsLookup.Add(context.contextInfo.name, context);
}
}
}
在这个例子里我们是多个component,一个简单的标签用于表示我们希望添加一个视图到这个实体上。
Components.cs (contd.)
[Game, Input, Ui]
public class AssignViewComponent : IComponent
{
}
现在我们可以编写一个MultiReactiveSystem来为任何上下文中的实体添加视图,就像我们在 Simple View and Movement中做的那样,我们将创建父GameObjects来存储统一层次结构中的组织视图。我们可以使用字典为每个视图找到合适的父级。
MultiAddViewSystem.cs
using System.Collections.Generic;
using Entitas;
using Entitas.Unity;
using UnityEngine;
// IViewEntity: "I am an Entity, I can have an AssignViewComponent and a ViewComponent"
public interface IViewableEntity : IAssignViewEntity, IViewEntity, IEntity {}
public partial class GameEntity : IViewableEntity {}
public partial class InputEntity : IViewableEntity {}
public partial class UiEntity : IViewableEntity {}
public class MultiAddViewSystem: MultiReactiveSystem<IViewableEntity, Contexts>
{
private readonly Transform _topViewContainer = new GameObject("Views").transform;
private readonly Dictionary<string, Transform> _viewContainers = new Dictionary<string, Transform>();
private readonly Contexts _contexts;
public MultiAddViewSystem(Contexts contexts) : base(contexts)
{
_contexts = contexts;
// create a view container for each context name
foreach (var context in contexts.allContexts)
{
string contextName = context.contextInfo.name;
Transform contextViewContainer = new GameObject(contextName + " Views").transform;
contextViewContainer.SetParent(_topViewContainer);
_viewContainers.Add(contextName, contextViewContainer);
}
}
protected override ICollector[] GetTrigger(Contexts contexts)
{
return new ICollector[] {
contexts.game.CreateCollector(GameMatcher.AssignView),
contexts.input.CreateCollector(InputMatcher.AssignView),
contexts.ui.CreateCollector(UiMatcher.AssignView)
};
}
protected override bool Filter(IViewableEntity entity)
{
return entity.isAssignView && !entity.hasView;
}
protected override void Execute(List<IViewableEntity> entities)
{
foreach (var e in entities)
{
string contextName = e.contextInfo.name;
GameObject go = new GameObject(contextName + " View");
go.transform.SetParent(_viewContainers[contextName]);
e.AddView(go);
go.Link(e, _contexts.GetContextByName(contextName));
e.isAssignView = false;
}
}
}
这里我们使用上下文名称来访问两个字典,一个用于提供我们可以传递给实体链接方法的IContext
的引用,另一个用于查找正确的视图容器以设置为我们的新视图GameObject的父级。
结论
多反应系统有助于消除对许多独立系统的需求,每个系统中的代码完全相同。如果没有它们,我们需要一个GameDestroySystem,一个UiDestroySystem和一个InputDestroySystem - 每个都可以运行完全相同的代码,但具有不同的特定于上下文的类型(GameMatcher, GameContext, GameEntity etc).MultiReactiveSystems可以帮助您 深化 你的代码库。