開発日誌::HoloLensアプリ開発にチャレンジvol5.UniRx本格導入。デモ試作はほぼ完了
前回
コメント
ようやっと、ゲームとして形になりました。なんか弾の出方がちょっと微調整必要だけど、まあまあ敵も倒せるし、UIもばっちりUniRxで制御してる。ということで、デモ試作は一段落かなと。
挙動
こんな感じになりました^^
実装したこと
・シーンを3つにする
Start(Air TapでMainへ)=>Main=>Ending(Air TapでMainに戻る)
以下はUniRxで実装
・敵を倒して加点したら、点数変動を察知して、スコアTextに反映
・体力が最初10だけど0になったらゲームオーバーシーンへ移動
コード解説詳細記事あります^^
k-mawa.hateblo.jp
・体力Scoreの表示
・エンディングシーン(変数定義したスクリプトのあるシーンとは別のシーン)で得点スコア表示
・タイマーが0になったらエンディングシーンへ遷移
これもコード解説詳細記事あります^^
k-mawa.hateblo.jp
Unity::UniRx実装例メモ::ReactiveProperty.Subscribe()ケース別実装例::「普通のint変数の値を条件にUniRxコードでゲーム制御」など
コメント
今回のゲーム開発で使ったUniRxの実装例をメモしておこうと思います。ReactiveProPertyの使いこなしはUniRxでの実装実現の大きな一歩になるし、とにかく便利で読みやすいコードになるので、大プッシュしておきたいと思います^^ .Subscribe()はどういうときに使うのかわかりやすい記事を目指しました。
今回の例は、UniRxで、「単にint変数として活用できるようなReactivePropertyを単にテキスト表示してみる」と、「複数のスクリプトでひとつの変数(的オブジェクトのReactiveProperty)を参照し別々の出力をさせる(Subscribe)」と「普通のint変数の値によって条件づけをして、一定条件のときにのみReactivePropertyを発火させる」の実装例をご紹介します。
実際の実装後の動き
下記の実装例コードはこの自分で作ったゲームで実際に動いています^^「体力Scoreの表示」「エンディングシーン(変数定義したスクリプトのあるシーンとは別のシーン)でスコア表示」「タイマーが0になったらエンディングシーンへ遷移」がUniRxで実装しています。
コード解説::ReactivePropertyを条件づけしないでSubscribeだけ発火させる
この実装は「体力Scoreの表示」の部分です。
using UniRx; using UniRx.Triggers; using UnityEngine.UI; public class ColliisionController : MonoBehaviour { //スコア表示させる部分 [SerializeField] private Text _text; //オプションつきのpivate int =10;と //同じようなものだと思えば理解しやすい ReactiveProperty<int> score = new ReactiveProperty<int>(10); // Use this for initialization void Start() { //.ToString()で文字列にして挿入 //_text.textで流し込める //(_textではないところに注意) score.Subscribe(x => _text.text = x.ToString()).AddTo(this);
コード解説::ほかのスクリプトのReactivePropertyを参照してSubscribe発火を実装
この実装は「エンディングシーン(変数定義したスクリプトのあるシーンとは別のシーン)でスコア表示」の部分です。
//ReactiveProperty<T>を設定したスクリプト using UniRx; using UniRx.Triggers; //いらないかも using UnityEngine.UI; public class GameMasterScript : MonoBehaviour { public static ReactiveProperty<int> rp_gameScore = new ReactiveProperty<int>(0); //↑要するに、public static int gameScore = 0; //の代わりに設定するゲームスコアの値
↓
//Subscribeを発火させたい別のスクリプト@別シーンにアタッチしたもの //スコア表示させる部分 [SerializeField] private Text _text; // Use this for initialization void Start () { //[スクリプト名(クラス名)].[変数名].Subscribe()で //呼び出せる GameMasterScript.rp_gameScore .SubscribeToText(_text); //↑そのまま値を文字列として流し込むメソッド }
コード解説::Obserbableではない変数を条件にして、Subscribe発火させる
この実装は「タイマーが0になったらエンディングシーンへ遷移」の部分です。
using UniRx; using UniRx.Triggers; //↑※UpdateAsObservable()をつかうときは必要! using UnityEngine.UI; using UnityEngine.SceneManagement; public class TimerController : MonoBehaviour { public Text Timer;//タイマー public static float Timer_sec = 120.0f; //タイマーの持ち時間 void Start () { //1行目:Update()と同じ感じの毎フレーム更新時作動 //だけど、ReactiveExtention機能つきの //Upadate()みたいなもの //2行目:Timer_sec(普通のfloat変数)<=0で //時間切れになったら //3行目:Endingシーンをロード this.UpdateAsObservable() .Where(_ => Timer_sec <= 0) .Subscribe(_=>SceneManager.LoadScene("Ending")); } // Update is called once per frame void Update () { // 下記は普通のC#によるタイマーの設定、 //0秒を下回ったら持ち時間マイナスになると変なので //0に固定する if (Timer_sec >= 0) { Timer.text = Timer_sec.ToString("N2"); Timer_sec -= Time.deltaTime; } if (Timer_sec < 0) { Timer_sec = 0; Timer.text = Timer_sec.ToString("N0"); } } }
こんな感じです^^
Unity::UniRx実装例メモ::ObserveEveryValueChanged()のゲームにおけるケース別実装例::「スコアが加点されたら加点状況に応じてUIに反映&効果音再生」など
コメント
今回のゲーム開発で使ったUniRxの実装例をメモしておこうと思います。ゲームで定義した値に変化があったら実行ではObserveEveryValueChanged()を使うと直観的にも挙動が分かりやすい実装になった!今回の例は、UniRxで、「敵を倒して加点したら、点数変動を察知して、スコアTextに反映」と、「体力が最初10だけど0になったらゲームオーバーシーンへ移動」の実装例をご紹介します。事例としては応用かけやすいかなと・・・^^
参考資料
大変参考になりました!^^ ありがとうございます!
実際の実装後の動き
下記の実装例コードはこの自分で作ったゲームで実際に動いています^^「敵を倒して加点したら、点数変動を察知して、スコアTextに反映」と、「体力が最初10だけど0になったらゲームオーバーシーンへ移動」がUniRxで実装しています。
コード解説::ReactivePropertyの値の監視が2つ以上のスクリプトにまたがる場合(汎用性高い)
この実装は「敵を倒して加点したら、点数変動を察知して、スコアTextに反映」の部分です。
//ReactiveProperty<T>を設定したスクリプト using UniRx; using UniRx.Triggers; //いらないかも using UnityEngine.UI; public class GameMasterScript : MonoBehaviour { public static ReactiveProperty<int> rp_gameScore = new ReactiveProperty<int>(0); //↑要するに、public static int gameScore = 0; //の代わりに設定するゲームスコアの値 //スコア加点時に再生する音声のセッティング private AudioSource audioSource_hit; public AudioClip sound_hit; [SerializeField] private Text _text;//スコア表示する変数欄 void Start () { //1行目:rp_gameScoreの値に変化があったら、 //(※ReactiveProperty<int>の値は.Valueで取り出す) //2行目:.SubscribeToText(_text)=>_textに値を流す rp_gameScore.ObserveEveryValueChanged(_ => _.Value) .SubscribeToText(_text); //1行目:rp_gameScoreの値に変化があったら、 //2行目:得点が1点以上の場合のみ.Subscribeに流す(0点も含めるとゲームスタート時に1度音が再生されてしまうのを防ぐ) //3行目:スコア加点時の音声ファイル再生 rp_gameScore.ObserveEveryValueChanged(_ => _.Value) .Where(_ => _ > 0) .Subscribe(_ => audioSource_hit.Play()); }
↓
//値が変化する関数を記述したスクリプト using UniRx; void OnCollisionEnter(Collision other) { if (other.gameObject.tag == "Ghost") { //衝突したら、ReactiveProperty<int>の //オブジェクトの値を変化させる(加点) //(変化があった時点で // すべての監視設定済みSubscribeが発火) GameMasterScript.rp_gameScore.Value += 1; } }
コード解説::ReactivePropertyの値の監視が1つのスクリプトで完結する場合(汎用性低め)
この実装は「体力が最初10だけど0になったらゲームオーバーシーンへ移動」の部分です。
//ひとつのスクリプト内で完結するタイプの書き方 using UniRx; using UniRx.Triggers; //いらんかも using UnityEngine.UI; using UnityEngine.SceneManagement; public class ColliisionController : MonoBehaviour { ReactiveProperty<int> score = new ReactiveProperty<int>(10); //↑要するに、scoreは、HPscoreとしてint変数と同じ使い方で //ReactiveExtention<int>を設定する //public staticはつけないから他のスクリプトで //参照できない(参照させたかったらpublic staticつける) // Use this for initialization void Start() { //1行目:scoreの値に変化があったら、 //(※ReactiveProperty<int>の値は.Valueで取り出す) //2行目:scoreが0以下のとき(HPが0になったら) //3行目:"Ending"シーンへ移動 score.ObserveEveryValueChanged(_ => _.Value) .Where(_ => _ <= 0) .Subscribe(_ => SceneManager.LoadScene("Ending")); } // Update is called once per frame void Update() { } void OnCollisionEnter(Collision other) { if (other.gameObject.tag == "Ghost") { score.Value -= 1; //HPscoreをー1する。 //(これにより、ObserveEveryValueChangedが // 反応する) } }
こんな感じですね^^
Unity::UniRx自分用メモ:C#のご当地仕様?!ReactiveProperty, ジェネリック, MVVM,プロパティ,アクセサーなど
コメント
ReactivePropertyのあたりを調べていて、いろいろ謎だったことが分かったと思うのでメモに残す。聞いたことある単語が最初の3行くらいに凝縮されていたことが判明・・・ C#、奥が深い・・・
参考資料
資料1:これを主に起点として、いろいろ試行錯誤。重要資料です。 qiita.com
資料2:ReactivePropertyの解説で役立ちました blog.okazuki.jp
資料3:private=>publicの流れプロパティ,set,getの理解に役立ちました ufcpp.net
資料4:ReactivePropertyとは何ぞやの基礎中の基礎の解説 ccs-tech-blog.blogspot.jp
コードのこの部分のメモ。
参考資料1からの引用
private ReactiveProperty<int> _countReactiveProperty = new ReactiveProperty<int>(0); // イベントを購読できるようにする. public ReactiveProperty<int> CountReactiveProperty { get { return _countReactiveProperty; } }
これでいつでもSubscribeできるオブジェクトができたみたいなんだけどね。これより先は、UniRxの既存のフィルターみたいなメソッドをつなげていけば直感的にわかりやすいコード構成なので省く。この3行はどういうことなのかしら・・・
疑問
整理すると3点くらいかな
1:ReactiveProperty<int>
ってなに??ReactiveProperty<string>
とか似て非なるコードもあるのだけども・・・
2:privateで宣言してからpublicで改めて呼んでるのはこれはなに?
3:get...てなに??setってメソッドのようなものもくっついているコード例もあってどういうこと??
疑問1「:ReactiveProperty<int>
ってなに??ReactiveProperty<string>
とか似て非なるコードもあるのだけども・・・ 」の自分なりの解答
この資料によると、 ccs-tech-blog.blogspot.jp
ReactivePropertyは値が変化したら信号を出すイメージのオブジェクトというざっくりとした理解でとりあえず動かす目的ならOKぽい。
ReactiveProperty<int>
やReactiveProperty<string>
をまとめてReactiveProperty<T>
と呼んでいて、このTはTypeの略らしい。そして、このTをジェネリックと呼ぶらしい。要するに、int,stringとデータ型が変わっても同じ処理をするオブジェクトにはこのジェネリックという概念を適用するということのようだ。
ちなみにReactiveProperty
疑問2「privateで宣言してからpublicで改めて呼んでるのはこれはなに?」と疑問3「get...てなに??setってメソッドのようなものもくっついているコード例もあってどういうこと??」の自分なりの解答
まとめて解答できる。参考資料2でも同じような個所がある
private string _input; public string Input { get => _input;
この資料により分かった。 ufcpp.net
つまり安全設計のためであった。おおもとの変数は、privateで隠す→publicで使うごとに呼び出す、そのときに値を設定するためにget,値を変更するためにsetというメソッド(アクセサー。値にアクセスするからだろう)のようなものを使い、get,setをアクセサーと呼ぶらしい。なるほど。アクセサーはクラスからオブジェクトを作る際(つまり初期化時)に変数だけちょこちょこいじりたいときに使うよう。
成果
今日の最終成果。各ボタン、UniRxで全部制御しています^^ 機能の一部の参考にした資料 #UniRx #Unity https://t.co/edKnkIiyOw pic.twitter.com/5om4e2WYZo
— mawa (@k_mawa82) 2018年4月6日
Unity::UniRx自分用メモ::基礎、Buttonを紐づける
コメント
ボタンとUniRxの連動についてのメモ
参考資料
コードの機能メモ
using UnityEngine.UI; //書き忘れ注意! using UniRx; using UniRx.Triggers; public class ClickButton : MonoBehaviour { public Button Button; //Button定義 void Start () { //Button.onClick.AsObservable //Buttonクリックされたら //Observableオブジェクト起動 Button.onClick.AsObservable().First() //First()は最初の一回だけTrueにする .Subscribe(_ => Debug.Log("push!")); } // Update is called once per frame void Update () { } }
あとはUnityEditorでボタンの登録D&Dも忘れずに。
Button.onClick.AsObservable().First().Subscribe(_ => Debug.Log("push!")); の挙動を実装^^ #UniRx #Unity pic.twitter.com/t45P3SdXHs
— mawa (@k_mawa82) 2018年4月6日
追加して動かしてDebug.Logにもコメント吐かせる
public class ClickButton : MonoBehaviour { public Button Button; //CubeをUnityEditorから登録 public GameObject Obj_one; // Use this for initialization void Start () { Button.onClick.AsObservable().First() .Subscribe(_ => Debug.Log("push!")); //↓これを追加一回だけ動く Button.onClick.AsObservable().First() .Subscribe(l => Move(1f, 0)); } // Update is called once per frame void Update () { } //動かす関数 Obj_oneで指定しているところがポイント public void Move(float dx, float dy) { Obj_one.transform.position += new Vector3(dx, dy, 0); } }
Unity::UniRx自分用メモ::基礎、Where、Select、TakeWhile
参考資料
コードの機能メモ
using UniRx; using UniRx.Triggers; void Start () { //Returnは、なんらかの値をSubscribe内に流し込む機能 //今回はVenctor2(1,1) Observable.Return(new Vector2(1, 1)) .Subscribe(v => gameObject.transform.position = v); //インデント一個分空けて、Subscribe実装 //Subscribe(ラムダ式)で出力を決定 this.UpdateAsObservable().Subscribe(_ => Move(0.01f, 0)); //フレームごとにSubscribe。移動する関数Moveで処理。 } void Update(){ } //UpdateAsObservable()があるので使わなくなる?! public void Move(float dx, float dy) //引数にフロート入れて { gameObject.transform.position += new Vector3(dx, dy, 0); //transform.positionで座標を引数分移動 }
Observable.Return・・・.Subscribeで初期位置指定して、.UpdateAsObservable() でフレームごとに移動。 #UniRx #Unity pic.twitter.com/88I7s14lLQ
— mawa (@k_mawa82) 2018年4月6日
いくつか便利なメソッドをピックアップ
Where
//クリックされてたら(されているときだけ)、 .Where(_ => Input.GetMouseButton(0)) .Subscribe(_ => Move(0.01f, 0)); //Subscribeへ
Select
//Selectで値を2に指定 .Select(_ => 2.0f) //floatじゃないとエラーになった(たぶん) .Subscribe(l => Move(0.01f * l, 0)); //float同士の演算にする必要あり?!(とりあえずこれで動いた) //Subscribeへ //0.01f * l
TakeWhile
//gameObject.transform.position.x <= 4の間は値を流す .TakeWhile(l => gameObject.transform.position.x <= 4 .Select(_ => 2.0f)) //Selectで値を2に指定 .Subscribe(l => Move(0.01f * l, 0)); //float同士の演算にする必要あり?!(とりあえずこれで動いた) //Subscribeへ //0.01f * l
引き続きUniRx基礎 だんだん便利さの理解が深まってきた!
— mawa (@k_mawa82) 2018年4月6日
.TakeWhile(l => gameObject.transform.position.x <= 4 //xが4以下のときだけ
.Select(_ => 2.0f))
.Subscribe(l => Move(0.01f * l, 0)); //Selectの比率で 右に移動 #Unity #UniRx pic.twitter.com/Yh2SgNByxU