naichi's lab

3日後の自分(他人)への書き置き

Unity1週間ゲームジャムに参加してUniRxとZenjectを使ってみた話 #unity1week

f:id:naichilab:20170903025940p:plain:w320

先週久しぶり(約3ヶ月ぶり)にUnity1週間ゲームジャムを開催し、おかげさまで160もの作品を投稿いただきました!!

f:id:naichilab:20171121211301p:plain:w320

https://unityroom.com/unity1weeks/7

参加いただいたみなさま、ありがとうございました。

まだ全然遊びきれていませんが、少しずつ遊んで感想を残していきたいと思います。

今回は私も参加できたので、この記事にまとめておこうと思います。

作った作品

f:id:naichilab:20171121211635p:plain:w320

Go! Go! Rocket!! | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

1プレイ30秒程度の超シンプルなゲームです。

  • ロケットの最高速度を競うゲーム
  • 操作はタップのみ
  • ゲージ満タンに合わせてタップ
  • 5回の合計で速度が決まる

オンラインランキングもつけてあるのでぜひハイスコア目指して遊んで見てください。

参加目的と個人目標

Unityのリハビリがしたかった

太陽人間を作ってからだいぶ日が空いてしまい、次を作りたいなーと思いつつもUnity開くのも億劫な感じになってたので、小さなものを作り上げてリハビリしようという目的で参加しました。

アセットストアさんのキャンペーンが後押し

unityroom & 1week GAMEJAMの投稿作品にアセットを登録してバウチャーをゲットしよう!

unityさんからお声がけいただき、unityroomと合同で上記キャンペーンを開催させていただきました。めちゃお得なので参加するしかないですよね・・・!

このキャンペーン、応募が必要なのでまだな人は忘れずに!

UniRxとZenjectを使いたい

UniRxZenjectはアセットの名前です。

太陽人間を作った時、クラス同士の結合が強すぎて最後の方にはどうしようもない感じになってしまいました。

次に作るものはもっと規模が大きくなる予定なので、さすがにこのままでは無理だなーと。前からいろいろ調べていて、UniRxを生かしてMV(R)P設計にしたりZenjectでDIするとスッキリできそうーと思ってたのでそれを試してみることにしました。

MV(R)P と DI について

MV(R)P (Model, View, Reactive Presenter)

Webとかでよく聞くMVCみたいなもので、モデル・ビュー、そしてそれを繋ぐプレゼンターって形があるそうです。

f:id:naichilab:20171121231838p:plain

この造りにすると、モデルとビュー(UI)の結合を弱くすることができます。

いろんな記事を読む限り、超便利そうだったので試してみたいと思いました。

これはUniRxを使って実現します。

MV(R)Pについては先にも掲載しましたがこの資料がめっちゃわかりやすいです。

UniRxでMV(R)Pパターン をやってみた

DI

クラス設計のパターンの一つに、Dependency Injectionというものがあります。DI自体はパターンの一つなのでググってください。

依存するオブジェクト(Dependency)を外から注入(Inject)することで、オブジェクト間の結合を弱くしていきます。このInjectを簡単に行ってくれるのがZenjectです。(たぶんそんな感じ。)

実装について

両アセットとも初めてですし、あまり深く考えず、実装しながら形を変えていきました。

ここでは最終的にどんな形になったかを紹介します。

モデル

把握しやすい程度に適当に分けました。

f:id:naichilab:20171121221146p:plain:w480

  • GameStateModel : タイトル/プレイ中/リザルトとかのステータス管理
  • ShipModel : 速度やブースト回数などを管理
  • GuageModel : ゲージの値を管理
  • HighscoreModel : ハイスコア管理

こんな感じ。

他にも細々したのはありますが、大きめなのはこれくらいです。

これらは全てMonoBehaviorを継承しており、シーン内に最初から置いておきます。そのインスタンスをZenjectに指定してシングルトンなInjectになるようにしました。

Zenjectを使うとプロパティにセットする手間がないですし、Awakeの呼び出し順が〜みたいなのも気にしなくてよさそう?なのでとても楽でした。

ビューとプレゼンター

ビューは背景のみSprite、残りは全てUGUIで作ってます。

