介绍

这一次,在Unity学习MessagePipe的同时,我会以备忘录和评论的形式给自己总结一下它的内容。

什么是消息管道

欲了解更多信息,请阅读此处的官方文章。

简而言之,MessagePipe 就是这样一个库。

  • 提供Pub/Sub 模式实现的库
  • 可在本地使用的其他消息传递机制
  • 各种用途
    • 内存驱动器
    • 服务器和客户端之间的通信(与MagicOnionSignalR合作)
    • 进程间通信 (IPC)
    • GUI 实现中View-ViewModel 之间的消息传递
    • MediatorCQRS 的模式实现
    • Zenject.SignalBusUniRx.MessageBroker 的统一替换
  • 异常出色的性能
  • 必须与DIContainer 一起使用

简而言之,它是一个专门从事“消息传递”的库。

什么是发布/订阅模式

Pub/Sub 模式也称为Pub/Sub 消息传递模型。

这是一种消息(事件)传输模式,主要由以下三个概念组成。

  • Publisher:发消息
  • Subscriber : 接收消息
  • Broker :管理传出消息并将其传递给接收者的机制

与一般的事件实现不同,Pub/Sub 模式具有“对象之间耦合更松散”和“消息传输更容易协调”的特点。

松耦合

在应用程序内部,通用事件实现(eventObservable)知道消息发布者并订阅它,因此它们是紧密耦合的。

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

当然,实现对象可以通过将接口夹在中间来松散耦合,但重点仍然是必须引用特定的接口。

【Unity】MessagePipeを触ってみる1 ~概要・導入編~


另一方面,Pub/Sub 模式允许您遇到这样的情况,如果您知道Subscriber,您可以收到消息。
换句话说,您可以轻松地发送/接收所需的消息,而无需知道路由中隐藏了哪种实现。

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

通过这种方式,使用Pub/Sub 模式可以实现灵活的消息传输,而无需了解对象之间的关系。

很重要的一点

能够松耦合是一个优势,但也存在以下问题。

  • 很容易编写忽略“关键设计约束”(例如层和架构)的代码
  • 难以跟踪哪些对象发送/接收消息(潜在泄漏)

易于协调消息传递

Pub/Sub 模式中,PublisherSubscriber 不依赖于任何特定对象。换句话说,“任何人都可以发送消息”和“任何人都可以订阅消息”。
Pub/Sub 模式易于扩展,例如以后增加或减少PublisherSubscriber

Pub/Sub 模式的另一个特点是,您可以通过调整中继消息传输的对象(Broker)的实现来调整消息传输本身的机制。
(虽然MessagePipe隐藏了Broker本身,但是通过选择Publisher/Subscriber接口可以在一定程度上改变行为。)

与 UniRx 的比较

类似于MessagePipe 的现有Unity 库是“UniRx 中的Observable(或MessageBroker)”。
Observable 具有非常高的表达力和广泛的用途,但在很多情况下它往往以更简单的模式结束,并且存在过于冗长的缺点。
换句话说,UniRx 的主要缺点是场景复杂,应该简单地处理。

MessagePipe 是一个满足UniRx 不擅长的“简单消息传输”需求的库。
而是MessagePipe更专注于“易于处理的消息”,因此可以根据情况比UniRx更灵活地处理。

(由于可以连接MessagePipeUniRx,如果需要的话,转换一下就可以了)

另外,UniRx 只能用于“同一进程内的消息传输”。
但是MessagePipe 允许您跨进程和通过网络传递消息。

如果说UniRx是一个“追求本地方便的库”,那么MessagePipe可以说是一个“可以以更简单的形式广泛使用的库”。

实际触摸

讲了很久,暂时先介绍一下,摸一摸。

在 Unity 中安装

通过 UPM 从PackageManager 安装。

OpenUPM 的 URL 添加到 Editor -> ProjectSettings,如下所示:

  • 姓名:OpenUPM
  • 网址:https://package.openupm.com
  • 范围
    • com.cysharp
    • jp.hadashikick.vcontainer

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

既然OpenUPM已经可用,我们就从PackageManager中介绍以下四个。

  • 消息管道: MessagePipe的正文
  • MessagePipe.VContainer:需要将MessagePipe 连接到VContainer
  • 虚拟容器DI ContainerUnity 框架
  • 单任务: MessagePipe 依赖的异步处理库

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

准备工作现已完成。

同步并尝试一对一移动

然后,作为最简单的模式,让我们尝试使用“同步”和“一对一”发送消息。
这一次,我们将“输入事件”作为消息传输并移动 Cube。

【Unity】MessagePipeを触ってみる1 ~概要・導入編~
(传输键盘事件并尝试实现Cube 移动)

要发送的消息内容

定义一个结构 InputParams 并将其作为消息发送。

using System;
using UnityEngine;

namespace MessagePipeSample.InputProvider
{
    /// <summary>
    /// ブロードキャストするメッセージ
    /// 入力情報
    /// </summary>
    public readonly struct InputParams : IEquatable<InputParams>
    {
        /// <summary>
        /// ジャンプフラグ
        /// </summary>
        public bool IsJump { get; }

        /// <summary>
        /// 移動操作
        /// </summary>
        public Vector3 Move { get; }
        
        public InputParams(bool isJump, Vector3 move)
        {
            IsJump = isJump;
            Move = move;
        }

        public bool Equals(InputParams other)
        {
            return IsJump == other.IsJump && Move.Equals(other.Move);
        }

        public override bool Equals(object obj)
        {
            return obj is InputParams other && Equals(other);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(IsJump, Move);
        }
    }
}

发件人

InputEventProvider 定义为发送消息的实体。
让这个InputEventProvider 使用IPublisher<InputParams> 来传递消息。

