介绍
这一次,在Unity学习MessagePipe的同时,我会以备忘录和评论的形式给自己总结一下它的内容。
什么是消息管道
欲了解更多信息,请阅读此处的官方文章。
简而言之,MessagePipe 就是这样一个库。
-
提供
Pub/Sub模式实现的库 - 可在本地使用的其他消息传递机制
- 各种用途
- 内存驱动器
- 服务器和客户端之间的通信(与
MagicOnion和SignalR合作) - 进程间通信 (
IPC) - GUI 实现中
View-ViewModel之间的消息传递 -
MediatorCQRS的模式实现 -
Zenject.SignalBus和UniRx.MessageBroker的统一替换
- 异常出色的性能
-
必须与
DIContainer一起使用
简而言之,它是一个专门从事“消息传递”的库。
什么是发布/订阅模式
Pub/Sub 模式也称为Pub/Sub 消息传递模型。
这是一种消息(事件)传输模式,主要由以下三个概念组成。
-
Publisher:发消息 -
Subscriber: 接收消息 -
Broker:管理传出消息并将其传递给接收者的机制
与一般的事件实现不同,Pub/Sub 模式具有“对象之间耦合更松散”和“消息传输更容易协调”的特点。
松耦合
在应用程序内部,通用事件实现(event 和Observable)知道消息发布者并订阅它,因此它们是紧密耦合的。
当然,实现对象可以通过将接口夹在中间来松散耦合,但重点仍然是必须引用特定的接口。
另一方面,Pub/Sub 模式允许您遇到这样的情况,如果您知道Subscriber,您可以收到消息。
换句话说,您可以轻松地发送/接收所需的消息,而无需知道路由中隐藏了哪种实现。
通过这种方式,使用Pub/Sub 模式可以实现灵活的消息传输,而无需了解对象之间的关系。
很重要的一点
能够松耦合是一个优势,但也存在以下问题。
- 很容易编写忽略“关键设计约束”(例如层和架构)的代码
- 难以跟踪哪些对象发送/接收消息(潜在泄漏)
易于协调消息传递
在Pub/Sub 模式中,Publisher 和Subscriber 不依赖于任何特定对象。换句话说,“任何人都可以发送消息”和“任何人都可以订阅消息”。Pub/Sub 模式易于扩展,例如以后增加或减少Publisher 或Subscriber。
Pub/Sub 模式的另一个特点是,您可以通过调整中继消息传输的对象(Broker)的实现来调整消息传输本身的机制。
(虽然MessagePipe隐藏了Broker本身,但是通过选择Publisher/Subscriber接口可以在一定程度上改变行为。)
与 UniRx 的比较
类似于MessagePipe 的现有Unity 库是“UniRx 中的Observable(或MessageBroker)”。Observable 具有非常高的表达力和广泛的用途,但在很多情况下它往往以更简单的模式结束,并且存在过于冗长的缺点。
换句话说,UniRx 的主要缺点是场景复杂,应该简单地处理。
MessagePipe 是一个满足UniRx 不擅长的“简单消息传输”需求的库。
而是MessagePipe更专注于“易于处理的消息”,因此可以根据情况比UniRx更灵活地处理。
(由于可以连接MessagePipe⇢UniRx,如果需要的话,转换一下就可以了)
另外,UniRx 只能用于“同一进程内的消息传输”。
但是MessagePipe 允许您跨进程和通过网络传递消息。
如果说UniRx是一个“追求本地方便的库”,那么MessagePipe可以说是一个“可以以更简单的形式广泛使用的库”。
实际触摸
讲了很久,暂时先介绍一下,摸一摸。
在 Unity 中安装
通过 UPM 从PackageManager 安装。
将OpenUPM 的 URL 添加到 Editor -> ProjectSettings,如下所示:
- 姓名:
OpenUPM - 网址:
https://package.openupm.com - 范围
com.cysharp-
jp.hadashikick.vcontainer
既然OpenUPM已经可用,我们就从PackageManager中介绍以下四个。
-
消息管道:
MessagePipe的正文 -
MessagePipe.VContainer:需要将
MessagePipe连接到VContainer -
虚拟容器:
DI ContainerUnity 框架 -
单任务:
MessagePipe依赖的异步处理库
准备工作现已完成。
同步并尝试一对一移动
然后,作为最简单的模式,让我们尝试使用“同步”和“一对一”发送消息。
这一次,我们将“输入事件”作为消息传输并移动 Cube。
(传输键盘事件并尝试实现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 设置
上述实现完成后,通过VContainer 和DI 定义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 的最终状态
GameObject 和 LifetimeScope 是在场景中准备好的,整个场景从这里初始化。
试着移动
通过这种方式,消息通过MessagePipe和MessageBroker提供的各种接口传输。
这次尝试了“同步”和“一对一”,但是消息处理可以做到“异步”,可以实现“多对多”。
尝试一对多
尝试为一个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);
}
// ---------変更点ここまで----------
});
}
}
}
(生成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