tonari note

オンラインゲームエンジニアの雑記

SwitchPattern

以前、ActionPatternなるものを作ったんですが、顧客が本当に欲しいものを吟味した結果、下のようなものができました。

SwitchPattern.cs · GitHub


まずは単純なswitch。

using static System.Console;
using static Tonari.ActionPattern.SwitchPattern;

//==

Switch(count)
    .Case(0, _ => WriteLine("Zero"))
    .Case(1, _ => WriteLine("One"))
    .Case(2, _ => WriteLine("Two"))
    .Default(_ => WriteLine("数えきれないよ"));

FizzBuzz

using static System.Console;
using static Tonari.ActionPattern.SwitchPattern;

//==

for(var i = 0; i < 100; ++i)
{
    Switch(i)
        .Case(_ => i % 15 == 0, _ => WriteLine("FizzBuzz"))
        .Case(_ => i % 5 == 0, _ => WriteLine("Buzz"))
        .Case(_ => i % 3 == 0, _ => WriteLine("Fizz"))
        .Default(_ => WriteLine(i.ToString()));
}

型マッチ。

using static Tonari.ActionPattern.SwitchPattern;

//==

public string WhatIs<T>(T value)
{
    var result = string.Empty;

    Switch(value)
        .Case<int>(_ => result = "int型")
        .Case<float>(_ => result = "float型")
        .Case<string>(_ => result = "string型")
        .Default(_ => result = "しらなーい");

    return result;
}

型マッチ+条件

using static Tonari.ActionPattern.SwitchPattern;

//==

public string WhatIs<T>(T value)
{
    var result = string.Empty;

    Switch(value)
        .Case<int>(x => x % 2 != 0, _ => result = "int型の奇数")
        .Case<int>(_ => result = "int型の偶数")
        .Case<float>(_ => result = "float型")
        .Case<string>(_ => result = "string型")
        .Default(_ => result = "しらなーい");

    return result;
}

複数Enumとかまとめて

using static Tonari.ActionPattern.SwitchPattern;

//==

enum Fruit { Apple, Orange }
enum Meet { Pork, Beef }

//==

public int GetPrice<T>(T value)
{
    var result = 0;

    Switch(value)
        .Case<Fruit>(Fruit.Apple, _ => result = 100)
        .Case<Fruit>(Fruit.Orange, _ => result = 50)
        .Case<Meet>(Meet.Pork, _ => result = 100)
        .Case<Meet>(Meet.Beef, _ => result = 200);

    return result;
}

さすがにUnity/Mono iOSではJIT落ちするんですが、Unity/IL2CPPだと実機で動いたんで多分動く。
きっと。


【追記】

using static Tonari.ActionPattern.SwitchPattern;

//==

public string WhatIs<T>(T value)
{
    var result = string.Empty;

    Switch(value)
        .Case<int>(x => x % 2 != 0, _ => result = "int型の奇数")
        .Case<int>(_ => result = "int型の偶数")
        .Case<float>(_ => result = "float型")
        .Case<string>(_ => result = "string型")
        .Default(_ => result = "しらなーい");

    return result;
}

using static Tonari.ActionPattern.SwitchPattern;

//==

public string WhatIs<T>(T value)
{
    return SelectSwitch<T, string>(value)
        .Case<int>(x => x % 2 != 0, _ => "int型の奇数")
        .Case<int>(_ => "int型の偶数")
        .Case<float>(_ => "float型")
        .Case<string>(_ => "string型")
        .Default(_ => "しらなーい");
}

と書けるようにしました

最近のUnity関連のgist

メモリにやさしい空の配列生成

gist.github.com
Enumerable.Empty(T)は毎度インスタンスを生成するので、こちらにすると型ごとに1つのインスタンスだけでメモリにやさしい。

IEnumerable(GameObject)に含まれてる特定のコンポーネントをまとめて持ってくる

gist.github.com

