MessagePipe使ってみる

メッセージングライブラリのMessagePipeがリリースされました。

qiita.com

github.com

いろいろ書いてあるんですが、動的にいろいろ動かしてみたいのでアクション寄りでいじってみたいと思います。

DIとかUnitaskとか知らない状態で雰囲気でやっているのでご了承ください。

リポジトリはこちらです github.com

f:id:Piffett:20210523195037p:plain

というわけで2Dでいつもの画面を起動します。

MessagePipeには内蔵で軽量のDIコンテナライブラリが付属しているらしいですが、今回は良さげらしいVCointainerを使ってみます。

UniTaskなどいろいろ依存ライブラリがあるので、Packages/manifest.jsonに以下のもろもろを追加します。

{
  "dependencies": {
    "...": "..."
    "com.unity.modules.vr": "1.0.0",
    "com.unity.modules.wind": "1.0.0",
    "com.unity.modules.xr": "1.0.0",
    "jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.8.2",
    "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.2.5",
    "com.cysharp.messagepipe": "https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe#1.4.0",
    "com.cysharp.messagepipe.vcontainer": "https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe.VContainer#1.4.0"
  }
}

Scriptsフォルダを作って以下のC#ファイルを用意します。

MainLifetimeScope.cs

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

public class MainLifetimeScope : LifetimeScope
{

    protected override void Configure(IContainerBuilder builder)
    {
        var options = builder.RegisterMessagePipe(/* configure option */);
        builder.RegisterMessageBroker<TheWorld>(options);
    }
}

public class TheWorld
{
    public float speed;
}

ファイル名の末尾が「LifetimeScope」だとLifetimeScopeから継承した実装のファイルが用意されます。今回はTheWorldという名前のclassを作って、このデータを通知したいと思います。

Scene内に空のGameObjectを使って先ほど作ったMainLifetimeScopeをアタッチします。

f:id:Piffett:20210523204701p:plain

今回はシューティングゲームでも作ろうと思うので、Playerを作ります。これは上下左右に動くだけです。

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            this.transform.Translate(-0.02f, 0.0f, 0.0f);
        }
        if (Input.GetKey(KeyCode.RightArrow))
        {
            this.transform.Translate(0.02f, 0.0f, 0.0f);
        }
        if (Input.GetKey(KeyCode.UpArrow))
        {
            this.transform.Translate(0.0f, 0.02f, 0.0f);
        }
        if (Input.GetKey(KeyCode.DownArrow))
        {
            this.transform.Translate(0.0f, -0.02f, 0.0f);
        }
    }
}

Enemyを作ります。messagepipeで通知を受け取ったら速度が遅くなるという仕様にしたいと思います。Playerに当たっても特に何も起きません。

Enamy.cs

using MessagePipe;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    float random = 0.0f;
    float speed = 1.0f;
    ISubscriber<TheWorld> OnStop { get; set; }

    private System.IDisposable disposable;
    public void SetUp(ISubscriber<TheWorld> theWorld)
    {
        OnStop = theWorld;
        var d = DisposableBag.CreateBuilder();
        OnStop.Subscribe(ev =>
        {
            speed = ev.stopSpeed;
        }).AddTo(d);

        disposable = d.Build();
        random = Random.Range(-0.01f, 0.01f);
    }

    void Update()
    {
        this.transform.Translate(random * speed, -0.03f * speed, 0.0f);
    }

    private void OnDestroy()
    {
        disposable.Dispose();
    }
}

f:id:Piffett:20210525090417p:plain

EnemyはPlafab化

ふと思い出してBox Collider 2DとRigidBody 2DをPlayerに付けました。

Enemyを上から降らせるやつを作ります。

using MessagePipe;
using System.Collections;
using UnityEngine;
using VContainer;

public class EnemyProducer : MonoBehaviour
{
    [SerializeField] GameObject enemy;
    [Inject]ISubscriber<TheWorld> OnStop { get; set; }
    void Update()
    {
        if(Random.Range(0, 30) == 15)
        {
            var obj = Instantiate(enemy, new Vector3(Random.Range(-2.0f, 2.0f), 4.5f, 0.0f), Quaternion.identity);
            var enemy = obj.GetComponent<Enemy>();
            
            enemy.SetUp(OnStop);

            StartCoroutine(DeleteObj(obj));
        } 
    }

    IEnumerator DeleteObj(GameObject gameObj)
    {
        yield return new WaitForSeconds(3);
        Destroy(gameObj);
    }
}

こんな感じ(画質荒)

f:id:Piffett:20210526052716g:plain

MessagePipeで通知を送るために、当たったらすべてのEnemyを遅くするオブジェクトを作ります。

using MessagePipe;
using UnityEngine;
using VContainer;

public class Stopper : MonoBehaviour
{
    [Inject] IPublisher<TheWorld> stopEvent { get; set; }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        stopEvent.Publish(new TheWorld() { stopTime=1.0f });
    }
}

こいつを上から降らせてもいいんですが、めんどくさいので右下に置きます。 Stopper.csをアタッチします。ついでにColiderとRigidbodyもアタッチします

最後にLifeTimeに通知を送る側と受け取る側のオブジェクトをAuto Inject GameObjectsにいれます。 f:id:Piffett:20210527064648p:plain

そうすると、[Inject]がついているIPublisherとISubscriberがInjectされて、StopperとEnemyProducerはそれぞれTheWorldのISubscriberとIPublisherでデータをやり取りできるようになります。そして、EnemyProducerはISubscriberをEnemyに受け渡しています。

最初はEnemyをLifeTimeのAuto Inject GameObjectsに直接放り込んだら行けるんじゃないかと思っていたのですが、vContainerではそういうやり方はできないみたいです。

動作はこんな感じになります(なんでこんなに色味がおかしいのかはわかってません。どっかでちゃんと編集ソフトの使い方覚えます)

f:id:Piffett:20210527071552g:plain

今思ったんですけどこれだったら別にボタン押して止まる仕様でもよかったですね。最初は頑張ってシューティングゲームを作ろうとしていたので中途半端な形になってしまいました。

UniRxとかをちゃんと使ったことがないので、使いどころはまだわかりかねています。どっかでちゃんと勉強しないとですね。

ではまた