【UnityAsset】MVVM 4 uGUI – uGUIにMVVM(Model-View-ViewModel)パターンを導入

【UnityAsset】MVVM 4 uGUI – デモ
M4u 〜 あとがき

ここではMVVM 4 uGUI(M4u)がどんなAssetか説明します。
M4uの使用方法については【UnityAsset】MVVM 4 uGUI – デモをご覧下さい。

M4uとは

M4uは、uGUIにMVVM(Model-View-ViewModel)パターンをサポートしたAssetです。
M4uを使用することで、コード量を劇的に少なくすることが可能です。

動作環境

Unity2018.3.7+(ただおそらくUnity5+でも動きます)

使用方法

【UnityAsset】MVVM 4 uGUI – デモ

M4uについて

View(uGUI)の更新

Viewの更新を「従来のやり方」と「M4u適用後」で比較すると以下のようになります。

// 従来のやり方
using UnityEngine;
using UnityEngine.UI;

class Demo : MonoBehaviour
{
    [SerializeField]Text idText;
    [SerializeField]Text nameText;
    User user = new User();

    void Start()
    {
        int id = 1;
        string name = "てんぷら";

        // データ更新
        user.Id = id;
        user.Name = name;

        // Viewへ反映
        idText.text = id.ToString();
        nameText.text = name;
    }
}

class User
{
    int id;
    string name;

    public int Id { get { return id; } set { id = value; } }
    public string Name { get { return name; } set { name = value; } }
}
// M4u適用後
using UnityEngine;
using M4u;

class Demo : MonoBehaviour
{
    User user = new User();

    void Start()
    {
        int id = 1;
        string name = "てんぷら";

        // データ更新
        user.Id = id;
        user.Name = name;

        // Viewへの反映必要なし!
    }
}

class User
{
    M4uProperty<int> id = new M4uProperty<int>();
    M4uProperty<string> name = new M4uProperty<string>();

    public int Id { get { return id.Value; } set { id.Value = value; } }
    public string Name { get { return name.Value; } set { name.Value = value; } }
}
従来のやり方の問題点

今までは「データ更新」→「View反映」の手順が必要でした。ただ、大抵の場合、「データ=View」になってる場合が多く、同じようなコードを2倍書く必要があったかと思います。また、データ更新の際は常にViewを気にしてプログラムする必要がありました。

M4u適用後のメリット

M4uを使用するとデータ更新後のViewへの反映が必要なくなります。MVVMのデータバイディングの仕組みを利用することで、Viewへの反映はM4uが自動的に行ってくれます。このメリットはたくさんありますが、一番はコード量が劇的に少なくなることです。今まで常に2倍必要だったコードが1度で済むようになり、スッキリします。

  • コード量削減(無駄なコードを書く必要なし!)
  • 開発生産性/保守性の向上(データの修正だけでよくなる)
  • プログラムがスッキリする(見通しが良くなる)

これらがM4uのメリットです。

M4uの使用例

ユーザーデータの更新
using UnityEngine;
using System;
using M4u;

class User
{
    M4uProperty<int> id = new M4uProperty<int>();
    M4uProperty<string> name = new M4uProperty<string>();
    M4uProperty<int> level = new M4uProperty<int>();
    M4uProperty<int> exp = new M4uProperty<int>();
    M4uProperty<int> gold = new M4uProperty<int>();
    M4uProperty<int> maouseki = new M4uProperty<int>();
    M4uProperty<string[]> friends = new M4uProperty<string[]> ();
    M4uProperty<DateTime> recoveryAt = new M4uProperty<DateTime>();
    M4uProperty<DateTime> loginAt = new M4uProperty<DateTime>();

    public int Id { get { return id.Value; } set { id.Value = value; } }
    public string Name { get { return name.Value; } set { name.Value = value; } }
    public int Level { get { return level.Value; } set { level.Value = value; } }
    public int Exp { get { return exp.Value; } set { exp.Value = value; } }
    public int Gold { get { return gold.Value; } set { gold.Value = value; } }
    public int Maouseki { get { return maouseki.Value; } set { maouseki.Value = value; } }
    public string[] Friends { get { return friends.Value; } set { friends.Value = value; } }
    public DateTime RecoveryAt { get { return recoveryAt.Value; } set { recoveryAt.Value = value; } }
    public DateTime LoginAt { get { return loginAt.Value; } set { loginAt.Value = value; } }
}

ユーザデータを画面に表示する機会は多いです。その場合、データの数だけViewの操作が必要になり、しかも実際はデータの更新が必要なだけで、Viewへの反映は同じことを繰り返し書いてることが多いです。
M4uを使用すればデータの更新だけで済むようになり、無駄な記述がなくなります(バグの抑止にも繋がります)。