using MessagePipe;
using UnityEngine;
using VContainer.Unity;

namespace MessagePipeSample.InputProvider
{
    public sealed class InputEventProvider : ITickable
    {
        /// <summary>
        /// MessagePipeにメッセージを流す用のインタフェース
        /// </summary>
        private readonly IPublisher<InputParams> _inputPublisher;

        public InputEventProvider(IPublisher<InputParams> inputPublisher)
        {
            _inputPublisher = inputPublisher;
        }

        // 毎フレーム実行
        public void Tick()
        {
            // 入力状態を監視
            var isJump = Input.GetKey(KeyCode.Space);
            var axis = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));

            // メッセージを作成
            var inputParams = new InputParams(isJump, axis);

            // メッセージ送信
            _inputPublisher.Publish(inputParams);
        }
    }
}

接收者

MoveCube定义为最终接收和处理消息的对象。
将此附加到Cube GameObject 并使其成为预制件。

using Cysharp.Threading.Tasks;
using MessagePipe;
using UnityEngine;
using VContainer;

namespace MessagePipeSample.InputProvider
{
    public sealed class MoveCube : MonoBehaviour
    {
        /// <summary>
        /// MessagePipeからメッセージを受け取る用インタフェース
        /// </summary>
        [Inject] private ISubscriber<InputParams> _inputEventSubscriber;

        // 各種フィールド
        private CharacterController _characterController;
        private readonly float JumpSpeed = 3.0f;
        private readonly float MoveSpeed = 3.0f;

        private void Start()
        {
            _characterController = GetComponent<CharacterController>();

            // 入力イベントの受信を開始する
            _inputEventSubscriber.Subscribe(OnInputEventReceived)
                // MonoBehaviourに寿命を紐づける(これはUniTaskの機能)
                .AddTo(this.GetCancellationTokenOnDestroy());
        }

        /// <summary>
        /// 入力イベントを処理する
        /// </summary>
        private void OnInputEventReceived(InputParams input)
        {
            var moveVelocity = new Vector3(0, _characterController.velocity.y, 0);

            if (input.IsJump && _characterController.isGrounded)
            {
                moveVelocity += Vector3.up * JumpSpeed;
            }

            moveVelocity += input.Move * MoveSpeed;

            moveVelocity += Physics.gravity * Time.deltaTime;

            _characterController.Move(moveVelocity * Time.deltaTime);
        }
    }
}

进行 DI 设置

上述实现完成后,通过VContainerDI 定义VContainer DI

using MessagePipe;
using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace MessagePipeSample.InputProvider
{
    public class GameLifetimeScope : LifetimeScope
    {
        // MoveCubeのPrefabへの参照
        [SerializeField] private MoveCube _moveCubePrefab;

        protected override void Configure(IContainerBuilder builder)
        {
            // MessagePipeの設定
            var options = builder.RegisterMessagePipe();
            
            // InputParamsを伝達できるように設定する
            builder.RegisterMessageBroker<InputParams>(options);
            
            // InputEventProviderを起動
            builder.RegisterEntryPoint<InputEventProvider>(Lifetime.Singleton);

            // MoveCubeをDIしながらInstantiate
            builder.RegisterBuildCallback(resolver =>
            {
                resolver.Instantiate(_moveCubePrefab);
            });
        }
    }
}

Unity 的最终状态

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

GameObjectLifetimeScope 是在场景中准备好的,整个场景从这里初始化。

试着移动

【Unity】MessagePipeを触ってみる1 ~概要・導入編~

通过这种方式,消息通过MessagePipeMessageBroker提供的各种接口传输。
这次尝试了“同步”和“一对一”,但是消息处理可以做到“异步”,可以实现“多对多”。

尝试一对多

尝试为一个InputEventProvider(发送)设置多个MoveCube(接收)。
即便如此,几乎没有部分可以篡改代码。这是因为MessagePiep 从一开始就适应了邮件收件人的增长。

using MessagePipe;
using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace MessagePipeSample.InputProvider
{
    public class GameLifetimeScope : LifetimeScope
    {
        [SerializeField] private MoveCube _moveCubePrefab;

        protected override void Configure(IContainerBuilder builder)
        {
            // MessagePipeの設定
            var options = builder.RegisterMessagePipe();

            // InputParamsを伝達できるように設定する
            builder.RegisterMessageBroker<InputParams>(options);

            // InputEventProviderを起動
            builder.RegisterEntryPoint<InputEventProvider>(Lifetime.Singleton);

            // MoveCubeをDIしながらInstantiate
            builder.RegisterBuildCallback(resolver =>
            {
                // ---------変更点ここから-----------
                
                // 3つ並べて生成する
                for (int i = 0; i < 3; i++)
                {
                    var cube = resolver.Instantiate(_moveCubePrefab);
                    cube.transform.position = Vector3.forward * (i * 2f);
                }
                
                // ---------変更点ここまで----------
            });
        }
    }
}

【Unity】MessagePipeを触ってみる1 ~概要・導入編~
(生成3个MoveCube,每个注入ISubscriber,执行Subscribe

尝试连接到 UniRx

您可以通过调用ISubscriber<T>.AsObservable() 转换IObservable<T>
只要你能转换IObservable<T>,你就可以用UniRx为所欲为。

// 入力イベントの受信を開始する
_inputEventSubscriber
    .AsObservable()         // ↑ ここまでMessagePipe
    .DistinctUntilChanged() // ↓ ここからUniRx
    .TakeUntilDestroy(this)
    .Subscribe(OnInputEventReceived);

概括

这次介绍了MessagePipe的概述,如何介绍,以及一个简单的实现示例。
从下一次开始,我们计划接触“异步使用”、“EventFactory”和“Analyzer”。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308626808.html

相关文章: