UnityのInspectorはDataStoreの一種かなと思った
ふと思ったのでメモ
やっぱりInspectorもDataStoreの一種だよなあと思う今日この頃
— いも@EFB~相手は死ぬ~ (@adarapata) 2018年2月14日
今趣味で作ってるゲームはMVPで書いてるけど、ModelにDataStoreの概念があります。
public interface IDataStore<TEntity> : where TEntity : IEntity { TEntity ToEntity(); }
持ってるデータからEntityを生成してくれる君です。このデータはサーバからJSONで来てるかもしれないし、ScriptableObjectやAssetBundleかもしれないけどインタフェースで持つのであまり気にする必要はありません。
DataStoreがあるのでEntityはUnityの世界から切り離されていい感じになります。
public class Life : IEntity { public readonly int max; public int Current { get; private set; } public float Ratio { get { return max / Current * 100F; } } }
雑なライフポイント的なEntityですが、ここにUnityの概念はありません。
一方で、Unityはインスペクタから値を設定できる機能が便利です。ライフポイントとかはまさにインスペクタからチョチョイと弄りたくなるようなやつです。 単純に実現しようとしたらどちらかになりそう。
- EntityをSerializableにする
- MonoBehavior継承したScriptにメンバ変数を持たせて内部でEntityを生成する
前者はありだけど、インスペクタからごにょごにょしたいがためにやるのはなんだか・・という気持ちに。 ならば後者かなと思ったが、Entity生成のためにプリミティブなメンバ変数を用意する必要があるので、ちょっと散らかしてしまうなあという気持ちになってしまう。するとこれはつまりDataStoreの仕事では?となった。
なので、InspectorのDataStoreを作ってみる。
[Serializable] public class LifeStoreInInspector : IDataStore<Life> { public int max; public int current; public Life ToEntity() { // いい感じに生成して返す } }
これをComponentに持たせてインスペクタからいじれるようにすればやりたいことは達成できる。持つ側もEntity用のパラメータをそれぞれ持たせるよりは見通しがよくなったかなーというくらいの気持ち。 でも、SerializableせずにMonobehavior継承したComponentとして独立させた方が切り分け方として正しいのかもしれない。
GMOペパボ株式会社を退職しました
2/2が最終出社でした。
写真は送別会でいただいたポテチです。 中にポテチ状のメッセージカードが大量に入ってました。Bigサイズくらいはあったと思います。
やってきたこと
2012年に福岡で新卒入社して、研修とOJTを経てロリポップ!レンタルサーバーのフロントやサーバサイドなどを4年ほど担当してました。 去年の春からAndroidエンジニアに転向して東京に転勤、minneのアプリ開発を10ヶ月ほどやってました。だいたいReactive Extensionsの布教活動を行ってた気がします。
また、4年ほど新卒研修でメンター業などをさせていただきました。 そのほか、社内勉強会でシェーダ読書会やったり開発合宿でゲームリリースしたりを繰り返して、社内で「ゲームを作ってる人」ポジションで動いてたりしました。
感謝
入社時はhtmlタグすら書けなかった僕がいっぱしのエンジニアになれたのは、周りの方々の支援・指導の賜物です。とりあえず作れればいいやと考えていた自分がソフトウェアーキテクチャやチームビルディングまで考えて動けるようになったし、それは趣味の領域でも大いに役立ってます。
入社3年目くらいにWEBエンジニアとして力足りてないことに不安を覚えて、ゲーム作ってないでもっと集中した方がいいんじゃないかと悩んだことがあります。そのときに、研修してくださった @hsbt さんが「会社はただの通過点だから好きなことをやっていけばいい」と強く推してくれたのを未だに覚えています。あの一言があったので今の自分があると思ってるし、僕も後輩にも同じことを言ってます。
また、エンジニアだけでなく、皆さん本当に優しい人たちばかりで、この6年間は人間関係でで何一つストレス感じることなく働かせていただきました。最初に入った会社がペパボで本当によかったです。
次の職場
3月中旬から株式会社ミクシィのXFLAGでゲームクライアントエンジニアとして働きます。学生時代から趣味でゲーム開発はしていましたが、今回初めて仕事としてゲームを作る人になりました。 でも趣味でも引き続き作っていきます。 引っ越しもしないし、オフィスがまた渋谷区で通勤手段全く変わらないので、正直生活変わんないです。
これから
一ヶ月半ほど自由人なので、積んでる本とか読みつつ趣味開発をやっていこうかと考えてます。 ただ、積み本の実践DDDが早速濃厚過ぎて終わらなくて困ってます。今月中に終わるか。 基本的に無職なのでごはんとか呼んでいただけると行きます。 また、福岡にも一旦帰るのでよろしくお願いいたします。
ウィッシュリスト
皆様ご確認のほどよろしくお願いします。
https://www.amazon.co.jp/hz/wishlist/ls/1RJ0EM23AIDBT?&sort=default
2018年の抱負
明けて一週間くらい経ってた。
昨年は家を買ったり東京に引っ越したり、家族が体調を崩したりとだいぶ大変な年ではあった気がする。
今年は2017年より忙しくはなさそうなので、抱負を二つ宣言しとこうかと考えた。
プログラム以外の領域に手を出す
具体的には、絵を書いたり作曲とかをやろうと思う。
きっかけは、東京でいろんなコミュニティに顔を出して様々な開発者と話してからだ。誰も彼も凄まじい情熱を持っており、デザインからコーディングまで全てを一人でやる人も珍しくはない。 自分は基本的にコーディングのみで、音楽やイラストなどのリソースはアセットストアや知人に依頼などが多い。質の良いものを出すという点で委託は良い手段だと思うしこれからもやると思う。が、それはやらない理由にはならんよなあと話してて思った。 作りたいものを他人に伝えられないというところも大きい。以前モデラーの人に作りたいものを文章と口頭で話したことあるけどかなり大変だったし、デザインラフでも描いておけば楽だったんだろうなあと思った。 現状、自分の表現の幅が狭いんじゃないか?という気にはなった。あと単純に楽しそうだから。
とはいえ本業はプログラマーなので、そこのキャッチアップはきちんとしていかないといけないのでどうしようかな〜〜と思ったところで腹が減ったので松屋に行ってきます。
もう一つの抱負は、健康に気を使う事です。
RxJava2でオペレータを自作してみる
Android アドベントカレンダー 17日目の記事です。
Rxは非常に便利なライブラリです。特にオペレータ群はとても強力で、filterやmapやflatmapなど使っておけば割となんとかなります。 が、それ故に雰囲気で書けるとこもあり、「オペレータって具体的にどんな感じで動いてるの?」となることもあります。僕も雰囲気で書いてます。
今回は、実際にオリジナルのオペレータを作ることで、中で何が起きているのかをざっくりと見てみましょう。
事前準備
RxJavaリポジトリをcloneしておきましょう。
今回はforkして手元に持ってきました。
作るオペレータ
なんでも良いですが、とりあえず2種類作ってみましょう。
本来はFlowable、Single、Maybe、CompletableなどそれぞれのPublisherに応じたオペレータを作る必要がありますが、数が多いので一旦Flowableに対応したものだけにします。
Imo ファクトリメソッド
justやtimerなど、ストリームの最上流にいるメソッドです。 彼らは実際にデータを生成してSubscriberに流すのがお仕事です。
今回は、imoオペレータを作ります。役割は以下です
- "imo" という文字列を生成し流す
需要は高そうですね。
Println オペレータ
こちらはfilterやmapなど、最上流ではなく上から流れてきたデータをごにょごにょするオペレータです。
役割は以下。
- 上流から流れてきたデータをSystem.out.printlnで出力する
これら二つを作成すると、最終的に以下のようなコードが書けるようになります。
Flowable.imo().println().subscribe();
// => imo とログが吐かれるだけ
Imoオペレータを作る
FlowableImo class
RxJava2において、全てのオペレータは独立したクラスになっています。 例えばFlowable.justの中身は以下です。
コンストラクタで値をもらって、購読時はSubscriptionにデータを包んでSubscriberに渡します。
そのままだと利用者側はメソッドチェーンで呼びづらいので、Flowableのメソッドで呼べるようにいい感じにラップしてるようです。
なので、新規にオペレータクラスを作るなら2つの実装が必要です。
- オペレータクラスの作成
- Flowableにstaticなメソッドを定義する
なのでまずはjustに習ってFlowableImoクラスを定義します。
public final class FlowableImo extends Flowable<String> { public FlowableImo() { } @Override protected void subscribeActual(Subscriber<? super String> s) { s.onSubscribe(new ScalarSubscription<String>(s, "imo")); } }
imo
の決め打ちになるので引数はありません。subscribeActualでは購読処理のためにSubscriptionをSubscriberに投げています。
継承元であるFlowableがSubscriberインタフェースを実装しているので、subscribeメソッドは実装済みですが、こちらは分岐させたりなどして実際にonSubscribeするところまで至ってないのでActualという意味で分けてるっぽいです。
subscribeActualが呼ばれるのは、Flowable内で適切なSubscriberを生成した後のようですね。
実際Subscribe呼ぶときはonNextだけのConsumerを渡したり、Subscriberを直渡ししたりだとバリエーションが多いので、それらを吸収するための措置っぽい。
定義し終わったら、Flowableから呼べるようにstaticなメソッドを追加します。
public abstract class Flowable<T> implements Publisher<T> { ~~~~~~~~~~~~~~~~~~~ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable<String> imo() { return RxJavaPlugins.onAssembly(new FlowableImo()); } }
back pressure対応やスケジューラなどの設定アノテーションが付いていますが、長くなりそうなので今回は気にしないことにします。
やっていることは、FlowableImoを生成して返すだけです。その際に、RxJavaPluginsでFlowable生成時のフック処理を書いていた場合、それも引っ付けて返すために RxJavaPlugins.onAssembly
を呼んでいます。
Printlnオペレータを作る
次は流れてきた値をprintlnするオペレータを定義しましょう。
public final class FlowablePrintln<T> extends AbstractFlowableWithUpstream<T, T> { public FlowablePrintln(Flowable<T> source) { super(source); } @Override protected void subscribeActual(Subscriber<? super T> s) { source.subscribe(new PrintlnSubscriber<T>(s)); } static final class PrintlnSubscriber<T> implements FlowableSubscriber<T>, Subscription { final Subscriber<? super T> actual; Subscription s; PrintlnSubscriber(Subscriber<? super T> actual) { this.actual = actual; } @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.validate(this.s, s)) { this.s = s; actual.onSubscribe(this); } } @Override public void onNext(T t) { System.out.println(t); // ここがplintlnオペレータのやりたいこと actual.onNext(t); } @Override public void onError(Throwable t) { actual.onError(t); } @Override public void onComplete() { actual.onComplete(); } @Override public void request(long n) { s.request(n); } @Override public void cancel() { s.cancel(); } } }
imoオペレータといくつか違う点があります。
AbstractFlowableWithUpstream
は上流のFlowableを扱うための抽象クラスです。
HasUpstreamPublisher
インタフェースを持つことで、自分より上流にPublisherがいることを保証しています。
この実装により、一個前のFlowableを保持させて、メソッドチェーンでオペレータを繋げられるようになっています。 FlowableImoはファクトリメソッドのため上流がありませんでしたが、printlnオペレータは必ず上流が存在するので継承が必要です。
また、インナークラスとして PrintlnSubscriber
が存在しています。
このSubscriberがオペレータのコアの機能で、onNextでデータを送るときにごにょごにょする部分です。
今回はonNextでprintlnして、データには変更を加えずそのまま流しています。
あとはファクトリメソッドと同じようにFlowableにメソッドを定義します。
public abstract class Flowable<T> implements Publisher<T> { ~~~~~~~~~~~~~~~~~~~ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> println() { return RxJavaPlugins.onAssembly(new FlowablePrintln<T>(this)); } }
以上で、自作オペレータが実装できるようになりました。
Flowable.imo().println().subscribe();
// => imo とログが吐かれるだけ
できあがりはこちらです
見やすいようにPRにしてみました
所感
かなり端折りましたが、オペレータがどんな挙動をしているのかはざっくりわかりました。
ただ、SubscriberがSubscriberとSubscriptionの両方の役割を担っているので、そこのコードを追うのがめちゃめちゃ大変でした。 この辺りは別のタイミングで書こうかと思います。
昔作ったゲームを公開した
出稼ぎダンジョン進捗報告 2
割と時間開けてしまった。
今回は見た目の変更はほぼなし。その代わり、急ピッチで作った部分を色々とリファクタリングなどしていた。
Zenjectの導入
戦闘システム周りなど、敵と味方が相互に依存しあったりして割とつらいコードになっていた。そのあたりのリソースの参照を一か所に集中させるためにシーンごとにDataStore的なクラスを用意してそこから参照するようにしていたが、毎度用意するのがやや面倒だった。
また、Storeがシングルトンという役割を持つので、シングルトンにしたいPureClassなどもここに追いやられていた。
namespace Game.Store { /// <summary> /// Battleシーンのデータを全て保持しているやつ /// </summary> public class BattleStore { public BattlePartyView Party { get; private set; } public BattleEnemyView Enemy { get; private set; } public ActionResult InBattleResult { get; set; } public ActionDisplayView ActionDisplay { get; private set; } public View.MoneyView MoneyDisplay { get; private set; } public Model.Money Money { get; private set; } public void Initialize(){ // それぞれfindしたりなど // PureClassはここで生成したりなど Money = new Money(); } } public class Hoge { private BattleStore store; private Model.Money money; void Start(){ var party = store.Party; money = store.Money; // ほげほげ } } }
こういうのを用意して、全部Store経由で取ってたりした。 流石に将来的にしんどくなる気がしたので、ここにZenjectを導入して、データは極力直接injectする方針に変更した
namespace Game.DI { public class BattleInstaller : MonoInstaller<BattleInstaller> { public override void InstallBindings() { Container.Bind<Model.Money>().FromNew().AsSingle(); } } } public class Hoge { [Inject] private BattlePartyView party; [Inject] private Model.Money money; void Start(){ // ほげほげ } }
- Storeが不要になった
- PureClassの生成はInstallerに移行できたので置き場所がわかりやすくなった
コード量がだいぶ減って見やすくなったのだった。 まだ使いこなせたとは言い難いけど、現状でも十分便利。
BehaviorDesignerを導入
敵キャラの行動パターンは、完全なランダムで技を繰り出すだけだったので細かい思考を調整できるようにしたかった。
なので、昔に買ったけど放置していたBehaviorDesignerを導入することにした。
https://www.assetstore.unity3d.com/jp/#!/content/15277
BehaviorTreeを作ること自体はじめてだったけど、その辺はググりながらでサッと解決した。
Behavior Designer使って敵キャラの思考を細かく設定できるようにした。めっちゃ便利だけど、BehaviorTree慣れるまで少し時間かかりそう #出稼ぎダンジョン pic.twitter.com/dq7SshrKoK
— いも@EFB~相手は死ぬ~ (@adarapata) 2017年10月29日
上記のTreeは以下の思考パターンになってる
- HPが30%より大きかったら、「たいあたり」「れんぞくたいあたり」のどちらかを繰り出す
- HPが30%以下だったら、「すごいいちげき」「あばれる」のどちらかを繰り出す
いわゆる、ピンチになると発狂する挙動だけど、これくらいなら数分で書けるので非常に便利。そして楽しい
自前の戦闘システムに組み込むため、攻撃を行うActionとHPをチェックするConditionalは自分で実装した。 この辺りは仕組みが簡単なので割と量産しやすそう。
今のところ敵キャラだけに実装してるけど、味方もBehavior Treeで動けるような仕組みにはするので、HPが低かったら回復とかいい感じに思考してくれるやつをこれから作る予定。
裏側はだいぶよくなったので、そろそろ見えるとこも直していこう・・。