M4u 〜 あとがき

【無料化アセット】MVVM(Model-View-ViewModel)パターンでuGUIを制御する日本作家さんによる開発効率系スクリプトが無料化!解説サイトが日本語でわかりやすい「MVVM 4 uGUI」

嬉しいことに上記サイトでM4uを取り上げていただきました☆
感謝の代わりといってはなんですが、最後にあとがきを書こうかと思います。

はじめに

先日M4uの1.2.0をリリースしましたが、実は結構前に雛形は出来てました(コミット履歴を見たら2016年には完成してたよう)。
じゃあなんでこのタイミングかっていうと、M4uの後継アセットを作ってたからです(M4u2!)。

M4uの良くないところ

元々M4uにはいろいろと良くないところがありました。

バインドパスを文字列で指定してる

typoやパス指定がめんどくさかったり。

バインドの種類が少ない(uGUIしかない)

アセットのコンセプト否定ですがw
uGUI限定ではなく、もっと汎用バインダーでいいような・・・。

パフォーマンスがそこまでよくない(主にリフレクション)

UnityでMVVMの仕組みを作る場合リフレクションは必要なんですけど、ちょっとリフレクションしすぎっていう(設計が悪かった)。

手動でデータ更新の通知を行えない(AssetStoreのコメントにあったRaisePropertyChangedみたいな)

普通はINotifyPropertyChangedを実装して、手動でデータ更新の通知を行えるのですが、M4uの場合はできない(自動更新)。

全体的にコードが古い(最適化されていない)

4年前のアセットなので・・・。

M4u2

もろもろの改善を行うには既存のM4uでは影響範囲が大きすぎたので、新たにM4u2として作り直すことにしたわけです。
ただ、結論から言うとM4u2は未完成です(開発を中止しました)。一応ひと通り動作するところまでは出来てるのですが、実践で試してないので調整不足という感じです(Ver.0.5くらい?)。
なので公開するつもりはなかったのですが、せっかくなので紹介だけしておきます。

プロジェクト

https://github.com/okamura0510/M4u2
Unity2018.3.7+
※未完成です。ご使用の際は自己責任でお願いします(フォーク改編ご自由に)。

使い方
M4uBindableBaseを継承して、データ更新時にSetMemberメソッドを呼ぶ

データ更新の通知を行うには、M4uBindableBaseを継承してSetMemberメソッドを呼ぶ(IM4uNotifierのM4uMemberChangedEventで通知を行う)。

using UnityEngine;

namespace M4u2
{
    public class Demo : M4uBindableBase
    {
        [SerializeField] string message = "Thank you Ichiro.";
        
        public string Message
        {
            get { return message;                }
            set { SetMember(ref message, value); }
        }
        
        public void OnUpdate()
        {
            Message = Random.Range(200, 263).ToString();
        }
    }
}
using UnityEngine;
using System.Runtime.CompilerServices;

namespace M4u2
{
    /// <summary>
    /// IM4uNotifierを継承したバインドクラス。本クラスを継承してSetMemberメソッドを呼ぶことで通知を行える。
    /// 本クラスじゃなくてもIM4uNotifierを継承すればバインドクラスは自由に作成可能。
    /// </summary>
    public abstract class M4uBindableBase : MonoBehaviour, IM4uNotifier
    {
        /// <summary>
        /// メンバーが変更されたことを通知
        /// </summary>
        public event M4uMemberChangedEvent MemberChanged;

        /// <summary>
        /// メンバー設定
        /// </summary>
        /// <param name="member">メンバー</param>
        /// <param name="value"></param>
        /// <param name="memberName">メンバー名(CallerMemberNameで自動設定される)</param>
        protected void SetMember<T>(ref T member, T value, [CallerMemberName] string memberName = null)
        {
            if(Equals(member, value)) return;

            member = value;
            MemberChanged?.Invoke(this, memberName, value);
        }
    }
}
M4uBindingを追加して、読み込み/書き込みデータを設定

f:id:okamura0510:20190323000346g:plain

BindingDataでバインド設定を変更可能

f:id:okamura0510:20190323000914p:plain:w320

設定 解説
InitializeOnEditor エディタで値を初期化するか
InitializeOnRuntime ランタイムで値を初期化するか
ReadObject/WriteObject
  BindingFlags DeclaredOnly