UI要素それぞれにプレゼンターを作り、モデルのプロパティが変わったことを検知して勝手にビューに反映されるにようにします。

例えばスコア表示のプレゼンター

public class ScorePresenter : MonoBehaviour
{
    private TextMeshProUGUI text;
    [Inject] private ShipModel ship;

    private void Awake()
    {
        text = GetComponent<TextMeshProUGUI>();

        ship.Speed.Subscribe(s =>
        {
            text.text = string.Format("{0}km/h", s.ToString("0.00"));
        });
    }
}
  • これだけでship.Speedが更新されるたびに、textを書き換えてくれます。
  • このスクリプト自体がTextMeshProUGUIコンポーネントのついたGameObjectにつく想定なので、text はAwake時にGetComponentしています。
  • ship はZenjectがInjectしてくれるので、インスペクターその他何も気にしなくてもAwake時には参照が入っています。(すんごい!)

同様にボタンなど、入力を受け付けるプレゼンターは、

public class GoButtonPresenter : MonoBehaviour
{
    private Button go;
    [Inject] private ShipModel ship;

    private void Awake()
    {
        go = GetComponent<Button>();

        //加速
        go.OnPointerDownAsObservable()
            .Subscribe(_ =>
            {
                ship.Boost();
            });

        //燃料切れ
        ship.CanBoost.Subscribe(c =>
        {
            go.interactable = c;
        });

        //以下略
    }
}
  • OnPointerDownAsObservable()でクリック押下イベントのストリームを受け取れます。(簡単!)
  • ship.CanBoostがfalseになった(燃料が切れた)ら、ボタンを押せなくしています。

こんな感じで、モデルにデータと操作を集約。プレゼンターがビューとモデルを橋渡し。って感じで作りました。

ただ、このまま作っていくとモデルにロジック書いちゃってるので、大きくなりすぎる気がします。ゲームロジックはどこに書くのがいいんでしょうか?

UniRxとZenjectについて

この使い方で正解なのかよく分かりませんが、間違いなく 楽だった とは言えます。 もーちょい整理しつつ、うまく使いこなしたいですね。

他に使ったアセット

UniRxとZenject以外に使ったアセットと使い道を紹介します。

DOTween Pro

タイトルロゴ、ロケット、ロケットの炎、これらはDOTweenで動かしています。全てインスペクター上での設定のみでコーディングなし。超楽でした。

Advanced PlayerPrefs Window

エディタ上でPlayerPrefsの設定内容を確認・編集・削除できます。 デバッグ中にハイスコア確認したり消したりとか。使い勝手良くて愛用してます。

QHierarchy

Hierarchyウィンドウを超強化してくれるアセット。GameObjectの有効・無効をクリック一つで切り替えたり、Missingな参照を表示してくれたり。今回初めて使いましたが便利でした。今後も使います。

ちなみに今回のプロジェクトではこんな感じ。

f:id:naichilab:20171121232801p:plain:w320

もりもり情報量増えます。

TextMesh Pro

言わずと知れたアセットですね。標準のUGUITextではぼやけてしまうアウトラインとかも綺麗に表示してくれます。太陽人間のときからテキスト表示はすべてこれです。 どうして標準で入らないんだろう・・・

Asteroids Game Pack

絵が描けないのでロケットと背景素材として購入しました。 親切にIllustrator用の .ai ファイルも入ってたので、ロケットは炎部分と分離してゲーム内では使っています。

Target Platform

フォロワーさんのアセットです。#if を普通の if 文で書けるので便利でした。時間あればAndroid版とかサクッと出しちゃおうと思ってるのでそのときにも活躍しそうです。

まとめ

目標は

始める前に立てた目標も一通り達成できました。

スケジュールは

木曜ぐらいからはじめて、実質20時間ぐらいの作業でした。

土曜の23時でこれ。

めっちゃしんどかったw

でもやっぱり楽しかった

しんどいですがやっぱりゲームを作るのは楽しかったです。

すぐに遊んでもらえて声が聴けるのが最高に嬉しい。

また2〜3ヵ月後に開催すると思いますので、みなさんとゲームを作れるのを楽しみにしています。

今後ともunityroomをよろしくお願いします。