Inter context communication in Entitas 0.39.0 - OneYoungMean/Entitas-CSharp-OYM GitHub Wiki

Inter-context communication in Entitas 0.39.0(待更新)

警告:应该注意的是,这个例子是为网络游戏派生的,其中输入是通过服务器传输的。

我们想要的是能够在一个玩家输入上下文中发生一些事情,这些上下文在网络中传播以影响其他玩家和他们自己的游戏上下文。下面详细介绍了一个简单的按钮点击过程,其中按钮小部件将用户操作传输到权利系统和行为中。

为简洁起见,省略了网络代码。如果这只是一个单人游戏,可以推导出一种更简单的方法。 Entitas对于网络游戏的一个有趣特性是它允许确定性地创建实体,这意味着每个玩家具有相同的呈现,仅需要通过网络传输输入以使系统保持步调。

在这个例子当中我们有两个实体:

  • Game
  • Input

整个过程分为:

  • 用户在本地按下按钮
  • GameObject 捕捉到点击的信息,通过网络发送事件,在本地生成实体,然后在另外一个地方生成实体
  • 新实体通过收集器触发反应系统行为
  • 输入被翻译成游戏实体

Components

因此,我们需要一个处理按钮上的单击操作的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;
}

Auto-generated Context Extensions(自动生成的上下文扩展)

因为我们通过[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);
    }
}

Unity GameObjects

所以现在我们有一组生成的组件,我们需要触发它们。如果我们将一个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());
        }
}

Input Context Systems

然后在您的功能中的某个位置,您将拥有一个系统,该系统会对具有单击组件的输入实体做出反应:

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);
        }
    }
}

Game Context Systems

现在,在游戏上下文中已经更改了实体,系统可以对更改做出反应。 在此网络示例中,输入与游戏状态分离,以便本地系统负责更改对象的视图和状态。

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);
        }
    }    
    
}

你的按钮现在应该改变颜色。

⚠️ **GitHub.com Fallback** ⚠️