Updated at: 2016-02-25
tart framework
krewFramework の反省を活かした新しい俺俺フレームワークの設計について考える際のメモ
スコープの管理
- Entity と Resource の生存管理として、Scene スコープと Scene を子に持つ Chapter スコープを用意
- Chapter は Chapter か Scene を複数持つ(Scene が Leaf の Composite)
// イメージ
"Global Chapter"
- Title Scene
- "NonBattle Chapter" // 例えばグローバルメニューの Entity スコープ
- "Homebase Chapter"
- Room Scene
- Shop Scene
- "Field Chapter"
- WorldList Scene
- LevelMap Scene
- "Battle Chapter"
- Battle Scene
// - Intro State
// - Main State
// - Result State
- 何も指定しなければ、デフォルトでは Global Chapter が 1 個あり、 そこに全ての Scene が足されるようにする
- 関連する Scene をまとめたスコープを作りたくなったときに 後付けで Chapter を定義できるようにする(キャッシュ目的を想定)
Chapter と Scene はどう違うのか?
- 実質やることは似てるが、大域スコープを分かりやすくするためにわざわざ Chapter という別名をつける
- Scene が子 Scene を持つ形にはしない
- Chapter には遷移できない
- 遷移の概念は Scene to Scene のみ
どこからでも呼び出したい系 Scene をどうするか
- 例えばあらゆる画面から開き得るゲームオプション画面など
- レアケースだけど Field から Shop 行って戻ってきたいんだよね、とか
- でもレアケースだから最初から Shop のリソース読み込んでおきたくはない、みたいな
- (基本的に上記のようなツリー構造は横断的関心事が出てくると破綻する)
Scene の履歴管理をどうするか
- 例えばキャラクター編成画面
- 割と Scene っぽい大掛かりなものだが、複数から呼び出される系
- ホームメニュー → 編成 → Back でホーム
- 出撃前確認 → 編成 → Back で出撃前
- 2 段階以上階層をもぐる、なんてこともありえる
- 解決策は基本、Scene を stack させるか、階層型の SceneState を持たせるかのどちらか
- 複数 Chapter に同じ Scene を置くのは Scene 遷移時に どちらの Chapter を処理したらいいか分からないのでダメ
- 割と Scene っぽい大掛かりなものだが、複数から呼び出される系
結局
- Scene の push はやらない(アクティブな Scene は常に 1 つ)
- Scene の履歴管理もフレームワークレイヤーでは用意しない(
backScene()
とかやらない)- 背景を保持しておきたいもの、push/pop の概念が必要なものは全て Modal エンティティとして扱い、 Scene に置いた ModalStack エンティティに制御してもらう
- Scene 内の状態遷移は HFSM エンティティが頑張る
スコープへの読み込みのパターン
- Resource
- Chapter に入る際、Chapter スコープに読み込む(Chapter を出るときに自動解放)
- Scene に入る際、Scene スコープに読み込む(Scene を出るときに自動解放)
- Entity
- Chapter に入る際、Chapter スコープに読み込む(Chapter を出るときに自動破棄)
- Scene に入る際、Scene スコープに読み込む(Scene を出るときに自動破棄)
- SceneState のスコープは作らない
- State からは Scene に投げ込む
- 解放処理は State 抜けるときハンドラに自分で書く
- (フェードしてから破棄とかやるだろうし、即座に破棄されても困るだろう)
- ※ 総じて、Scene から上位の Chapter スコープに投げ込むようなことは、ややこしくなるのでやらない
- ある Scene のタイミングで Resource を読むが、それは複数 Scene でキャッシュしたい、みたいな場合は?
- Chapter に「自分の Chapter 内の特定の Scene に初めて入ったハンドラ」を書けるようにしておけばよい
- 何にしてもレアケースだろう
- ある State に初めて遷移するタイミングでリソース読み込めばいいけど、
一度開いたら Chapter 抜けるまではキャッシュしておいてほしい、みたいな場合は?
- Scene スコープでお茶を濁せないものか…
- ある Scene のタイミングで Resource を読むが、それは複数 Scene でキャッシュしたい、みたいな場合は?
Scene 遷移時の処理の流れ
Entity に attach されたロジックから、遷移のリクエストを送る
- 然るべきフェーズで tart がハンドリング
Exit 処理
- Scene の解放処理
- Scene :: onExitAsync (もし非同期の解放処理を挟みたい場合)
- Scene :: onExit (同期でよい解放処理)
- Scene :: _disposeResource (tart が呼ぶ)
- 〜 もしも今いた Chapter を出る場合 〜
- Chapter の解放処理
- Chapter :: onExitAsync
- Chapter :: onExit
- Chapter :: _disposeResource
- Chapter の解放処理
Enter 処理
- 〜 もしも新しい Chapter に入る場合 〜
- 遷移先 Chapter のロード処理
- Chapter :: beforeLoadResource
- Chapter :: _loadResourceAsync
- Chapter :: onEnterAsync
- Chapter :: onEnter
- 遷移先 Chapter のロード処理
- 遷移先 Scene のロード処理
- Scene :: beforeLoadResource
- Scene :: _loadResourceAsync
- Scene :: onEnterAsync
- Scene :: onEnter
ネーミング案
【Enter 処理】
- awake
- (_loadResourceAsync)
- initAsync
- init
【Exit 処理】
- disposeAsync
- dispose
- (_disposeResource)
- disposeAsync は不要な気がする
Scene クラス
要件の整理
- Scene には基本、必要なものを宣言的に書くだけにしたい
- Scene スコープに読み込みたいリソース一覧や、初めに置く Entity 一覧など
- だが実際のところ、現実的な規模のゲームを作ろうとすると
Scene の冒頭にちゃんとした初期化フェーズが必要だったりする
- 最初の Entity と言われても管理者 Entity くらいしか置けない
- 何故なら Entity の初期化のためにレベルデータの読み込みだったり、 処理の順序立てが必要になるから
- そのへんを onEnterAsync の時点で済ませるか、SceneState でやるのか?
- まあ FSM な Entity を最初に置くのが一番構造的にシンプルで汎用性高いか
- Scene に次の Scene を書くべきか? 遷移の関心事からは独立させるべきか?
- 履歴管理をやりたいかどうかにもよる
- 例えば OptionScene はどこから呼ばれたかは気にせず、仕事が終わったら前の Scene に戻るとか
- いや汎用のポップアップ的な概念は State でやった方が扱いやすいという話もある
こんな風に書きたい
public class LevelScene extends TartScene {
private var _levelId:int;
// コンストラクタは引数とれるようにしないと不便
public function LevelScene(levelId:int) {
_levelId = levelId;
}
// Scene スコープに読み込みたいアセットのファイル名一覧
public function assets():Array {
return [
"image/atlas_" + _levelId + ".jpg"
,"image/atlas_" + _levelId + ".xml"
,"bmp_font/fonts.png"
,"bmp_font/fonts.xml"
,"sound/se_01.mp3"
,"level_data/level_" + _levelId + ".json"
];
}
// アセットのロード開始前に呼ばれるハンドラ
public function awake():void {
spawn(new LoadingIndicator());
}
// Scene の最初の時点に必要な Entity 一覧
public function initialActors():Array {
return [
new Player()
,new Ground()
,new HeadUpDisplay()
,new EnemyGenerator()
,new LevelMapFSM()
];
}
}
Chapter クラス
- 複数の Scene をまとめた大域スコープを作るためのもの
こんな風に書きたい
public class GlobalChapter extends TartChapter {
public function GlobalChapter() {
addScenes(
OptionScene, ...
);
addChapters(
new HomebaseChapter,
new FieldChapter,
new BattleChapter,
...
);
}
public function assets():Array {
return [ ... ];
}
public function initialActors():Array { ... },
public function onEnter():void { ... },
public function onExit():void { ... },
public function onEnterSceneFirst(scene:TartScene):void { ... }
}
SceneState クラス
- 入り組んだ State の遷移を宣言的に書くためのもの
- 定義に書かれている以外の遷移はしないことを保証する
- (外側から自由に
gotoState("scene_id")
みたいなことはさせない)
- (外側から自由に
こんな風に書きたい
public class LevelMapFSM extends TartFSM {
public function LevelMapFSM() {
// いつかツールを作ったらそれでこの中身を吐けるようなイメージで
// ステート定義(階層型)の json を書く
super({
{
id : "init_level_phase",
enter : _initLevel,
trigger : ["load_complete_event", "next_state_id"],
children: [
{
...
},
...
]
},
new PlayPhase(), // クラスにまとめることもできるように
new ResultPhase(),
...
});
}
}