var hoges = eventData.hovered.SelectComponents<HogeBehaviour>();

みたいな感じで、複数のGameObjectから特定のコンポーネントだけを持ってくる。

UniRxで過去の履歴が取りたい

gist.github.com
RxのBufferは指定個数貯まらないと値が流れてこないので、今の値と過去の履歴を毎度まとめて持ってこれるやつ。

MarkUXとUniRXでGUIを作る話

この記事はUnity Advent Calendar 2015、23日目の記事です。

前日はkyusyukeigoさんのエディターでWebViewを使う話でした。
鯖蔵通信ゲームならサーバ周りをWebでポチポチしながら作業することは多いと思うので、夢が広がりんぐだと思います。

さて、今日はサーバ側ではなく、MarkUXとUniRxでクライアントのGUI実装を楽にしよう、という話です。

注意

この記事はuGUIを使用するのでUnity 4.6以降が必要です。
また以下のアセットを使用します。

また、スクリプトC#的な流儀にのっとっていません、あくまでサンプルとして短く書いています。
(MarkUXがプロパティに対応してないというのもありますが…)

はじめに

ある程度のゲームを開発するときに、必ずと言っていいほど必要になるものがGUIです。
GUIがなければユーザーが必要な情報を見れなかったり、必要な情報にアクセスしたりできなくなります。
それは当然Unityでゲームを作るうえでも必要になるものですが、ゲームの「面白さ」に直結する部分ではないので、GUIの実装に工数はあまり割きたくない、と思っているゲーム開発者は多いのではないでしょうか。

Unity4.6以降であれは、Unity標準のGUIシステム(uGUI)が使用できます。
しかし、完璧に使いやすいかといわれるとそうではありません。

そこに対する一つのアプローチがMarkUXになります。

uGUIの問題点

uGUIにおける最大の問題点となるのが、シーンやプレファブの管理でしょう。
バージョン管理上でコンフリクトしたとき、ForceTextオプションをつけていたとしても、マージは至難の業です。

Unityはエンジニア以外でも触れるツールで、個人的には様々な業種の方が使えるようになることが好ましいと思っていますが、やはりUnityに対する理解が少ないメンバーの操作が原因でGUIDが入れ変わって画像がmissingになったりすることもあるでしょう。