指定した型の階層のレベルで宣言されたメンバーのみを対象(継承メンバーは除外)
FlattenHierarchy
階層上位のパブリックおよびプロテクトの静的メンバーを返す(継承クラスのプライベートな静的メンバーは除外)
Instance
インスタンスメンバーを含める
Static
静的メンバーを含める
Public
パブリックメンバーを含める
NonPublic
パブリックメンバー以外のメンバーを含める
GetField
フィールドメンバーを含める
GetProperty
プロパティメンバーを含める
OwnGameObjectOnly
自ゲームオブジェクトのみを対象とする(WriteObjectに入れると便利)
NotifierOnly
IM4uNotifierを継承したクラスのみを対象とする(ReadObjectに入れると便利)
DynamicBinding
ダイナミックバインディングを有効にする。パスの入力を文字列で行えるようになる(入れ子のパスに対応可能になる)。
  SelectMember 選択メンバー
  Object 設定オブジェクト
  MemberPath 設定メンバーパス
  MemberPaths 設定オブジェクトのメンバーパスリスト
  Members 設定オブジェクトのメンバーリスト
Hierarchyアイコン

「Tools/M4u2/Show HierarchyIcon」でアイコンを表示可能。

f:id:okamura0510:20190323001718p:plain:w250

特徴
UnityEventのようにパスを選択式に!

UnityEventがすごい便利で、こんな感じでバインド出来たらいいなーという感じで作りました☆

汎用バインダー(どんなものでもバインド可能)

ゲームオブジェクトに存在するコンポーネントのメンバー全てがバインド可能!

パフォーマンス改善

リフレクションを最小限に抑えました(データ更新時は1リフレクション)。M4uではパスの入れ子の分だけリフレクションしていたのですが、基本入れ子にならない作りに変更。入れ子が必要な場合はDynamicBindingを使えば可能です。
InitializeOnRuntimeを外せば、さらにリフレクションを抑えられます。

手動でのデータ更新の通知をサポート(INotifyPropertyChangedみたいな)

IM4uNotifierで手動でのデータ更新の通知を行えるように。

コード最適化

全体的に無駄がないように設計しました。M4uBindingにいろいろ設定があったりしますが、エディタでしか必要ないものはランタイムでは除外されるようになってます。
ただ調整が甘いので、まだまだ最適化の余地はあるかも。

Decorator(新機能)

f:id:okamura0510:20190323002832p:plain
「値が〜の場合に設定」や「文字列を〜にフォーマット」みたいなデータの装飾をしたい場合、M4uだとバインドコンポーネント自体に処理を記述していたのですが、M4u2では

  • データのバインド → M4uBinding
  • データの装飾   → M4uDecorator

のようにコンポーネントを分けました。これによって、基本となるデータのバインドはそのままに、いろいろなデータの装飾を行えます。

M4uBindingの下のDecoratorコンポーネントがそのバインドの装飾対象になります(1ゲームオブジェクトに複数のM4uBindingがある可能性があるので、「下」の条件を入れました)。
M4uDecoratorを継承すれば装飾クラスの自作が可能です(IM4uDecoratorを継承してればいい)。

当時コンポーネント指向にハマってて、Behavior Designerを試してたのですが、「Behavior Designerみたいに機能ごとにコンポーネントを分けられたら便利だなー」と思い、この機能を作りました。

UnityでのMVVMの使いどころ

そんなM4u2ですが、なんで開発を中止したかというと、個人開発(プロトタイプ)だと相性が悪かったんですよね。ビューやデータの構造を頻繁に変えてしまって、その度にビューモデル(M4u2)の修正も行うっていう余計な手間が増えてしまい。。「個人開発では当面不要」という結論に至った感じです。

データ構造がしっかり決まってると便利だと思うんですよね〜。ソシャゲのユーザデータとか、リリース済みアプリの改修とか。
決まったデータを元にビューに結びつける、みたいな使い方がMVVMには適してるんじゃないかなぁ、と思います。

最後に

M4uをバージョンアップした後にM4uの良くないところを上げるという(笑)
とはいえ、M4uは実績は十分なので、普通に使えると思います。パフォーマンスも、ものすごい速度が要求されるところじゃなければ問題ないと思いますし。

M4u2も、興味がある方はお試しください♪