LoginSignup
1
3

More than 5 years have passed since last update.

良い感じに数値をインクリメント描写してくれる機能をUniRxで作る。

Last updated at Posted at 2018-04-14

参考記事

[UniRx]購読を停止する
CompositeDisposableにおけるClearとDisposeの挙動
UniRx入門 その1

環境

Unity 2017.3.1
.NET 4.6(c#6.0)

TL;DR

  • 途中でインクリメント・デクリメントする最終的な値が変更してもシームレスに動作するプログラムをUniRxで作成した。
  • 例えばSTGの加算・最終値が代わり続ける得点や、ギャンブル台の演出によくある残りゲーム・獲得金額のインクリメント(インクリメント処理中もプレイによるG数及びコイン数の減算が発生する)に対応できる。
  • 色々良い所はあるけれども、インクリ・デクリ時に音を再生した際に、常に一定間隔で聞こえるのが良い。

IncrementDemo.gif
Button押下毎にStreamを新規で作成していますが、FrameCountが一定である点が今回の重要なポイントです。
インクリ・デクリ終了後に改めてStreamを作成する時は即時である点も重要。

解説

UniRxの基本的な部分については省略して、
- CompositeDisposable
- Stream間のframe間隔の同期
上記2点について解説したいと思います。

1: CompositeDisposable

CompositeDisposableは、複数まとめてDisposeすることのできるクラスです。

CompositeDisposableをDispose先として登録する方法は2種類あります。特に差は無いです。
1.StreamのAddTo()CompositeDisposableを記載する。

CompositeDisposable _Disporsables = new CompositeDisposable();
Observable.EveryUpdate().Subscribe(_ =>
{
//hogehoge
   //_DisporsablesがDisposeしたらこのStreamを終了する。
}).AddTo(_Disporsables);

//Observableが実施中ならばDisposeする。
_Disporsables.Clear();

2.CompositeDisposableStreamAdd()する。

CompositeDisposable _Disporsables = new CompositeDisposable();
var hogehogeStream = Observable.EveryUpdate().Subscribe(_ =>
{
//hogehoge
}).AddTo(this);

_Disposables.Add(hogehogeStream);
//Observableが実施中ならばDisposeする。
_Disporsables.Clear();

CompositeDisposableDispose()する時は、基本的にClear()を使用しましょう。
Dispose()を実施すると、CompositeDisposableが以後使えなくなりますが、Clear()ならば再利用が可能です。
今回は使いまわしているため、Clear()が妥当です。Dispose()は使わないぐらいが良いです。

2: Stream間のframe間隔の同期

実施中のStreamの破棄については解説いたしました。
次は、そのStreamの破棄と次のStreamの生成の間隔をStream自体のIntervalと合わせる方法についてです。

まずは、Stream実施時のTime.frameCountを保持しておくことです。

//frameCountを記憶しておく。
_PreviousFrameCount = Time.frameCount;

上記を、incrementStreamの購読内容に加えることで、破棄した時と直前のincrementStreamで購読した時のframe数の差異を算出できるようにしておきます。

開始時から現在までの累計frame数 - 開始時から直前にincrementStreamを実施した時までの累計frame数

により、直前にincrementStreamを実施した時から現在までの累計frame数を取得します。
あとは_FrameIntervalから上記の値を引くことで、現在から次の発行タイミングまでのframe数を算出することができます。


//incrementStreamで使用するTimer値。初期値は0。
var frameBetweenObservable = 0;
//incrementStreamが実行中だったら、
if (IsIncrementRunning)
{
    //直前にSubscribeした時と今回購読するストリームのタイミングを合わせるための値を算出する。
    frameBetweenObservable = _FrameInterval - (Time.frameCount - _PreviousFrameCount);
}

これで、frameBetweenObservableObservable.TimerFrameのTimer部分に引数として割り当てれば完成です。

サンプルコード

Increment.cs

using UnityEngine;
using UniRx;
using UnityEngine.UI;

public class Increment : MonoBehaviour
{
    /// <summary>
    /// incrementStreamの生殺与奪権を持つ。
    /// </summary>
    CompositeDisposable _Disporsables = new CompositeDisposable();

    /// <summary>
    /// _PlayerHealthの値を変更するためのボタン
    /// </summary>
    [SerializeField]
    Button _ButtonForChangePlayerHealth;

    /// <summary>
    /// 実際のプレイヤーのHP。
    /// </summary>
    [SerializeField]
    IntReactiveProperty _PlayerHealth = new IntReactiveProperty(100);

    /// <summary>
    /// インクリ・デクリを実施する描画用Textコンポーネント
    /// </summary>
    [SerializeField]
    Text _IncrementText;

    /// <summary>
    /// インクリ・デクリを実施する間隔(frame値)
    /// </summary>
    [SerializeField]
    int _FrameInterval = 5;

    /// <summary>
    /// デバッグ用
    /// </summary>
    [SerializeField]
    Text _StatusForDebug;

    // Use this for initialization
    void Start()
    {
        //初期値の代入。
        _IncrementText.text = $"{_PlayerHealth.Value}";
        //_PlayerHealthの値をインクリ・デクリで表示するための、描画用のint値
        var _IntForIncrementText = _PlayerHealth.Value;
        //直前にincrementStreamがSubscribeされた時のFrameCount値
        var _PreviousFrameCount = 0;
        //_PlayerHealth.Subscribe内にあるincrementStreamの実行状態を表すBool値
        var IsIncrementRunning = false;


        //ボタンがクリックされたら購読する。
        _ButtonForChangePlayerHealth.OnClickAsObservable().Subscribe(_ =>
        {
            //0から100までの乱数を_PlayerHealthに代入して発行する。
            _PlayerHealth.Value = Random.Range(0, 101);
        }).AddTo(this);


        //_PlayerHealthの値が変更されたら購読(実施)する。
        _PlayerHealth.Subscribe(playerHealth =>
        {
            //incrementStreamで使用するTimer値。初期値は0。
            var frameBetweenObservable = 0;
            //incrementStreamが実行中だったら、
            if (IsIncrementRunning)
            {
                //直前にSubscribeした時と今回購読するストリームのタイミングを合わせるための値を算出する。
                frameBetweenObservable = _FrameInterval - (Time.frameCount - _PreviousFrameCount);
            }

            //incrementStreamが実施中ならばDisposeする。
            _Disporsables.Clear();


            //frameBetweenObservableだけ待機して_FrameInterval毎に実施する。
            var incrementStream = Observable.TimerFrame(frameBetweenObservable, _FrameInterval)
                //表示用int値とplayerHealthが同値になればDisposeする。
                .TakeWhile(_ => _IntForIncrementText != playerHealth)
                //購読
                .Subscribe(_ =>
                {
                    //Stream実行中フラグをONにする。
                    IsIncrementRunning = true;

                    //プレイヤーのHealthに合わせてインクリ・デクリする。Tween系をしてみるのも面白そう。
                    if (_IntForIncrementText < playerHealth) _IntForIncrementText++;
                    else _IntForIncrementText--;

                    //ここで音を出すと気持ちいい。
                    //_AudioSource.PlayOneShot(_AudioClip);

                    //インクリ・デクリした値を画面上のテキストに反映する。
                    _IncrementText.text = $"{_IntForIncrementText}";

                    //デバッグ用
                    _StatusForDebug.text = $"Player Health:{playerHealth}\n"
                        + $"FrameCount:{Time.frameCount},-:{Time.frameCount - _PreviousFrameCount},"
                        + $"%:{Time.frameCount % _FrameInterval}\n"
                        + $"FrameBetweenObservable:{frameBetweenObservable}";

                    //frameCountを記憶しておく。
                    _PreviousFrameCount = Time.frameCount;
                }
                //完了したらTimer機能をOff(待ち時間を0)にする。
                , () => IsIncrementRunning = false)
                .AddTo(this);

            //_DisporsablesにincrementStreamを登録する。
            _Disporsables.Add(incrementStream);

        }).AddTo(this);
    }
}
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3