※実際にはUnityエディタ上でのバインド設定が必要ですが、ここでは省略しています(詳しくは使用方法をご確認下さい)

ステータスによって表示を切り替えたい場合
using UnityEngine;
using System;
using M4u;

enum DialogType { Notice, Confirm }

class Dialog : MonoBehaviour
{
    M4uProperty<DialogType> type = new M4uProperty<DialogType>();
    M4uProperty<string> message = new M4uProperty<string>("");
    M4uProperty<string> positive = new M4uProperty<string>("");
    M4uProperty<string> negative = new M4uProperty<string>("");
    Action<Dialog> onPositive;
    Action<Dialog> onNegative;

    public DialogType Type { get { return type.Value; } set { type.Value = value; } }
    public string Message { get { return message.Value; } set { message.Value = value; } }
    public string Positive { get { return positive.Value; } set { positive.Value = value; } }
    public string Negative { get { return negative.Value; } set { negative.Value = value; } }
    public Action<Dialog> OnPositive { get { return onPositive; } set { onPositive = value; } }
    public Action<Dialog> OnNegative { get { return onNegative; } set { onNegative = value; } }

    public static Dialog Create(DialogType type)
    {
        var dialog = ((GameObject)Instantiate(Resources.Load("Dialog"))).GetComponent<Dialog>();
        dialog.Type = type;
        dialog.transform.localScale = Vector3.zero;
        return dialog;
    }

    public void Show()
    {
        transform.localScale = Vector3.one;
    }

    public void Hide()
    {
        Destroy (gameObject);
    }

    public void OnClickPositive()
    {
        if(onPositive != null) onPositive(this);
    }

    public void OnClickNegative()
    {
        if(onNegative != null) onNegative(this);
    }
}

enumの値で表示を切り替えたい時はしばしばあります。上の例だとDialogTypeによって、ボタンの数を変化させたりなど。
こういう場合、Viewの操作が必要なくなるのでプログラムがスッキリします。

更新が頻繁でいろいろな場所で表示されるオブジェクト
using UnityEngine;
using System.Collections.Generic;
using M4u;

class Character : M4uContext
{
    M4uProperty<int> id = new M4uProperty<int>();
    M4uProperty<string> name = new M4uProperty<string>();
    M4uProperty<CharacterType> type = new M4uProperty<CharacterType>();
    M4uProperty<Sprite> sprite = new M4uProperty<Sprite>();
    M4uProperty<int> hp = new M4uProperty<int>();
    M4uProperty<int> mp = new M4uProperty<int>();
    M4uProperty<List<string>> magics = new M4uProperty<List<string>>();
    M4uProperty<float> upStatus = new M4uProperty<float>();
    M4uProperty<float> downStatus = new M4uProperty<float>();

    public int Id { get { return id.Value; } set { id.Value = value; } }
    public string Name { get { return name.Value; } set { name.Value = value; } }
    public CharacterType Type { get { return type.Value; } set { type.Value = value; } }
    public Sprite Sprite { get { return sprite.Value; } set { sprite.Value = value; } }
    public int Hp { get { return hp.Value; } set { hp.Value = value; } }
    public int Mp { get { return mp.Value; } set { mp.Value = value; } }
    public List<string> Magics { get { return magics.Value; } set { magics.Value = value; } }
    public float UpStatus { get { return upStatus.Value; } set { upStatus.Value = value; } }
    public float DownStatus { get { return downStatus.Value; } set { downStatus.Value = value; } }
}

enum CharacterType { Yusha, Monster, NPC }

M4uはデータ更新が多い場合に真価を発揮します。
例えば、キャラクターのデータは事あるごとに変更されます。今までだと、更新箇所が増える度に「Viewの参照を保持」->「View反映のコード記述」という手順が必要でした。
これらがM4uを使えば一切なくなります。しかも上の例だと、CharacterをM4uContextで継承させていますが、こうすることでScrollViewのアイテムにそのままデータを結びつける事が可能です(使用方法CollectionBinding参考)。
CollectionBindingはとても強力な機能ですので、是非ご確認下さい。

Asset誕生秘話

実はM4uには元ネタがあります。

NData
Unity 〜NDATA その1 MVVMについて〜

自分の以前のUnityプロジェクトでNDataを使用していました。NDataはNGUIにMVVMを適用したAssetで、とても便利で、NDataなしでは今後開発できないとまで思ってました。
そんなNDataですが、2014年を最後に更新が停止しています。NDataの開発会社がAssetStoreから撤退した可能性があります。

そこで誕生したのがM4uです。M4uはNDataのuGUI版なのです。

最後に

M4uは、自分が開発したチャリで来た。カメラや外部プロジェクトでも採用されている実績があります。開発力を上げたい方は是非ご使用下さい。

M4uを宜しくお願いします。