A Day In The Life

とあるプログラマの備忘録

UnityでRedux.NETを使ってみた

フロントエンド開発と iOS アプリ開発でステート管理に Redux(iOSはReSwift)を使っています。Unity でも Redux 使えないかなと探していたところ Redux.NET というC♯の実装を見つけたので使ってみることにしました。

手順

Redux.NET を使うためには Reactive Extensions というやつをインストールしないといけないのですが今のところ(2016年6月現在) Unity には対応していません。なので代わりに Reactive Extensions の Unity 実装である UniRx をインストールします。

UniRxのインストール

Asset Store からUniRxをインストールする

Redux.NETのインストール

Redux.NET は100行にも満たないシンプルなコードなのでインストールというほど大げさなものではないです。

GuillaumeSalles/redux.NET から Store.cs と IAction.cs をコピーして Unity プロジェクトの Scripts フォルダに追加してください。 github.com

Redux.NETをUnityで動くように修正する

Redux.NET はそのままではコンパイルエラーになるので以下のように Store.cs を修正します。

using System;
//using System.Reactive.Subjects;
using UniRx;

namespace Redux
{
    ...その他コード
}

以上で準備は完了です。

使い方

カウンターを使った簡単なサンプルを例に使い方をみてみましょう。

アクションの作成

初めにアクションを定義します。

public class IncrementAction : IAction { }

public class DecrementAction : IAction { }

ストアの作成

次にストアを定義します。

public class State
{
  public int Counter { get; set; }
}

Reducerの作成

最後に Reducer を作成します。

public static class CounterReducer
{
  public static State Execute(State state, IAction action)
  {
    if (action is IncrementAction)
    {
      return new State {
        Counter = state.Counter + 1
      }
    }
    else if (action is DecrementAction)
    {
      return new State {
        Counter = state.Counter - 1
      }
    }
    return state;
  }
}

ステートの監視

アクションの発行とステートを監視してみます。

using UnityEngine;
using UniRx;
using Redux;

public class Hoge : MonoBehaviour
{
  public IStore<State> Store { get; private set; }
  void Start()
  {
    // ストアの生成
    store = new Store<State>(Reducers.Hoge, initialState);
    store.Subscribe(state => {
      // stateが変わったら呼ばれる
      print(state.Counter);
    });
    // ボタンのイベント(ボタンが2つ配置されていると仮定)
    var buttons = GetComponentsInChildren<Button>();
    foreach (var button in buttons)
    {
      var name = button.name;
      button.onClick.AddListener(() => OnButtonClick(name));
    }
  }
  void OnButtonClick(string name)
  {
    if (name == "Increment")
    {
      // アクションの発行
      this.Store.Dispatch(new IncrementAction());
    }
    else if (name == "Decrement")
    {
      // アクションの発行
      this.Store.Dispatch(new DecrementAction());
    }
  }
}

注意点

一つだけ注意点があります。UniRx が iOS の Mono2x に対応していないため iOS アプリのビルドをする場合は IL2CPP に設定をしてから実行する必要があります。

おすすめUnity本

今から始める Swift 3 対策

Xcode 7.3 から Swiftのバージョンが 2.2 になりました。このバージョンから Swift 3 に向けた deprecated(非推奨)警告が出るようになったのでその対策です。

'++' is deprecated: it will be removed in Swift 3

結構話題になったインクリメント(デクリメントも)演算子の廃止の対策です。+=使えってことらしいです。Python もインクリメント演算子ないので、案外無くても困らないのかもしれませんね。

修正前
var i = 0
i++
修正後
var i = 0
i += 1

「--」も同じで「-=」を使いましょう。

C-style for statement is deprecated and will be removed in a future version of Swift

C言語風の for 文が廃止になりました。下記の例だとはじめに「'++' is deprecated: it will be removed in Swift 3」の警告が出ます。インクリメント演算子を修正したあとにこの警告が出るので一気に修正してちゃいましょう。

修正前
for var i = 0; i < 10; ++i {
  //
}
修正後
for i in 0 ..< 10 {
  //
}

「i」は宣言しなくても良いみたいです。少し気持ち悪いけど慣れれば大丈夫です。

Use of string literal for Objective-C selectors is deprecated; use '#selector' instead

セレクタの渡し方が変わりました。Objective-c に近くなった印象です。個人的にも文字列でいいんかいなと思ってたのでまぁ妥当な変更だと思います。

修正前
class HogeViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let button = UIButton()
    button.addTarget(self,
      action: "RespondToButton:",  // 警告
      forControlEvents: .TouchUpInside)
  }
  func RespondToButton(sender: UIButton) {
  }
}
修正後
class HogeViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let button = UIButton()
    button.addTarget(self,
      action: #selector(HogeViewController.RespondToButton(_:))
      forControlEvents: .TouchUpInside)
  }
  func RespondToButton(sender: UIButton) {
  }
}

Use '#selector' instead of explicitly constructing a 'Selector'

文字列の代わりにセレクタ関数を使ってた場合も同じです。

修正前
class HogeViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let button = UIButton()
    button.addTarget(self,
      action: Selector("RespondToButton:"),  // 警告
      forControlEvents: .TouchUpInside)
  }
  func RespondToButton(sender: UIButton) {
  }
}
修正後
class HogeViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let button = UIButton()
    button.addTarget(self,
      action: #selector(HogeViewController.RespondToButton(_:))
      forControlEvents: .TouchUpInside)
  }
  func RespondToButton(sender: UIButton) {
  }
}

'init()' is deprecated: init() will be removed in Swift 3. Use nil

init()が非推奨になりました。C言語で開発されたライブラリを使ってると遭遇確率が高いと思います。

修正前
var musicTrack = MusicTrack()
MusicSequenceGetIndTrack(musicSequence, 0, &musicTrack)
修正後
var musicTrack: MusicTrack = nil
MusicSequenceGetIndTrack(musicSequence, 0, &musicTrack)

アドレス渡しする前にコンストラクタ呼び出しするのは確かにおかしいので納得です。

UnsafeMutablePointerを使う場合

UnsafeMutablePointer を使ってる場合も同じ警告がでます。

修正前
let packetListPtr: UnsafeMutablePointer<MIDIPacketList> = UnsafeMutablePointer.alloc(1)
var packet = UnsafeMutablePointer<MIDIPacket>() //警告
packet = MIDIPacketListInit(packetListPtr)
修正後
let packetListPtr: UnsafeMutablePointer<MIDIPacketList> = UnsafeMutablePointer.alloc(1)
var packet: UnsafeMutablePointer<MIDIPacket> = nil
packet = MIDIPacketListInit(packetListPtr)

別の関数で Init するのに宣言時にコンストラクタ呼び出しするのはおかしいですね、これも納得です。

参考