【Unity】Zenjectで禅の心を手に入れる
Zenjectについて
導入を躊躇っていたものの、あるところから脅しがかかったので本格的に使い始めた
基本的な使い方などはこちらをご参考いただければわかるのではないかと思います
Unity3DのDIフレームワーク、Zenjectの紹介 | Aiming 開発者ブログ
今回はちょっと具体的な使い方を模索してみました。
何ができるのか
リポジトリのreadmeを読めばわかる
DI(Dependancy Injection)を行うためのライブラリで、すっごい簡単に言うと
newの引数とか、シングルトンで引っ張ってきてたオブジェクトを、登録しておいたオブジェクトの中から勝手に引っ張ってきて自動で参照を入れてくれるやつ
って認識でいいと思う、もちろんこれは機能のごく一部に過ぎない
他にもいろいろできる、公式読んで
中身の話
Zenjectの基本は
- Containerにオブジェクトを登録
- Containerに登録したオブジェクトをインスタンス化(この時に引数などの参照が自動で持ってこられる)
- オブジェクトが作られたらごにょごにょする
になる
Containerは、登録されたオブジェクトが入ってるDictionaryみたいなもので
ここの中にオブジェクトが登録されていないと、参照を解決できない
また、自動で参照してくれるようにするためには、参照先のオブジェクトも登録してあげないといけない
つまり、こう
これは全てにおいて適用されるので、まずBindを忘れないようにしよう。
使い方
まだ全てを把握できているわけではないので、状況別で
SingletonにしていたManagerを、Singletonにしない
(MonoBehaviour継承クラスに、参照を渡す方法)
Zenjectには、ZenjectBindingというコンポーネントが存在する
こいつは、登録したコンポーネントを勝手にContainerに登録してくれるすごいやつ
実際に使ってみるとこうなる
Managerの中身は、普通のMonoBehaviour継承クラスになっている
使用する場合は、こう書く
MonoBehaviour継承クラスの場合は、Injectアトリビュートを使用することで自動で参照が設定される
非継承の場合は、newで渡してあげる
これで、Managerは自動で使いたいところで使えるようになるので、ただのstatic参照のためだけにSingletonである必要がなくなる
もちろん、同一インスタンスが一つしかないことを保証するために使うのはあり
同一クラスは一つのみ生成し、あとはキャッシュを使う.AsSingle()というBind方法が存在する
同じことをしてる人がいたので、こちらも
余談ですが、torisoupさんはいつも勉強になる記事をあげてくださっているので定期購読をお勧めします
また、Manager以外にもZenjectBindingに登録さえすればなんでも使えます
Playerとかカメラとかを登録すると捗るかもね
Pureクラスを好きなタイミングでBindしながら生成する
MonoBehaviourの参照を渡したりするPureクラスとか・・・
クラスがごちゃごちゃしがちな原因の一つだが、Zenjectを使用することで解決出来る
まずMonoBehaviour継承クラスで渡したいクラスをBindする
- シーンに最初から直置きしてあればZenjectBindingを使用する
- 動的に生成される場合は、Container.Bindを使用してContainerに登録する
動的生成する際に勘違いされがちだが
別にInstallerを使って記述しなくてもいい
DIContainerへのBindさえできればよく、さらに
DIContainerはBindされているので好きなクラスにInjectで引っ張ってこれる
好きなタイミングでBindすればいいと思う
試しに、ボタンを押したらHogeをBindして生成するにはこうする
GameManagerはBindInstallerか何かでBindしてあげる必要があるが
BindさえすればDIContainerを引っ張ってこれるのでこういう書き方ができる
Container.Resolve<Hoge>()はBindされているオブジェクトをインスタンス化してreturnするメソッド
他にも.TryResolve<T>()などのメソッドが用意されているので、動的バインドする基底クラスかヘルパーを作ることをお勧めする
Resolveについては公式を
https://github.com/modesttree/Zenject#dicontainer-methods-resolve
ちなみに、BindされたPureクラスは明示的にUnBindしない限り参照を握られ続けるため破棄されない
Disposeメソッドなどを使い、破棄するタイミングでContainer.UnBind<Hoge>()を呼んであげよう
(これって非推奨だったりするのかな、知っている人がいたら教えてください)
その他
.FromNew
.FromMethodsとかは紹介したかったのですが、長くなりそうなのでまとめたときにでも
DIContainer.Inject
DiContainer.Instantiate
は今試してます。追記予定。
処理速度とかの測定はしてないんだけど、Bindは結構重いらしい
まあそうだろうねって感じだけど、これも測らないといけないね
終わり
禅の心強いわー
【Unity】StateMachineBehaviourについてと注意まとめ
StateMachineBehaviourとは
過去記事
AnimatorのStateにAddComponentできるスクリプト
(MonoBehaviourみたいな感じ)
クラスに継承させることで使用できる
役に立たない公式リファレンスはこれ
https://docs.unity3d.com/jp/540/ScriptReference/StateMachineBehaviour.html
動作
基本的には用意されたコールバックをoverrideすることで使用する
- OnStateEnter
- ステートマシンがこのステートを評価している場合、最初の Update のフレームで呼び出されます。
MonoBehaviourで言うところのStart()
- OnStateUpdate
- 最初と最後のフレームを除いて Update フレームごとに呼び出されます。
MonoBehaviourで言うところのUpdate()
- OnStateExit
- ステートマシンがこのステートを評価している場合、最後の Update のフレームで呼び出されます。
MonoBehaviourで言うところのOnDestroy()
Update()が完全に流れなくなるフレームで呼ばれる
----------この辺からよく知らずに使うと闇---------------------------------
- OnStateIK
- MonoBehaviour.OnAnimatorIK の直後に呼び出されます。
IKを使用した処理を行う
overrideすると、IKの処理を自前で実装することになり(Animator.SetIK〜を使う)
Stateに対してIKが自動で適用されなくなる
base.OnStateIKを呼んでもダメ
基本overrideする必要なし
- OnStateMove
- MonoBehaviour.OnAnimatorMove の直後に呼び出されます。
Animationに設定されたRootAnimationの処理を行う
Animation内のPosition、Rotation、Scaleに関わる処理が行われるため
overrideすると、Stateに対してRootAnimationが適用されなくなる
base.OnStateMoveを呼んでもダメ
基本overrideする必要なし
AnimationCrossFade時のStateの取り扱いについて
CrossFadeのリファレンス
AnimationがCrossFadeしている時(durationが0以上の時)
CrossFadeが終了するまで、CurrentStateはCrossFade元のまま
さらに、normalizedTimeもそのまま加算され続ける
normalizedTimeを使用した処理をする時は、CrossFadeを使用しないようにするか
自前でnormalizedTimeを使用しよう
最後に
用法用量を守って使う
基底クラスを作ろうとしてOnStateMoveとOnStateIKをoverrideすると痛い目にあう(あった)
【Unity】OnStateMoveをoverrideするとRootMotionが適用されずに詰む
StateMachineBehaviourについてのまとめを読んでください
概要
Unity.ver 5.4.4
UnityにはStateMachineBehaviourというものがある。
Unity - スクリプトリファレンス: StateMachineBehaviour
AnimatorのStateに紐づけられるスクリプトで、そこそこ便利
その中にOnStateMoveというoverrideできるメソッドがある
日本語のリファレンスはこんな感じ
何も書いてないに等しい
それで今回は表題の通り地雷を踏んだ
RootMotion
RootMotionはAnimationに適用されているポジションや回転をオブジェクトに作用させてくれる機能で、それがなぜか効かなくなって半ギレしていた
(Apply Root Motionはチェックを入れていて、そのほかもろもろもすべてokだった)
StateMachineBehaviour用の基底クラスを作成して、処理を共通化した時に効かなくなったので何かあるなと思い調査
結果、StateMachineBehaviourのOnSTateMoveがRootMotionのPosition更新制御を行っていて、それを直接overrideして書き換えている様子
なので、overrideしているとそのStateにセットされているアニメーションは適用されなくなるらしい
base.OnStateMove()を呼んでみたものの、適用されなかったので
OnStateMove()は自前で移動処理を書かないなら
絶対にoverrideしてはいけない
でFA、OnStateUpdateで間に合わせよう
この辺の仕様はマジでリファレンスに書いとけよUnity
UnityのコーディングにVisual Studio for Macを導入して一週間が過ぎた
誰か俺にVSforMacを入れる勇気を
— ザツヨウペンギン (@YutaDevelop) 2017年2月23日
入れました、導入した後にやった設定と感想をつらつらと書きます
とりあえずPreview版とはいえ、まあなかなかでした
導入・インストール
New Release Preview: Visual Studio for Mac | Visual Studio
ここからインストールしてください
導入・Unity側の設定
Preferences を開いて
External Tools からExternal Script Editor でインストールした Visual Studio for Macを指定してあげる。
これでスクリプトファイルを開くと動くぞ!
とはいかない、このままだとインテリセンス(補完)が効かない
導入・VSforMac側の設定
※機密プロジェクトがあるので名前がバレないように「あ」を入力してます
Open から、Unityのプロジェクトディレクトリの中にある.sinファイルを選択して、ソリューションを開こう
.sinファイルがない時は、Unityでスクリプトを選択して開けば多分自動生成されるはず
これでインテリセンスが効くようになる
ただこのままだと死ぬほど使いづらいので色々カスタムする
カスタマイズ・エディタカラー
まずはエディタのカラーを変える、白背景とか、謎の文字色とかゴメンだし
Visual Studio から ユーザー設定を開く
余談だけど日本語のメニューなのいいよね。
インターフェースを エンジニア大好き Dark にした
テキストエディターからColor Themeを選択、選択すればエディタが変更されるので好きな色を選ぼう
現状Theme編集機能はないらしい
自分でカスタマイズしたテーマがあるときは、MonoDevelopなどからエクスポートして持ってこよう
自分はOblivion派なので、今回は割愛
カスタマイズ・ポリシー
ソリューションごと、あるいはグローバルでコードフォーマットなどを定義できる
ここから新しくポリシーを作る
基本的にはコードの書式設定から、C#の設定項目を変更していく
自分の好きなようにやろう
ソリューションごとにポリシーを適用できるので、プロジェクトを複数抱えてても安心
ポリシーにAttribute改行の設定項目がない件について
VSforMac
— ザツヨウペンギン (@YutaDevelop) 2017年2月23日
アトリビュートの自動改行マジで何とかしろよ…_:(´ཀ`」 ∠):
こいつさえなければ…
あとアトリビュートの中身にインテリセンス効かない
— ザツヨウペンギン (@YutaDevelop) 2017年2月23日
KUSO
Unity使ってると頻繁に出てくるSerializeField変数を一行で宣言したい時、アトリビュートで勝手に改行されるのでその度にCmd+Zを押すことになる
その上アトリビュートで囲むと補完されないので一回一回手打ちになる
カスタマイズ・コードテンプレート
とりあえず上記の問題を解決するためによく使うアトリビュートをテンプレート化した
追加する時はユーザー設定からCode Snippetsを選択して、追加をする
また、$class$などの記述を行うことでフリー入力を作ることもできるので活用する
今回、アトリビュートでよく使うSerializeFieldを登録した
ただ、テンプレートに
[SerializeField] $class$ $field$
みたいな感じで記述すると勝手に改行されたのでやらないことにした
わけわからんので製品版で早く設定追加してほしい
ただ海外フォーラム見てると超昔から言われてるのに開発者がResharperを使ってるからいつまでも追加されないとかなんとか
カスタマイズ・パフォーマンス
とりあえずで上記の設定を終え、使ってみると
インテリセンス遅すぎワロタ状態
だった
とにかくクッソ重い、ちなみに使用してるmacは2015年モデルのフルカスタマイズなのでスペックが低いわけじゃないと思う
なので、パフォーマンス改善をする
まずは何はともあれテキストエディタからアニメーションを無効に
強調表示とかも消す
他にも視覚効果をOFFっていく
効果があるかはわからないものの、コードの動的コンパイルが走るので解析も毎回走るんじゃないかと思い解析も切った
だいぶ早くなった
カスタマイズ・キーバインド
人それぞれすぎるので割愛、設定しとくと楽やで(提案)
最後
頻繁に落ちる上に操作不能になるMonoDevelopよりは使いやすい
VisualStudioCode、Atom、VimとかのエディタよりIDE派なので今後に期待したい・・・特にアトリビュートとパフォーマンス
メモリ2GB食われるのでメモリの残量には気をつけよう
もうしばらく使ってみる
昨今のUnity開発におけるMVP設計思想についてと、それの適用可否の話
こちらも合わせてどうぞ
はじめに
こちらの記事を読んで、最近Unity開発でよく言われるようになった
MVPについてだいぶ浸透(あるいは導入)してきたのではないかと思った。
ただ、すべてのゲームがこの思想でカバーできるかというとそうでもないと思っている。
「そりゃ仕様によって最適解は変わるだろw」と思うかもしれないが
言いたいことはそういうことではなく
MVP設計というのは元々Web業界で浸透した設計であり
WebのフロントはユーザーがUIを操作するものであるということだ。
つまりこの設計思想をそのまま、あるいは少しのアレンジを加え適用できるのはユーザーがUIを操作することが基本となる
モバイル向けソーシャルゲームアプリということである。
それを意識せず、ユーザーがコントローラを持ち、キャラクターを操作するタイプのゲームなど(あくまで、など)で
「MVPっていうのがいいらしい」
「とりあえず導入しよう」
でどこまでがModelでどこまでがViewでPresenterをどこまで定義すれば良いのかが曖昧のまま設計が迷走する例をよく見た。
MVPの具体的な活用法に対しては先ほどの記事を読んでもらえばわかると思うので
今回はModel-View-Presenterに対して、各レイヤーの解釈について特にUnityゲーム開発における持論を書こうと思う。
ある人にとっては間違っているかもしれないが、ある人にとっては正解への道標になるはずだ。
先に結論を書くと、Viewは積極的に拡大解釈しろという話だ。
あとUniRxとMVPの理解は前提で。
レイヤーの明確な責任
まずModel-View-Presenterにおける役割を明確にするのと同時に
ゲームを作るために必要なレイヤーを全て洗い出す。
MasterData
一応書く。
ゲーム中の経験値テーブルや、アイテムのId、価格などのデータ。
Model
ゲームを構成するマスターデータをもとに、ユーザーの現在所持しているアイテム数や、経験値などのいわゆるセーブ対象になるデータを管理しているもの。
ゲームを進行するごとに、書き換えられていく。
役割として
- データを保持する
- データの書き換えロジックを、メソッドとして外部に公開する
- データが書き換えられた時、通知を発行する
この役割さえ守っていれば、中身でどれだけインスタンスを生成しようと、どんなロジックを持とうと構わない。
逆に言うと、これ以外で役割を持ってしまってはいけない。
そしてModelは依存先を持たず、どんな状態でも自身をインスタンス化できるようにしなければならない。
View
プレイヤーが実際に見る画面と勘違いされやすい(名前的に)。
本質的な役割は
- ユーザーが実際に見る画面を構成し、表示する
- 画面を更新するメソッドを外部に公開する
- ユーザーの操作を通知する
最初に書いた通り、UIを操作するだけであるならこのUIが操作されたという通知を飛ばすだけで済む。
その通知の結果、Viewの更新を受け取るだけで済むからだ。
ただ実際にはそうはいかない。
なぜなら、キャラクターを操作できるゲームであるなら、キャラクターの位置が更新され、その結果アイテムを拾えたりする領域に入ったりするなど、複雑であるからだ。
そして愚直にMVPに当てはめるのであれば、コントローラ操作というViewが通知を発行し、キャラクターのモデルが位置の変更を通知で管理しなければいけなくなる。
はっきり言ってそんな設計は冗長すぎてクソだ。
なので後ほどViewの拡大解釈と自由設計について記述する。
ただ、上記3つの役割自体は変わらないので厳守しなければならない。
そして、Modelと同じく、Viewはどんな時でもインスタンス化可能でなければならない。
Presenter
具体的な機能の提供者である。役割として
- Viewからの通知を受け取り、Modelが外部公開しているメソッドを呼ぶ
- Modelのメソッドを呼んだ結果、Modelからの通知を受け取り、Viewが外部公開しているメソッドを呼ぶ
単体では機能しないViewとModelをつなげ、ゲームとして成り立つような機能をロジックとして提供する
となっているが、例として
- アイテムを拾える領域に入った通知がViewから飛んでくるので
- その通知が飛んできた後で他のViewからボタンが押された通知が飛んできたら
- Modelが公開しているアイテムを一個増やすメソッドを呼び
- アイテムが増えたという通知を受け取り
- Viewが公開している「アイテムを拾った演出」を出すメソッドを呼ぶ
という処理を行うといえば、わかりやすいだろうか。
Viewが作り込まれれば、あとはPresenterを量産するだけでゲームを拡張していくことが可能になる。
以上がレイヤーの責任の話。
Viewを積極的に拡大解釈していく
ここまでの話で、何が一体問題なのかというと
ゲームはユーザーの操作がバリエーション豊富すぎてViewの役割がブレやすい
というところだ。
例として、ゲームの操作キャラクターについての話をした。
ここで考えたい
キャラクターとはどこのレイヤーに属するのか?
ゲームの1機能としてキャラクターを見るのであれば
Model-View-Presenterにまたがって処理を書かなければいけない。
でも、ユーザーの操作だし、キャラだって要は画面の1要素なのだからView単体で記述しなければいけない。
どっちだろうとなる。
結論としては、どっちも正しい
なので
Viewとしての役割を厳守できれば、内部の処理はView内で完結されたMVPで記述されても良いのでは
と考えることも可能である。
そしてさらに拡大解釈するなら
Viewとしての役割さえ守れていれば、中身の設計はなんでもいい
だ。
例として、クラス図を書いてみた。
ViewがCharacter全体の各コンポーネントで、ゲームに関連するHpなどをModelに移譲することで見通しが良くなる。
CharacterPresenterはCharacterの各コンポーネントの通知を使って、Modelを更新すれば良くなるが、既存のPresenterとは違う点がここで生まれる。
それは通知を発行したViewは自身で更新を行うということだ。
ダメージを受けた時に、ダメージモーションの遷移などをいちいちPresenterから発行されていては、Character自身が独立して動かなくなってしまう。
なのでPresenterの役割としては
Viewの通知を使い、Modelを更新した結果、必要であれば他のViewに対しての更新を行う
になる、ダメージの結果HPゲージを更新する処理を呼ぶとかね。
最後
蓋を開けてみればなんだそんなことかと思うかもしれないが
責任を明確にしないままMVPを導入するとカオスになるのを身を以て経験している。
うまくレイヤーを定義してやり、そのレイヤーに属するものは何なのかを考えよう。
そして、責任さえ守れていれば、レイヤーの中身は自由に解釈して構わない
そういう結論を今自分では出している。
推敲されておらず、勢いだけで書いた文なのでわかりづらいかもしれないが
許してね^^
何かあればTwitterまでどうぞ。
あとこんなこと書いてる暇あるならゲーム作れって言われそうなのでゲーム作ります・・・。
UniRxでObservable -> IEnumerator -> Observable変換した時のライフサイクル
コード
gist9ab1097554ad9f37a8931f82cfdcb467
破棄されない話
.SelectMany()にCoroutineを使用するとうまく破棄されない。
.ToYieldInstruction()を使ってObservable -> IEnumeratorに変換したCoroutineを実行中にコンポーネントを削除するとエラーを吐き出す。
.ToYieldInstruction()したCoroutineはどうやら源流のObservableに紐づけられないようなので
別途.AddTo()でライフサイクルを定義してあげる必要がある
CompositeDisposableなどを使用したライフサイクル管理をしている場合は
- 引数でライフサイクルの依存先を渡す
- Observable.FromCoroutine(_ => Coroutine()).AddTo(依存先)
でなんとかできるのではないでしょうか。
追記
@YutaDevelop ToYieldInstructionにCancellationTokenを渡すのが、伝搬方法になっているのですが、SelectManyでは繋ぎにくいですね、一応模範解答はこうですが、面倒くさいのは否めない。 https://t.co/qPPtEVrtOG
— neuecc (@neuecc) 2017年2月20日
作者様
よりご回答いただきました、模範解答はこちらのようです。
CancellationTokenと言う仕組みがあり、引数にそいつを渡してあげることでライフサイクルを定義できるようです。
シランカッタ・・・
使うときはちゃんとオペレータの実装も見ないといかんですね。
neuecc様有難うございました。
終わり
Monodevelop - UnityでVersionControlが消えた時の対処
はじめに
MacOSX 10.11.2
Unity 5.4.2f2
MonoDevelop-Unity 5.9.6
バージョン管理はGit
BlameやDiff、Changesをワンクリックで見れるので重宝してる、こんなやつ
ただこいつはよく消える
消えた時
Monodevelop-Unity > Add-In Managerを開いてこれをenableにする
これで復活
する時もあればしない時もある
しない時はSolutionWindowを開いて(View > Pads > Solutionで開ける)
スクリプトファイルを選択した時に出る歯車アイコンクリック、DiffとかBlameとかLogとか選択すると復活する
それでも復活しなかった時が来たら対処法調べて追記します。