MessagePipeを使って簡単なアクションゲームを作る【MessagePipe,Unity】

最近注目を集めている MessagePipeを使ってみようと思いいろいろ調べて,自分なりにアクションゲームを作ってみました! (ゲームといってもMessagePipeを使うことが目的なので,ゲームにすらなっていないです)

成果物

Demo

キーボードのAを押すことで,左側のUnity chanに攻撃することができます
また,左のUnity chanからは定期的に(5秒後ごと)攻撃を受けます どちらかのHPが0になったら勝敗モーションに移行後,ゲーム終了します

リポジトリ

リポジトリはpublicにしていますので,詳細はリポジトリをご確認ください

github.com

WebGL

ayutaz.github.io

MessagePipeとは?

公式のGithubページより

MessagePipe is a high-performance in-memory/distributed messaging pipeline for .NET and Unity. It supports all cases of Pub/Sub usage, mediator pattern for CQRS, EventAggregator of Prism(V-VM decoupling), etc.

  • Dependency-injection first
  • Filter pipeline
  • better event
  • sync/async
  • keyed/keyless
  • buffered/bufferless
  • broadcast/response(+many)
  • in-memory/distributed

MessagePipe is faster than standard C# event and 78 times faster than Prism's EventAggregator.

簡単に言うと,型でイベントの発行等を管理するライブラリみたいです(理解が怪しい)

環境

© Unity Technologies Japan/UCL

(注) VContainerとMessagePipeを使うときは,こちらのMessagePipe.VContainerを入れないと動かない?気がします

f:id:ayousanz:20210601161227p:plain

MessagePipeでの値の受け渡し方

関係する部分のみ記載しています.実際に使用方法は, #MessagePipe部分のコード もしくはリポジトリをご確認ください

イベントPublish側

//使用するPublisherとDisposeを定義
[Inject] private IPublisher<PlayerData> PlayerData { get; set; }
private IDisposable _disposable;

//イベントの発行

PlayerData.Publish(new PlayerData(){AttackValue = value,IsLose = isLose});

イベントSubscribe側

[Inject] private ISubscriber<EnemyData> EnemyData { get; set; }
var d = DisposableBag.CreateBuilder();
            EnemyData.Subscribe(e =>
            {
                playerHp -= e.AttackValue;
                isPlay = e.IsLose;
                _animator.SetTrigger("IsDamage");
                playerHpText.text = $"enemy hp:{playerHp}";

                if (playerHp <= 0)
                {
                    _animator.SetBool("IsLose",true);
                    PublishData(0,true);
                }
            }).AddTo(d);

            _disposable = d.Build();

MessagePipe部分のコード

Player.cs

using System;
using MessagePipe;
using UnityEngine;
using UnityEngine.UI;
using VContainer;

namespace Model
{
    public class Player : MonoBehaviour
    {
        [SerializeField] private int playerHp;
        [SerializeField] private int attackValue;
        [SerializeField] private Text playerHpText;
        [Inject] private ISubscriber<EnemyData> EnemyData { get; set; }
        [Inject] private IPublisher<PlayerData> PlayerData { get; set; }
        private IDisposable _disposable;

        private Animator _animator;
        private bool isPlay;

        private void Awake()
        {
            playerHpText.text = $"player hp:{playerHp}";
            _animator = GetComponent<Animator>();
        }

        private void Start()
        {
            var d = DisposableBag.CreateBuilder();
            EnemyData.Subscribe(e =>
            {
                playerHp -= e.AttackValue;
                isPlay = e.IsLose;
                _animator.SetTrigger("IsDamage");
                playerHpText.text = $"enemy hp:{playerHp}";

                if (playerHp <= 0)
                {
                    _animator.SetBool("IsLose",true);
                    PublishData(0,true);
                }
            }).AddTo(d);

            _disposable = d.Build();
        }


        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.A) && !isPlay)
            {
                Debug.Log("プレイヤーが攻撃");
                _animator.SetTrigger("IsAttack");
                PublishData(3,false);
            }
        }

        private void PublishData(int value, bool isLose)
        {
            PlayerData.Publish(new PlayerData(){AttackValue = value,IsLose = isLose});
        }

        private void OnDestroy()
        {
            _disposable?.Dispose();
        }
    }
    
}

Enemy.cs

using System;
using MessagePipe;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
using VContainer.Unity;

namespace Model
{
    public class Enemy : MonoBehaviour,IStartable
    {
        [SerializeField] private int enemyHp;
        [SerializeField] private Text enemyHpText;
        private bool _isPlay;

        [Inject] private ISubscriber<PlayerData> PlayerData { get; set; }
        [Inject] private IPublisher<EnemyData> EnemyData { get; set; }

        private Animator _animator;
        private IDisposable _disposable;

        private void Awake()
        {
            enemyHpText.text = $"enemy hp:{enemyHp}";
            _animator = GetComponent<Animator>();
        }

        public void Start()
        {
            var d = DisposableBag.CreateBuilder();
            PlayerData.Subscribe(e =>
            {
                enemyHp -= e.AttackValue;
                _isPlay = e.IsLose;
                _animator.SetTrigger("IsDamage");
                enemyHpText.text = $"enemy hp:{enemyHp}";

                if (enemyHp <= 0)
                {
                    PublishData(0,true);
                    _animator.SetBool("IsLose",true);
                }
            }).AddTo(d);

            _disposable = d.Build();

            Observable.Interval(TimeSpan.FromSeconds(5f))
                .Where(_=>!_isPlay)
                .Subscribe(_ =>
                {
                    Debug.Log("敵が攻撃");
                    _animator.SetTrigger("IsAttack");
                    PublishData(3,false);
                }).AddTo(this);
        }
        
        private void PublishData(int value, bool isLose)
        {
            EnemyData.Publish(new EnemyData(){AttackValue = value,IsLose = isLose});
        }

        private void OnDestroy()
        {
            _disposable?.Dispose();
        }
    }
}

DI(VContainer)

using MessagePipe;
using Model;
using VContainer;
using VContainer.Unity;


public class GameLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        var options = builder.RegisterMessagePipe();
        builder.RegisterMessageBroker<PlayerData>(options);
        builder.RegisterMessageBroker<EnemyData>(options);

    }
}

受け渡しているデータクラス

namespace Model
{
    public class Data
    {
        public int AttackValue;
        public bool IsLose;
    }

    public class PlayerData : Data
    {
        
    }

    public class EnemyData : Data
    {
        
    }
}

参考にしたサイト

piffett.hateblo.jp

qiita.com