この記事は、 大阪工業大学 Advent Calendar 2021の20日目の記事です。
はじめに
こんにちは、Nihuです。
n番煎じネタです。
感想主体になるのでUnityつよつよマンは読まなくていいと思います。
あとUniRx知りたいって人ももっと詳しい記事が無限にあるので読まなくてい(ry
UniRxとは
一応簡単に概要(UniRx入門シリーズ 目次さんから引用)
UniRxとは、neueccさんが作成されているReactive Extensions for Unityなライブラリです。
Reactive Extensions(以下Rx)は、要点だけ箇条書きすると次のようなライブラリとなっています。
MicrosoftResearchが開発していたC#向け非同期処理向けライブラリ
デザインパターンの1つ、Observerパターンをベースに設計されている
時間に関係した処理や、実行タイミングが重要となる処理を簡単に記述できるようになっている
完成度が高く、Java,JavaScript,Swiftなど様々な言語に移植されている
正直この説明じゃよくわからんと思いますがなんか便利なんだなぐらいでいいと思います。
やったこと
とりあえず何か書かないと覚えないので、雑に横スクロールを作りました

(ガビガビなのはユルシテ、あと黒いのはマウスカーソルです)
所感&解説
全部解説するのは重いので、感じたことを一部ソースコードを挙げながらまとめていきます。
- UniRx.Triggersシリーズが便利すぎる
 
今回使っていて一番感じた部分です。正直本来のRx要素無視してこれだけ使っててもいい気がする(よくはない)。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class Walk : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 1.0f;
    [SerializeField] private float moveForceMultiplier = 1.0f;
    [SerializeField] private float jumpForce = 10.0f;
    void Start()
    {
        Rigidbody rb = GetComponent<Rigidbody>();
        var jumpCount = new IntReactiveProperty(0);
        
        //プレイヤー移動
        this.UpdateAsObservable()
            .Where(_ => Input.GetAxis("Horizontal") != 0&&jumpCount.Value==0)
            .AsUnitObservable() // Unit型に変換
            .BatchFrame(0, FrameCountType.FixedUpdate) //入力メッセージを次のFixedUpdate時に出力する
            .Subscribe(_ =>
            {
                var x = Input.GetAxis("Horizontal") * moveSpeed;
                var moveVector = new Vector3(x-rb.velocity.x,0,0);
                rb.AddForce(moveForceMultiplier*moveVector);
            });
        
        //ジャンプ
        this.UpdateAsObservable()
            .Where(_ => jumpCount.Value<2)
            .Where(_ => Input.GetKeyDown(KeyCode.Space)||Input.GetKeyDown(KeyCode.W))
            .AsUnitObservable() // Unit型に変換
            .BatchFrame(0, FrameCountType.FixedUpdate) //入力メッセージを次のFixedUpdate時に出力する
            .Subscribe(_ =>
            {
                jumpCount.Value ++;
                Debug.Log("ジャンプ回数:"+jumpCount.Value);
                var velocity = rb.velocity;
                rb.velocity= new Vector3(velocity.x,0,velocity.z);
                rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
            });
        
        //Groundに接触したらジャンプ可能
        this.OnCollisionEnterAsObservable()
            .Where(x => x.gameObject.CompareTag($"Ground"))
            .Subscribe(_ =>
            {
                jumpCount.Value = 0;
                Debug.Log("ジャンプ可能");
            });       
    }
}
自機の移動に関するソースコードです。
UpdateAsObservableとかOnCollisionEnterAsObservableとかがUniRx.Triggersシリーズです。
概要としてはUnityが提供するほとんどのコールバックイベントをUniRxのIObservableに変換してくれる機能です。
例えばUpdateAsObservableはUnityのUpdate()と同じタイミングで通知してくれます。
これの凄いところはRxのオペレータ(Whereとか)が使える点も便利なのですが、同じソースコード内に複数使える点です。
例えばUpdate()だと1ソースコードで一個しか使えないため、Update内に色んな処理が記述されていきます。
そのためどこかどの処理かがわかりづらくなったりifの多様で入れ子になったりして不便な点がありました。
そこでUpdateAsObservableを使うと、処理毎に分けて記述することができ、Walk.csでも移動とジャンプで分割して記述しています。
またオペレータを用いることでifの乱立などもなくなるので、見やすいコードがかけるなーといった印象です。
- ReactivePropertyシリーズも便利
 
さっきの似たようなやつで、ReactivePropertyで変数を宣言すると変数にSubjectの機能が追加され、
変数の変更を通知してくれるようになります。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class CharaState : MonoBehaviour
{
    [SerializeField]
    private IntReactiveProperty _health = new IntReactiveProperty(100);
    public IReadOnlyReactiveProperty<int> Health {
        get { return _health; }
    }
    
    void Start()
    {
        this.OnCollisionEnterAsObservable()
            .Where(x => x.gameObject.CompareTag("Bullet"))
            .Subscribe(x =>
            {
                _health.Value -= 5;
                if (_health.Value < 0)
                {
                    _health.Value = 0;
                }
                Destroy(x.gameObject);
            });
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class Presenter : MonoBehaviour
{
    [SerializeField] private UIview _UIview;
    [SerializeField] private CharaState _charaState;
    private void Awake()
    {
        _charaState.Health
            .Subscribe(x =>
            {
                _UIview.SetHPber(x);
            });
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using UnityEngine.UI;
public class UIview : MonoBehaviour
{
    [SerializeField]
    private Slider HPber;
    
    public void SetHPber(int hp)
    {
        HPber.value = hp / 100f;
    }
}
HPバーの処理に関するコードです。
IntReactiveProperty(int型のReactiveProperty)で宣言したHealthにSetHPber()を登録(購読)させ
HPの値が変動したら自動でHPバーが更新させるようになります。
これもUniRxなしでやる場合、HPが変更されるたびにSetHPberを呼び出して更新するか、UpdateでSetHPberを呼び続けるかみたいな実装方法になり、とてもめんどくさそうです。
それを変数に購読しておくだけで自動で変更になるので、とても便利ですね。
- 見やすい
 
さっきも少し書きましたが、ここまで書いてきて共通するものとして、全体的にコードが見やすくなるなという印象です。
オペレータを使うことでどこがどの処理を担当してるかわかりやすかったり、ifの乱立で入れ子にならなくて綺麗だ...といった印象です。
- 学習コストが高い
 
ここからはよくないなと思った点をあげていきます。
まずやっぱり一番は学習コストが高いなと感じました。
まず根幹のObserverパターンの概念から内容が重たく、またオペレータやUniRx.Triggersの種類も数多くあり、使いこなすにはなかなかしんどそうだなといった印象です。
- そもそも
 
学習コストと関連しますが、UniRxに触れる前にUnityやC#についての基礎知識が足りてないなと感じました。
追えば追うほどLINQなどの関連する別の概念が出てきて、勉強不足を痛感しました。
まとめ
今回はUniRxを雰囲気だけ触ってみました。
正直前提知識不足が否めなかったですが、記事などを追っているうちに知れたことも多いので、今後も当たって砕けろしていこうかと思います。