赤宿 = Red Inn

素人の試行錯誤と2Dゲームプログラミング

Processing (old)

 Action(行動オブジェクト)の演出をどう実装するか。必要条件にPDS(=Model/Viewの分離)があるものとする。
 HUDやログは、下の2つ目の方法を取ればObserverパタンで問題が無い見通しだ。継続して保留する。

廃案: イベントの観測 - Using An Event Queue

方法 - The Way

 ゲーム内のすべての出来事をイベントとし、イベントのキューをUIに返す。UIはイベントを可視化/演出する。
 e.g. ActionBegins, Hit, Attacked, Walked, etc.

問題 - Problems

1.ModelとViewが同期(?)していないこと。

 たとえば演出の最中にもマウス上のキャラの体力を表示する場合、UIから参照するModelの情報が未来の情報であってはならない。よって、ゲームModelは『一瞬』だけ進んでからUIに制御を返す必要がある。一気にゲームを進めてからイベントキューを返すなら、この条件を達成できない。

2.Eventのクラスを作る手間が大きい

 ちなみに:

UI側に返されたイベントオブジェクトを扱う関数、を派生イベントクラスごとに用意するならば、ダブルディスパッチの必要がある。僕は、visualize( someEvent as dynamic )という形で、ダックタイピングでダウンキャストしようとした。
 Visitorパタンで対応する方法もあるが、操作を足される元クラスがダブルディスパッチのことを『知る』必要があるため、いただけないと思う。一方Visitorパタンには、扱う派生型をこぼすことが無い、というささやかな利点はある。dynamicでダブルディスパッチをする場合は、ダックタイピングでダブルディスパッチ元の関数を呼び出してしまう可能性などに注意。

検討中: ActionをMotionsに分けて、各Motionを繋げる

方法 - The Way

 ゲーム内の出来事、すなわちActionを瞬間単位のコードに分割しておき、viewで派生して演出を足し、Actionとしてmodelのゲームループに渡す。その方法をより詳しく説明すると…

1.ActionをMotionsに分解する - Every Action Consists of Motions

 先の <問題1.ModelとViewの分離> の通り、ゲームModelは『一瞬』だけ進んでから止まる必要がある。そこで、まずActionを挙動(Motion)のサブルーチンに分けてみる。
 e.g. Throw, FallToFloor, HitActor, etc…

2.Motionを繋ぐ - Chain of Motions

 攻撃が命中したかどうかなどで、続くMotionは動的に決まる。Motionは続く次のMotionを何らかの方法で指定する。次回のgame.tick()時にはそのMotionが実行される。
 記法に不満はあるが、ラムダ式に頼れば動かすことはできる。

// chain next motion
return base.chain( () => someMotionMethod( andTheArgs ) );

ローグライクのゲームループだけはIEunmerable(C#)なため、ゲームループ側は実に簡単なコードのままで済む。

3.Motionの演出 - Creating Effects of Motions

 Viewから各Actionを派生する。overrideしたMotionメソッドから演出用のオブジェクトを作り、実行し、再びゲームModelの時間を進める(model::Game.tick())。
 ActionはViewから渡されたActionFactoryを通して生成する。Model側のゲームループが、View側のActionを渡されて処理をすることになる。シンプル。

問題 - Problems

手間 - The Requirements

 ActionFactoryからActionを作ることと、ActionFactoryの実装。その手間を妥協できるか。

仕様 - The Complexity

 Motionをbase.chain()するというViewのための仕様がActionに加わってしまう。できることなら、Motionsのつなぎ方はMotionから分離してしまいたい。たぶん無理だろう。
 また戻り値をゲームループが利用している場合、view側ではいったんbase.thisMotionFunction()の戻り値を保存する必要がある。

その他 - What’s Else?

 これから試していくところ。5月はターン制ゲームの実装を予定していたが、このアイディアを練るのに時間がかかり過ぎた。
 妥協した実装をする過程で考えたことが、これらのアイディアに繋がった覚えがある。今度の案はうまい仕組みとなるだろうか。

後日談 - Action Sandbox Extention

 model::actions::XxxをViewから継承するとき、多重継承できないことに気付いた。oh nooohhhhhh
 model::actions::Baseは、サンドボックスパタン的に派生クラスに機能提供を行っている。view用のサンドボックスはいかに用意するか。

砂場の機能をoverrideするには - Overriding Sandbox Functions

 たとえばlog(string)はmodel::Log.write()に委譲して、viewからLogを派生してmodelへ渡すことでログの書き込みに演出を足す。
 この例ならobserverパタンでいいとは思う。一応この手もあるということで。

砂場に機能を足すなら - Adding Sandbox Methods

 拡張メソッドでmodel::actions::Baseへのviewからの機能追加ができる。グローバルなオブジェクトから目当てのオブジェクトを辿れるならば、拡張メソッドでも機能的には遜色が無い。綺麗なやり方とは言えたものではないが… これで行こう。