また、現状はHTMLにおけるCSSのようなものがなく、スタイルを分離することが難しい状態です。
たとえば木目調だったUIをプラスチック調にしたい、フォントをやっぱりこっちに変えたい、ということになると、様々なシーンに手を加えることになります。
そしてコンフリクトして(以下略。
ここでのプレファブ化は直接的な解決策ではないので、いずれ破たんし、地獄を見る羽目になります。

MarkUXでのGUI作成

まず、MarkUXに馴染みのない方も多いかと思うので、超簡単にMarkUXを紹介します。

Viewを作る

MarkUXではXMLを使用してUIを作成していきます。

<MyView>
  <Label Text="FUN for ALL, ALL for FUN."/>
</MyView>

と記述すると、

f:id:ykimisaki:20151207143206p:plain

こうなります。
ヒエラルキを見てみると、MyView配下にLabelが作成されています。
LabelはuGUIのTextコンポーネントに、MarkUXのLabelコンポーネントが張り付いたものです。
これはXMLから自動で生成(もちろん再生開始時にも再生成)されるので、Unityのシーンに対してできる限り非依存ということになります。

<MyView>
  <Label Text="デフォルト位置、デフォルトサイズ" />
  <Label Text="位置は上部、横幅はフォントに合わせる" Alignment="Top" AdjustToText="Width" />
</MyView>

このようにパラメータを続けて書いていくと、

f:id:ykimisaki:20151207144749p:plain

のように位置やサイズなどを指定することもできます。

f:id:ykimisaki:20151207150258p:plain

真ん中の文字が「デフォルト位」で止まっているのは、横幅が確保されていないためです。

値をスクリプトから入れる

せっかくViewを作っても、スクリプト側から値を入れられないのであれば話になりません。
MarkUXの場合、スクリプトのフィールドの値をそのまま表示させることができます。

<MyView>
  <Label Text="{SampleText}"/>
</MyView>
public class MyView : MarkUX.View
{
    public string SampleText = "さんぷる";
}

このように、Viewと同名のクラスを作成し、MarkUX.Viewを継承します。
そして、内部にあるpublicフィールド名を中括弧で囲み、XML側に記述します。
(残念ながらプロパティには対応しておらず、プロパティ対応も試してみたのですが複雑すぎて挫折しました。)

また、スクリプトでLabelを直接扱いたい場合は、以下のようにIdを指定します。

<MyView>
  <Label Id="Label1" />
</MyView>
public class MyView : MarkUX.View
{
    public MarkUX.Views.Label Label1;
}

これでスクリプトのLabel1フィールドに、View側のインスタンスが渡されます。

スタイルを使用する

スタイルは先ほどのView用のXMLとは別に、Theme用のXMLを使用します。
例えば、文字サイズを統一したい場合は、

<Theme Name="MyViewTheme">
  <Label AdjustToText="Width" />
  <Label Style="Big" FontSize="28" />
  <Label Style="Small" FontSize="12" />
</Theme>

のようにテーマを記述し、

<MyView>
  <Label Text="FUN for ALL, ALL for FUN." Style="Big" Offset="0,0,0,20px" />
  <Label Text="FUN for ALL, ALL for FUN." Style="Small" Offset="0,20px,0,0"/>
</MyView> 

のように指定すると、

f:id:ykimisaki:20151207145806p:plain

となります。
当然ですが、スタイル側を変更すれば、すべてのViewに反映されます。便利。

アニメーションさせる

GUIに求められるアニメーションは、移動するだけ、サイズを変えるだけ、透明度を変更するだけ等の簡素なもので十分ですが、GUIを構成するパーツは膨大なので、これらをすべての要素に対して1つずつ適応するのは骨が折れます。
コンポーネントを作成し貼り付けるにだけでもしんどい作業で、コンポーネントを貼る=シーン/プレファブに依存するということにもなります。

MarkUXではこれらの簡易なアニメーションを定義できます。

View側XML

<Theme Name="MyViewTheme">
  <ViewAnimation Id="FadeIn">
    <Animate Field="Alpha" From="0" To="1" Duration="0.2s" EasingFunction="QuadraticEaseIn" />
  </ViewAnimation>
</Theme>

これらの発火タイミングをスクリプト側で制御したいので、Idで渡してあげます。

public class MyView : MarkUx.View
{
    public MarkUX.Views.ViewAnimation FadeIn;

    public void OnHoge()
    {
        FadeIn.StartAnimation();
    }
}

またButtonやViewChanger(タブ操作でViewが切り替わる系)などでは特定のアニメーションをXML上に記述することも可能です。

MarkUXとUniRxをつなげる

さて、実際のゲームではUIに表示される値は随時変わっていきます。
そこで、UniRxを使用して値の変化を検知し、スクリプト上からLabelの値を随時変えていこうと思います。

<MyView>
  <Label Id="Label1" Text="Before" />
</MyView>
public class MyView : MarkUX.View
{
    public MarkUX.Views.Label Label1;
}

まず、Labelへの参照をスクリプトに持ちます。
そしてスクリプト側で

public class MyView : MarkUX.View
{
    public MarkUX.Views.Label Label1;

    public void Start()
    {
        Label1.SetValue(() => Label1.Text, "After");
    }
}

MarkUXのコンポーネントにはSetValueというメソッドが生えており、そいつを使うと上記のような感じで更新することができます。
これをUniRxと結び付けると、

public class MyView : View
{
    public Label Label1;
        
    // 外部から値を放り込まれる
    public ReactiveProperty<string> Text = new ReactiveProperty<string>();

    public void Start()
    {
        Text
            .Subscribe(x => Label1.SetValue(() => Label1.Text, x))
            .AddTo(this);
    }
}

これでUniRxでコネコネした結果をダイレクトにMarkUX上のコンポーネントに反映させることができます。
ただ、これだと使いづらいので、UniRxにあるSubscribeToTextのような拡張メソッドを用意してあげます。

public static class MarkUXObservableExtensions
{
    public static IDisposable SubscribeToLabel<T>(this IObservable<T> source, Label target)
    {
        return source.Subscribe(x => target.SetValue(() => target.Text, x));
    }
}

これで先ほどのスクリプト

public class MyView : View
{
    public Label Label1;
        
    // 外部から値を放り込まれる
    public ReactiveProperty<string> Text { get; private set; }

    public void Start()
    {
        Text.SubscribeToLabel(Label1).AddTo(this);
    }
}

アラステキ!
という便利メソッドをたくさん作るとモアモアベターですね。
(個人で作った拡張メソッド群はそのうちGistにあげようかなと思っています。)

XAMLだと、ReactivePropertyのValueに対して直接バインドができてハピハピなんですが、MarkUXはそこまで高機能ではないっぽいです。
実際もろもろ魔改造してプロパティに対応させようとしていたのですが、中身がリフレクション芸すぎて色々と参ってやめました…
人間、すなおが一番ですね。

さいごに

さて、MarkUXとUniRxでGUIを作ってきました。
MarkUXはメチャイケだから使え!と言っているのではなく、当然開発スタイルに合わせてuGUIをそのまま使っても、NGUIを使っても全く問題ないです。

しかし、

  • ロジック部分はUIとは無関係にする
  • ロジックからUI、UIからロジックへの流れを作る
  • 統一感のあるUIを作るためにスタイルを有効に使う
  • Unityのシーン/プレファブにできる限り非依存にする

ということをやりやすいのが、MarkUXとUniRxの合わせ技の利点といえるので、これらを求めている人に対しては実用的なアセットではないでしょうか。
また、スマフォ向けパズルゲームのパズル操作部分のUIなんかはよりパフォーマンスが求められ、そういう部分にはMarkUXは向いていないといえるので、きっちりと使用すべき場所を把握しておくのも大事かと思います。

みなさんも是非、MarkUXとUniRxで楽しいuGUIライフを!

おしらせ

さて、話は別になりますが、実は冬コミに参加します。

3日目 西 え-07a です。
白鳥トモエ(Tokyo 7th Sisters)と宮本フレデリカ(THE IDOLM@STER CINDERELLA GIRLS)のイラスト本でお待ちしております。

次はrodostwさんです。

UniRxとカスタムバインディング

お久しぶりです。
最近UniVM更新しつつもどうなってるかは全くな感じだったんで、現状どうなってるかだけメモがてら書いておきます…

github.com

まずUniVMの方向性を、MVRPパターンを拡張する形で、バインディングの部分を担う感じにしようと思いまして、UniRx必須にしました。

speakerdeck.com

UniRxを必須にして何が嬉しいかというと、ReactivePropertyが使える点です。
UniVMはUnityEventへのバインディングもでき、またUniRxはUnityEventをIObservableに変換してくれるので、UIからの入力からUIへの出力までを全てUniVM/UniRxを介して行うことができるようになってます。
結局のところ、UniRxが提供してくれているOnClickAsObservableやSubscribeToTextの部分をもっと柔軟にし、MVRPのRPの部分から、UnityEngine.Componentを排除できるものがUniVMと思ってもらえると。
あと、当然ですがUniRxと違ってこちらはオレオレライブラリです。

まずContextによってMonoBehaviourへの依存を切る方法です。

using UniVM;
using UniRx;

// ContextはMonoBehaviourに影響を受けない
public class SampleContext : IContext
{
    public ReactiveProperty<int> Value { get; private set; }

    public void Initialize()
    {
        Value = new ReactiveProperty<int>();
    }

    public void Dispose()
    {
        Value.Dispose();
    }
}

// ViewModelはMonoBehaviourなので、インスペクタから外部の値を設定できる
public class SampleViewModel : ViewModel
{
    // 外部から何かしらのComponentを持ってくる
    public AnotherSampleBehaviour Another;

    public SampleContext Child { get; private set; }

    public ReactiveProperty<int> Value { get; private set; }

    public override void Initialize()
    {
        Child = new SampleContext();
        Child.Initialize();

        // Anotherの値で初期化みたいなことができる
        Value = new ReactiveProperty<int>(Another.Value);
    }

    public override void Dispose()
    {
        Child.Dispose();
        Value.Dispose();
    }
}

SampleContextのValueへのバインディングですが、今回はViewModel内でChildという名前で定義されているので、バインディングのPathに"Child.Value"を入れることでアクセスできます。

余談ですが、ReactivePropertyの配列やContextの配列なども定義でき、それらの配列要素に関しては、"Child[0].Value[0]"みたいにインデクサを指定することによってアクセスが出来ます。

また、カスタムバインディングを用意しました。
たとえば、以下の様な感じ。

using UnityEngine;
using UnityEngine.UI;
using UniVM;

enum Type { Light, Dark }

[RequireComponent(typeof(Image))]
class MyCustomBinding : CustomPropertyBinding<Type>
{
    private Type current;

    // 各タイプに合ったスプライト(インスペクタで設定)
    public Sprite LightSprite;
    public Sprite DarkSprite;
    // スプライトを表示するImage
    private Image image;

    protected override Type GetComponentValue()
    {
        return current;
    }

    protected override void InitializeComponent()
    {
        // Componentの初期化
        image = GetComponent<Image>();
    }

    protected override void UpdateComponent(Type value)
    {
        current = value;

        // バインドされたTypeに応じてImageにSpriteを適応
        switch (value)
        {
            case Type.Light:
                image.sprite = LightSprite;
                break;
            case Type.Dark:
                image.sprite = DarkSprite;
                break;
            default:
                break;
        }
    }
}
//=================
// Context内
public ReactiveProperty<Type> CharaType { get; private set; }

このスクリプトによって、ContextのReactivePropertyからImageへのバインディングを定義することができます。
こうすることによって、よりコンポーネントに依存し、特定の場合に特化されている物を外に出してやることによって、RP層の肥大化を防ぎ、ContextをUIから遠ざけることによって再利用性を高めることができます。

またMVRPで必要なコンポーネントをインスペクター上でD&Dする作業を極力減らしながらも、他のシーン上のComponentとの繋ぎ(汚れ仕事)をViewModelが担ってくれるので、下手にいろんな所が抜け落ちてる、みたいな状態を回避できるかとも思います。

C#でパターンマッチ


ひとりごと - tonari note

前回、独り言のようにつぶやいたこれですが、一旦形になった気がするのでまとめます。
テストとか超適当。
あとUnityでJIT落ちしない事は一応確認していますが、もし落ちるとかあったら教えてくださいませ。


yKimisaki/ActionPattern · GitHub

パターンマッチって何ぞや?って方はコチラへ。

パターン マッチ (F#)

そもそも最初に、ちょっとした分岐でswitchとかifとか書きたくない、null返ってきても良い感じに処理したい、みたいな事がありまして作る経緯となりました。

まずF#なパターンマッチのサンプルの

type Color =
    | Red = 0
    | Green = 1
    | Blue = 2

let printColorName (color:Color) =
    match color with
    | Color.Red -> printfn "Red"
    | Color.Green -> printfn "Green"
    | Color.Blue -> printfn "Blue"
    | _ -> ()

は今回のライブラリを使うと

enum Color
{
    Red = 0,
    Green = 1,
    Blue = 2
}

 // 型引数は、Color型でマッチするActionPatternですよ、という意味
var printColorName = ActionPattern<Color>
    .Pattern(Color.Red, x => Console.WriteLine("Red"))
    .Pattern(Color.Green, x => Console.WriteLine("Green"))
    .Pattern(Color.Blue, x => Console.WriteLine("Blue"))
    .Default(x => { });

となります。
パターンは上から順番にマッチするか確かめ、最初にマッチしたパターンのラムダ式を呼び出すインターフェイスを返します。
後のラムダ式のxにはマッチした値が入っています。
当てはまらなかった場合はDefaultが呼ばれます。

Defaultは必ず設定しなくてもコンパイルできますが、どのパターンにも当てはまらず、Defaultもないパターンを呼び出すと例外を投げるので注意してください。
まぁこの辺はC#の限界って所かなぁ。

また、F#のnullのマッチも同じように、

var printColorName = ActionPattern<Color>
    .Pattern(Color.Red, x => Console.WriteLine("Red"))
    .CatchNull(x => { })
    .Default(x => { });

とCatchNullで設定でき、nullが来たときはこちらが呼ばれます。
また、CatchNullがない時にnullが放り込まれると、ActionPatternはDefaultを呼ぼうとします。

作成したパターンを実際に使用する場合は、Match拡張メソッドを使用します。

Color.Red.Match(printColorName);
// Console.WriteLine("Red")) が呼ばれる 

ActionPatternのマッチ判定部分には、boolを返すラムダ式も使うことが出来ます。

var fizzbuzz = ActionPattern<int>
    .Pattern(x => x % 15 == 0, x => Console.WriteLine("fizzbuzz"))
    .Pattern(x => x % 5 == 0, x => Console.WriteLine("buzz"))
    .Pattern(x => x % 3 == 0, x => Console.WriteLine("fizz"))
    .Default(x => Console.WriteLine(x.ToString()));

このように多少複雑なマッチングも対応しています。

ActionPatternはIEnumerableに対する拡張メソッドを用意しています。
上のfizzbuzzを例にすると、

Enumerable.Range(1, 100).MatchTrace(fizzbuzz).ToArray();

でIEnumerableの各要素に対して順番にパターンマッチを適応させていきます。
MatchTraceの返り血はIEnumerableで、Actionを実行したあと、シーケンスをそのまま返します。
あと遅延評価なので今回はToArray()して無理やり呼んでいます。

ActionPatternは戻り値をとることが出来ます。

 // 型引数:Colorでマッチ、stringを返す
var getColorName = ActionPattern<Color, string>
    .Pattern(Color.Red, x => "Red")
    .Pattern(Color.Green, x => "Green")
    .Pattern(Color.Blue, x => "Blue")
    .Default(x => "");

var redColorName = Color.Red.Match(getColorName);

返り値を取るActionPatternのLINQ拡張メソッドはMatchSelectです。
Selectの様にパターンマッチすることにより、シーケンスの型が変わります。

最後に、Match時に値を渡したい、という時のために、1引数までなら今のところ対応しています。

 // 型引数:intでマッチ、stringを追加の引数にし、stringを返す
var getClampedScore = ActionPattern<int, string, string>
    .Pattern(x => x < 0, (x, format) => 0.ToString(format))    
    .Pattern(x => x > 100, (x, format) => 100.ToString(format))
    .Default((x, format) => x.ToString(format);

var score = 56.Match(clamp, "000");

このようにMatchの第二引数に追加の引数を渡します。
また、マッチ部分でも第二引数を用いて評価することができます。

var hogehoge = ActionPattern<int, string, string>
    .Pattern((x, format) => x < 0, (x, format) => 0.ToString(format));

自分のやりたいことはできるようになったぞい!
やったね。

ひとりごと

C#で稀によくenumなりで状態を返してちょっとした処理をしたいときがあります。
たとえば簡単な例として、符号を調べる関数があるとして

enum Sign { Positive, Zero, Negative }
static Sign GetSign(int value)
{
    if (value > 0) return Sign.Positive;
    if (value < 0) return Sign.Negative;
    return Sign.Zero;
}
//===
var sign = GetSign(new Random().Next(-5, 5));
switch (sign)
{
    case Sign.Positive:
        何か良い事(); break; 
    case Sign.Negative:
        何か悪い事(); break;
    case Sign.Zero:
    default:
        break;
}

みたいな。
enum作るのも面倒だし、switch書くのも面倒だし、みたいな。

で、

new Random().Next(-5, 5)
    .Pattern(x => x > 0, x => 良い事())
    .Pattern(x => x < 0, x => 悪い事());

みたいに書きたいってなって作りました。


yKimisaki/ActionPattern · GitHub

上みたいな書き方もできるんですが、

enum Element { Fire, Water, Wind, None }

みたいな属性があって、火は水に弱いみたいなジャンケンがあるとします。
すると、それは

var getWeak = ActionPattern
    .Pattern(Element.Fire, x => Element.Water)
    .Pattern(Element.Water, x => Element.Wind)
    .Pattern(Element.Wind, x => Element.Fire)
    .Default(x => Element.None);

みたいに書くことができ、

var weak = Element.Fire.Match(getWeak)();

で取得できます。
このMatchの返り値はカリー化されたデリゲートで、実際の値はMatch()()で取得できます。
返り値がデリゲートなのは将来的に引数付でアレコレしたいからです。

ココからまだ未実装ですがたとえば

var getDamageRate = ActionPattern<Magic, Character>
    .Select(magic => magic.Element, character => character.Element)
    .Pattern((magicElem, charElem) =>
        // 魔法の属性が、キャラクターの属性の弱点だったら、2倍
        magicElem == charElem.Match(getWeak)(), x => 2f)
    .Pattern((magicElem, charElem) =>
        // 逆だったら、0.5倍
        charElem == magicElem.Match(getWeak)(), x => 0.5f)
    .Default(x => 1f);

// ダメージ計算で上のレートを掛ける
damage *= usingMagic.Match(getDamageRate)(enemy);

みたいなことができたらなぁ、など。

UniVM

知人が双方向バインディングしたいっていうのでuGUI向けに作りました。
まだUnityEngine.UI.Text⇔stringの部分しかないですが後は根気で。


yKimisaki/UniVM · GitHub

一応簡単なサンプルつけてます。

f:id:ykimisaki:20141026034934j:plain

まず、UI作って、CanvasにView Rootをくっつけます。

f:id:ykimisaki:20141026034746j:plain

Textオブジェクトを作り、Text Bindingをくっつけます。
下のPathにはバインド先のプロパティ名を付けてください。

次にスクリプトを書きます。

using UnityEngine;
using UniVM;

public class SampleContext : Context
{
	public StringProperty SampleText { get; set; }

	public SampleContext()
	{
		SampleText = new StringProperty();
	}
}

public class SampleViewModel : MonoBehaviour
{
	public ViewRoot View;
	public SampleContext Context;

	void Awake()
	{
		Context = new SampleContext();
		View.Context = Context;
	}

	void Update()
	{
		Context.SampleText.Value = "FUN for ALL, ALL for FUN.";
	}
}

必要なのは、ContextとViewModelです。
SampleContextには、String型のプロパティを作成し、その名前をさっきのTextBindingのPathで指定した名前にしてやります。

次にViewModelのAwakeで、ViewRootとContextを繋いでやります。
これでViewとModelがつながれます。

最後にあとは自由にContextにあるプロパティをいじってやります。
そうするとバインド先のViewが変わります。

f:id:ykimisaki:20141026035600j:plain

あとは空のGameObjectを作り、さっきのViewModelのスクリプトを貼り付け、バインド先のViewを放り込みます。

そんな感じで以上です!