【Unity】UniRxで独自オペレータを作る、Observableの拡張
UniRx
いつもお世話になっています。
オペレータとは
IObservable<T>の拡張メソッドとして用意されたストリームソースを加工するためのクラス
オペレータはIObservable<T>を継承しているので、メソッドチェーンすることができます
ざっくりふわふわとした説明は以上
作るもの
オペレータの作成のためにはまず2種類のクラスと拡張メソッドを定義する必要があります
具体的には
- OperatorObservableBase<T>を継承したストリームをSubscribeするクラス
- OperatorObserverBase<TSource,TResult>を継承したストリームを加工する処理を書くクラス
- IObservable<T>をソースとして受け取り、作成したOperatorObservableBase継承クラスの中にソースを流し込んでnew()して返す拡張メソッド
です、最初は何言ってるかわからないと思います
適当に作っていきます
BossObservableをいうものを作ってみようと思います
原理の説明やこれなんのGenericやねんというのはまったく説明しません
オペレータが作成された時(newされた時)に
「デキタヨ!」
オペレータの中を処理が通った時に
「トオッタヨ!」
オペレータの中をErrorが通った時に
「アワワワワワ」
ストリームがCompleteした時に
「ボクニマカセテ!」
を出力したいと思います、特に意図はありません。
まずはBossObservableを定義します
これで定義できました。
基本的にObserverOperatorBaseを継承したクラスの
new()
OnNext()
OnError()
OnComplete()
にいろいろな処理を書くことができます
ストリーム中にインプットを止めたい・・・などはかなり多い拡張かと思います
次は拡張メソッドを定義します
シンプルです、前回のオペレータをsourceとして引数で引っ張ってきて、newして返すだけです
途中の処理は重複回避やnullチェックですが、重複してもよければ省きましょう
以上でBossの定義ができました
これで全てのストリームに対して.Boss()を挟むことができます
やったぜ
最後に
UniRxに既に定義されているオペレータだけでも処理はできますが、やはり1つラップされた処理があると使う方は非常に楽です
書いていれば理解できてくると思うので、ぜひいろいろ試してみてください
【Unity】Zenjectをマルチシーンで使うときの3つの方法
Zenject関係の過去記事
マルチシーンでZenjectを使う
少し慣れてきた人向けです
Zenjectの中核となるDiContainerは、SceneContextというクラスに紐付いている
SceneContextが破棄されることでBindされていたデータも破棄されてしまうため、シーンを跨いでSceneContextが破棄される場合、データを保持できない
また、複数シーンを使用する場合、各々にSceneContextが存在する場合、何もしなければContext間でBindされたインスタンスは共有されない
そのため、マルチシーン用の機能を使う必要がある
方法は三種類ある
1.ProjectContextを使用する
https://github.com/modesttree/Zenject#global-bindings
ProjectContextをSceneContextと同様に作成してprefab化する
InstallerをprefabにアタッチしてAssets/Resources/以下に置くことで
いずれかのSceneContextが読み込まれた際に自動でResources.Loadで生成される
ようになる
ProjectContextの特徴として
- DontDestroy属性付きで生成される
- 全てのSceneContextから参照される
ため、各SceneContextから、ProjectContextでBindされたインスタンスを参照することが可能になる
また、ProjectContextを操作したい場合はsingletonになっているので
で取得できる
どのSceneContextが読み込まれた時でも生成されるので、デバッグでも本番環境でもいてほしいBindインスタンスはこれで良いと思う
ただ、デメリットとして、いついかなる時でもSceneContextが生成されるとひっついてくるので、Testなどには向かない可能性高し
2.ContractNameを使用したParenting
https://github.com/modesttree/Zenject#scene-parenting
ProjectContextを使用しない方法その1
SceneContextには、ContractNameというパラメータを設定できる
これの
ここ
ContractNameを設定することで、SceneContextに属性を設定することができる
また勘のいい人はすでにお気づきかもしれないが
すぐ下に"ParentContractName"なるパラメータがある
ここに指定したContractNameの属性を持つSceneContextがParentとして参照されることで親のインスタンスがBindされているかの様に振る舞うことができるができる
Parentに指定したSceneContextが読み込まれていない場合、エラーになるので読み込み順には気をつけよう
また、Parentに指定されたSceneContextが先に破棄された場合も同様にエラーになる
特徴をまとめる
- Parentは子のContextを参照できない
- Parentは子のContextより先に読み込まれていないといけない
- Parentが子より先に破棄されてはいけない
使用するメリットとして、同じContractNameをつけることで
デバッグ用
本番用
テスト用
などのContextを用意し、状況に応じてシーンを付け替え、動作を変えることができる点があげられる
子からParentになっているSceneContextのContainerを指定してアクセスする場合
子のContainerをInjectで取得して
で取得できる
3.ContractNameを使用したDecorating
https://github.com/modesttree/Zenject#scene-decorators
ProjectContextを使用しない方法その2
前述のSceneParentingよりもシンプル
SceneContextを作成するのと同じ様に、DecoratorContextを作成する
DecoratedContractNameに、SceneContextに設定したContractNameを指定する
あとはInstallerを追加して〜で終わり
デバッグコマンドの追加や、元となるシーンに対して何かを付加する場合に使用すると良い
元のシーンを全くいじることなく、何かの機能を付与することができる
またSceneParentingを使用した場合と違って
- DecoratorContextと指定したSceneContextは相互に参照できる
- DecoratorContextのあるシーンは指定したSceneContextの前にロードされていないといけない
点に注意する、特に二個目はクセが強いと思うので・・・
まとめ
SceneParentingだけ使えばいいんじゃないかな感があるものの、それぞれメリットがあるので一考する
あと、UniRxとMVP(っぽい)設計とZenjectを組み合わせたアーキテクチャを試行錯誤しながら大体まとめられた気がするので、いつか書きたいなと思う(弱気)
Zenjectを使ってUnityの最前線(笑)にいる気持ちで開発しよう!
ついでに
実はもう一個シーン間の受け渡し方法があるんですが、ZenjectSceneHandlerで調べてください
めんどくさすぎるので紹介しませんでした
【Unity】アセットストアで地雷を踏まないための5つのTips
2017/7/10追記
アセットストアがアップデートされ、見た目が大幅に変わりました
一部のTipsがあまり意味をなしていない場合があります
あんまり変わってなかった
アセットストア
モデル、サウンド、エディタ拡張、完成プロジェクトまで様々なアセットを販売、購入できるプラットフォーム
高品質なアセット(普通に発注すればうん百万は下らなさそうなもの)も100$前後で買えたりします
Unityを使う一つの理由と言っても過言ではないんですが・・・
地雷が多いんじゃ
審査さえ通れば誰でも販売できる・・・そのため
- 品質がまちまち
- まともに動かすためには特定の手順が必要
- 他のアセットと競合
- importするとエラー
- etc
などなどの問題が多発します
C#やUnityスクリプトなどに深い知識がない人は、エラーを解決できずに泣く泣くアセットを使わない選択をすることもあると思います
なので、アセットストアで購入する際に地雷(が比較的多い)を選別するTipsを書こうと思います
ちなみに書いてる本人はアセットを15万円分くらい買ってます^p^
Tips.1 - 作成バージョンを見る
例でPlaymakerを見てみます
このトップ画面の下部にある
ここに注目します
「パッケージはUnity....で作られました」という表示があります
ここの部分の対応バージョンが自分の使用しているバージョンをサポートしているかを見てみましょう
この画像では最高で5.4.0となっています
つまるところ、現在最新版のUnity5.6.1で正常動作する保障はされておりません
表記がそうなっているだけで、5.6での動作を確認している場合もあるようです
さすがに、Unity5で使用したいのにUnity4で作成されたアセットなどの場合は気をつけましょう
Tips.2 - アップデートの頻度を見る
アセットは、アセットの開発元がユーザーからのバグ報告対応や、Unityのアップデートに合わせてバージョンアップする場合があります
このアップデートの頻度が高いほど、信頼度が高いです
バージョンの数字をクリックするとリリースノートを見ることができます
また、サポートウェブサイトとパブリッシャーサイトもチェックしてみましょう
詳細なアップデート履歴が観れる場合があります
ここが404NotFoundになっている場合、アップデートはほぼ絶望的と思ってもらって構わないと思います
Tips.3 - ディレクトリ構成を見る
アセットに含まれるファイルやフォルダ構成をパッケージ内容という部分で見ることができます
ここの判断基準は幾つかあります
-
StandardAssetsが含まれているかどうか
まず、StandardAssetsが含まれている場合が一番危険です
Unity4やUnity5のStandardAssetsが含まれている場合がある上に、ディレクトリが違う場合重複してインポートされます
そのため、DemoSceneなどで使用しているという理由でStandardAssetsが含まれている場合は注意しましょう
もちろんimport時にチェックボックスを外し、importしなければ問題はないです
-
パッケージのルートディレクトリが一つになっているかどうか
ルートディレクトリが一つになっていない場合(最近はアセットストアのルールで絶対になっているらしい?)
自分で作ったディレクトリと重複する可能性があるため、非常に困ります
これもおかしな話です、かなり少数ですがPluginsディレクトリに入っているアセットが幾つかあります
追記
Pluginsに入れる理由としてはAssemblyがfirstpassに移るからっていうのがありますね
— ケットシーウェア (@caitsithware) 2017年6月15日
C#で作っていてもjsからアクセスできるようになったり、ユーザーが無名空間に作ったクラス名と競合してもAssemblyが分かれるので衝突しないとか、いろいろ利点があります https://t.co/3Hv6n6plUX
Arbor2(https://www.assetstore.unity3d.com/jp/#!/content/47081)の作者様よりご指摘がありましたので引用させていただきます
Pluginsに入っていることはアセット提供側からするとメリットがあるため、逆によく考えられていることの方が多いかもしれませんね
なので、それ以外の場合(モデルデータや明らかにおかしいもの)は少し考えた方が良さそうです
- ソースコードの名前
ソースコードの名前がCameraControllerやGameControllerなど、重複する可能性の高い名前の場合注意が必要です
スクリプトを消すことでアセット自体が動作しなくなることもあり、コードを修正する必要が出てきます
-
ファイルの名前に全角文字やスペース、特殊文字が含まれているかどうか
これは基本的には問題にならないのですが、Gitなどを使用したバージョン管理をする場合非常に問題になります
一度繁体字が紛れていたことがあり修正に時間を取られました
気をつけましょう
Tips.4 - レビューではなくサポートフォーラムを見る
ページ下部にあるレビュー欄
仮でピックアップしてみましたが、全く当てになりません
現在のバージョンではないレビューの場合があるためです
また、1年前や2年前の投稿が出てくることはザラです、信用しないようにしましょう
現在進行形でアセットの評価をみたい場合はこちらを利用すると良いです
https://forum.unity3d.com/forums/assets-and-asset-store.32/
Unityの公式フォーラム内にある、AssetStore関連のスレッド群です
利用したいアセット名で検索することで、ユーザーの感想、問題点の共有やデベロッパーとのやりとりを見れる場合があります
私もデベロッパーに(本当にたまに)意見を言っています、だいたい拾ってくれますし丁寧に答えてくれることが多いです
英語ですが
Tips.5 - 使った人のレビューブログを見たり、可能なら直接聞く
当たり前の話ですが、一番確実です。
特にUnityはコミュニティが強いので、日本人もたくさんいます、聞きましょう
最後に
「超個人的」な印象ですがSimpleとかEasyとかついてるアセットをあまり信用してないです
判断基準の一つになれば幸いです、良いUnityライフを
アセットもバリバリ使ってる現在開発中のゲームの公式サイトはこれ(宣伝)
https://www.studiophantomisland.com/
よろしくお願いしますーす
BitBucketのプラン改定でLFS容量が10GBから1GBになった話
BitBucketのプラン改定
Pricing Updateというまあありていにいえば価格改定が行われ
その結果無料プラン、有料プランにLFSの容量が組み込まれた
改定前
リポジトリ容量2GB limit
LFS容量10GB limit
改定後
リポジトリ容量2GB limit
LFS容量1GB limit
(!!!!?????)
LFSの容量は有料プランの段階に応じて5GB -> 10GBと増え
それ以上はストレージ容量を月額で買う感じのようだ
値段は10$ / monthで100GB使える
結局
むしろよく10GBも使わせてくれてたよなと思いつつも、急に変えられて困った
支払い関係でpaypalが使えず(外部連携すれば使える)
JCBのカードしか持っていない弱小の僕はgithubの有料プランへ移行した
privateリポジトリを作るためにgithubの有料アカウントを取得
毎月5$払ってLFS容量を50GB買っている
だいたい月に12$弱持ってかれてる計算だ
Githubに移行してから
とりあえずリポジトリで使用しているLFS容量が9GBほどあったので、cloneに多大な時間がかかる
また、使用できるリポジトリの通信帯域制限50GBがあるのでcloneコケすると軽く数GBの帯域を持って行かれた
通信帯域制限はBitBucketにはなかったと思うので、Github有料にする前によく考えればよかったなーと思う
Nowでこんな感じ
兎にも角にも、サービスを使っているとこういうコストが生まれるので
やっぱり自前でサーバー立ててgitlabとか使った方が最終的には安く済みそうだなと思った。
終わり
【Unity】ZenjectのAsSingle(),AsCached(),AsTransient()の違い
Zenject使うじゃん
— ペンギン (@YutaDevelop) 2017年4月21日
AsSingleでBindするじゃん
UnBindするじゃん
もう一回Bindするじゃん
Injectされない!!!!!!
AsTransient使えば解決なんだけど、GC走って回収されてるのにキャッシュ残ってるってどういうことなんなんなん
調べてみた。
超適当にテストコード書いて挙動を確かめた
メソッドの名前が意味不明なのは許してほしい
挙動を理解しないとヤバい事故生みそうだなこれという結果になった
結果的にはAsSingle()でInjectされてる参照がUnbindで破棄されてなくて、Inject済みの参照をもう一回Bindし直してた
あとGCで回収されてなかった
そもそも
As〜ってなんぞやというと
BindするクラスやインスタンスのBind方法の定義である
https://github.com/modesttree/Zenject#binding
AsSingle(),AsCached()はキャッシュするのね〜
AsTransientはキャッシュ作らないのね〜くらいの認識だと
しぬ(しんだ)
AsTransient()
クラスをBindする際に複数インスタンスの生成を許容する
クラスがContainerにBindされている際、参照情報が一つであればResolveで解決出来る
しかし、Resolveは単一のインスタンスに対してのみ参照を解決するため
AsTransientでBindを複数回行っていた場合、multiple instanceエラーを吐く
AsTransientでのBindには必ず.WithId()を使用してIdentiferを定義するか
ResolveAllを使用した参照解決を図るようにした方が良い
また、As〜などの定義をしなかった場合はデフォルトでAsTransientになる
Factoryなどを使用したインスタンス生成もAsTransientになるため、Instantiateで複数生成したクラスに対してResolveするとエラー吐く、気をつけよう
(まあInstantiateした時点でResolveすることなんてほとんどないんだけど)
AsCached()
Bindされているクラスがすでにインスタンス化されている場合、同じインスタンスを使い回すようにする
また、UnbindするとCacheされていたインスタンスは全て参照を破棄される
同じインスタンスは使いまわしたいが、Unbindで破棄したい時はこれを使用する
クセがなく、取り回しがしやすいので動的Bindをする際は非常におすすめしたい
AsSingle()
クラスをBindする際にSingletonとして定義する
クラスがContainerにBindされている際は
「そのContainer内で唯一存在するクラス」となることが保障される
Bindされている際は、Cachedと同じ挙動をする
しかし、Unbindを呼んだ際に
Bindした自身の参照を他のクラスにInjectしなくなるが、生成したインスタンス自体は破棄されずキャッシュされたまま
のため、Unbindを呼び、その後もう一度同じクラスをBindしても
new()などが走らず、キャッシュされていた参照をもう一度Bindして使い回す
(HashCodeでやっているが、他にもResolveで返ってきた値の中身を書き換えたところどちらも書き換わった)
AsSingle()は必ず一つ、かつ一度しか生成されないオブジェクトに対してのみ使用しよう
まとめ
readmeには、「AsSingle()をほとんどの場面で使いたいと思うでしょうが〜」とか書いてあるけど
基本AsCached()でいいわ。
AsSingle()で動的Bindすると初期化走らせられなくてビビった結果、動作を知れたのでまあよかったと思う。
【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すると痛い目にあう(あった)