Inter context communication in Entitas 0.39.0 - OneYoungMean/Entitas-CSharp-OYM GitHub Wiki
警告:应该注意的是,这个例子是为网络游戏派生的,其中输入是通过服务器传输的。
我们想要的是能够在一个玩家输入上下文中发生一些事情,这些上下文在网络中传播以影响其他玩家和他们自己的游戏上下文。下面详细介绍了一个简单的按钮点击过程,其中按钮小部件将用户操作传输到权利系统和行为中。
为简洁起见,省略了网络代码。如果这只是一个单人游戏,可以推导出一种更简单的方法。 Entitas对于网络游戏的一个有趣特性是它允许确定性地创建实体,这意味着每个玩家具有相同的呈现,仅需要通过网络传输输入以使系统保持步调。
在这个例子当中我们有两个实体:
- Game
- Input
整个过程分为:
- 用户在本地按下按钮
- GameObject 捕捉到点击的信息,通过网络发送事件,在本地生成实体,然后在另外一个地方生成实体
- 新实体通过收集器触发反应系统行为
- 输入被翻译成游戏实体
因此,我们需要一个处理按钮上的单击操作的Component。
using Entitas;
[Input]
public sealed class ClickComponent : IComponent {
public bool state;
}
因此,我们需要一个处理按钮上的单击操作的Component。
using Entitas;
using Entitas.CodeGenerator.Api;
[Input]
public sealed class InputIdComponent : IComponent{
[EntityIndex]
public string value;
}
[Game]
public sealed class NameComponent : IComponent{
[EntityIndex]
public string value;
}
Also we need some entites that are the buttons in Entitas:
[Game]
public sealed class ButtonStateComponent : IComponent{
public bool buttonState;
}
因为我们通过[EntityIndex]标签使用离散索引实体,所以代码生成器会创建一些方法来查找所有这些实体:
using System.Collections.Generic;
public static class ContextsExtensions {
public static HashSet<InputEntity> GetEntitiesWithInputId(this InputContext context, string value) {
return ((Entitas.EntityIndex<InputEntity, string>)context.GetEntityIndex(Contexts.InputId)).GetEntities(value);
}
public static HashSet<GameEntity> GetEntitiesWithName(this GameContext context, string value) {
return ((Entitas.EntityIndex<GameEntity, string>)context.GetEntityIndex(Contexts.Name)).GetEntities(value);
}
}
所以现在我们有一组生成的组件,我们需要触发它们。如果我们将一个MonoBehavi附加到一个实体上,该实体可以通过以下方式处理来自Unity的标准鼠标输入(请参阅查看中的组件和系统) Match-One example project)
这是一个将组件作为参数传递的事件:
using UnityEngine.Events;
public class ClickEvent : UnityEvent<ButtonWidget> { }
这是可以在Unity Editor中配置的小部件,它应该位于实体视图游戏对象上.
using Entitas.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using System;
using UnityEngine.Events;
public class ButtonWidget : MonoBehaviour, IClickable
{
[SerializeField]
protected Color onClickColour = Color.green;
protected Color originalOffColour;
public ClickEvent OnClick;
bool _state = false;
void OnEnable()
{
if (OnClick == null)
OnClick = new ClickEvent();
OnClick.AddListener(FindLocalNetworkPlayer().HandleClick);
originalOffColour = offColour;
}
void OnDisable()
{
if (OnClick != null)
OnClick.RemoveListener(FindLocalNetworkPlayer().HandleClick);
}
public string GetId()
{
return GetGameEntity().name.value;
}
public GameEntity GetGameEntity(){
return gameObject.GetEntityLink().entity as GameEntity;
}
public bool GetState()
{
return GetGameEntity().buttonState.buttonState;
}
public void OnPointerClick(PointerEventData eventData)
{
if (OnClick != null)
OnClick.Invoke(this);
}
public void SetState(bool state)
{
_state = state;
if (gameObject.GetComponent<Renderer>())
{
if (_state)
{
gameObject.GetComponent<Renderer>().material.color = onClickColour;
offColour = onClickColour;
}
else
{
offColour = originalOffColour;
gameObject.GetComponent<Renderer>().material.color = offColour;
}
}
}
}
现在我们有一个可以在本地处理输入的游戏对象,我们需要通过网络传输它并在所有玩家上创建新实体。所以让我们想象一下,这里的HandleClick方法是在每个玩家本地系统上发生的一些花哨的网络代码,就像通过魔法一样。
public class NetworkPlayer : NetworkBehavior{
// This happens locally for each player!
public void HandleClick(ButtonWidget btn)
{
var e = Contexts.sharedInstance.input.CreateEntity();
e.AddInputId(btn.GetButtonId());
e.AddClick(btn.GetClickState());
}
}
然后在您的功能中的某个位置,您将拥有一个系统,该系统会对具有单击组件的输入实体做出反应:
using System.Collections.Generic;
using Entitas;
public sealed class AddClickSystem : ReactiveSystem<InputEntity>
{
readonly InputContext _context;
public AddClickSystem(Contexts contexts) : base(contexts.input)
{
_context = contexts.input;
}
protected override Collector<InputEntity> GetTrigger(IContext<InputEntity> context)
{
return context.CreateCollector(InputMatcher.Click, GroupEvent.Added);
}
protected override bool Filter(InputEntity entity)
{
return entity.hasInputId;
}
protected override void Execute(List<InputEntity> entities)
{
foreach (var e in entities)
{
// Find the game entity with the name of the input id.
var obj = Contexts.sharedInstance.game.GetEntitiesWithName(e.inputId.value).SingleEntity();
if (obj != null && obj.hasButtonState)
{
obj.ReplaceButtonState(!obj.buttonState.buttonState);
}
e.RemoveClick();
}
}
}
该系统在发出任何输入后进行整理:
using System.Collections.Generic;
using Entitas;
using System.Linq;
using UnityEngine;
using System;
public sealed class RemoveClickSystem : ReactiveSystem<InputEntity> {
readonly InputContext _context;
public RemoveClickSystem(Contexts contexts) : base(contexts.input) {
_context = contexts.input;
}
protected override Collector<InputEntity> GetTrigger(IContext<InputEntity> context) {
return context.CreateCollector(InputMatcher.Click, GroupEvent.Removed);
}
protected override bool Filter(InputEntity entity) {
return entity.isEnabled;
}
protected override void Execute(List<InputEntity> entities) {
foreach(var e in entities) {
Contexts.sharedInstance.input.DestroyEntity(e);
}
}
}
现在,在游戏上下文中已经更改了实体,系统可以对更改做出反应。 在此网络示例中,输入与游戏状态分离,以便本地系统负责更改对象的视图和状态。
using System;
using System.Collections.Generic;
using Entitas;
public sealed class ButtonSystem : ReactiveSystem<GameEntity>
{
readonly GameContext _context;
public ButtonSystem(Contexts contexts) : base(contexts.game){
_context = contexts.game;
}
protected override Collector<GameEntity> GetTrigger(IContext<GameEntity> context)
{
return context.CreateCollector(GameMatcher.ButtonState);
}
protected override bool Filter(GameEntity entity)
{
return entity.hasButtonState && entity.hasView;
}
protected override void Execute(List<GameEntity> entities)
{
foreach(var e in entities){
// Let the editor configured game object handle colour changes
if(e.view.gameObject.GetComponent<Widgets.ButtonWidget>())
e.view.gameObject.GetComponent<Widgets.ButtonWidget>().SetState(e.buttonState.buttonState);
}
}
}
你的按钮现在应该改变颜色。