#はじめに
2015年にあるボードゲームに出会い、「これをなんとかデジタル化したい!」と思って約2年。
そしてphina.jsに出会い、開発してきた中で最も苦戦したScene制御、またManagerSceneに関して思うところを書きます。
ちょうど前日がローディングシーンのお話なので、流れ的にも悪くないかと。
諸先輩方を差し置いて、しかも勝手にいろいろ書いてるので…誤っていたらごめんなさい。
phina.jsバージョン: v0.2.1 ←あんまり関係ないかも
#何がしたかったの?
ボードゲームなので、ざっくりいえば以下の状態があります。
1.メインの「ボード」
2.ダイスの出た目で進むマス「スクエア」で発生するイベント
3.そのイベントで「カード」を引いたらそのカードのイベント
しかもカードは1枚だけではなかったりするわけで
(テキストベースですが)ざっくりとした状態遷移を描くと以下のようなことをしたい。
1.→2A-start.→3.(→3.…)→2A-end.→1.→2B-start.→3.(→3.…)→2B-end.→1.→…
ポイントは以下のとおり。
・必ず「1.ボード」からスタートし「1.ボード」に戻る。
・ボードで選ぶ「2.スクエア」はどうなるかわからない。
・「3.カード」を選ぶイベントが発生するかどうかは「2.スクエア」次第。(カード次第ではカードをもう一度引くこともあるが、ここでは割愛。)
ここで思うわけですよ。
たぶんこういうのをManagerSceneを利用しながらScene管理するんだろうな、と。
↓こういうのをみればそう思いたくなります。
http://github.dev7.jp/b/2015/12/01/phinaadvcal20151202/
かなり助かりましたが、上記の例はあくまでシューティングゲームなわけです。
具体的には上記の例は分岐はすれどあらかじめ予測された道にもどってエンドなんです。
ボードゲームって上記に示した1.~3.を行ったり来たりして、結果どうなるかなんて定義できないわけで、このあたりがかなり混乱しました。
#それが何か?
だったらManagerSceneなどにこだわらず、各々シーンを作ってpushScene,popSceneすればいいのはないか?と思われますが、そうもいかず。
というも、「2.スクエア」から「3.カード」に移るときにpushSceneしたとして、そのカードで別のカードをひくことになると、さらにpushSceneできるかというとそうはならず。
理由は、戻るときにpopSceneしたときに戻るのは1つ前しかなくて、また1つ前のカードを引くシーンに戻されてしまうため。
じゃあreplaceSceneすればいいじゃんと思いましたが、replaceSceneしちゃうと再度戻る場合もreplaceSceneするしかなく、そうすると毎度init処理が発生してしまう。(まぁ引数で渡しまくればそれもいいのかもしれませんけどね…)
#どうしたか
まず、「メイン」と「スクエア」を分け、この2つの行き来はreplaceSceneとしました。
ひとしきり「スクエア」のイベントが終われば、「メイン」でほしい情報はカードの枚数やスクエアの位置などゲームを通じて管理すべき内容のみとなり、それらはグローバル定義することにしました。
「メイン」から「スクエア」に移動するときにreplaceSceneするのですが、その際にSceneではなく、ManagerSceneを使うことにしました。
そして、「スクエア」から「カード」のシーンはpush,popの関係を使いました。
e.stopSquere.mark = 'Angel';
(略)
this.on('stopSquere', function(e) {
app.replaceScene(eval('Squere' + e.stopSquere.mark + 'SceneSequence()'));
});
(略)
phina.define('SquereAngelSceneSequence' , {
superClass: 'ManagerScene' ,
SquereMark : 'Angel', //ここに定義することもできる。
init: function() {
this.superInit({
scenes: [
{
label: 'Start',
className: 'SquereAngelScene',
nextLabel: 'End',
arguments: {selectNumberOfCards : 1,},
},
{
label: 'SelectDeck',
className: 'SelectDeckScene',
nextLabel: '',
},
],
});
//Startシーンでユーザに説明し「次へ」を押下した後、SelectDeckシーンへ
//SelectDeckシーンにて「カード」のシーンをpushし、pop時にexitすれば、↓のfinishが発火
this.on('finish',function() {
this.app.replaceScene(MainBoardScene());
});
},
});
#ManagerSceneについて思うところ
上記「どうしたか」を煮詰めていくと私が思うところは結局下記の話となります。
A→B→C→…と進むシーン遷移(個人的にはこれを**「直列のシーン遷移」と呼びます)にManagerSceneは有効と思います。このほうがシーンがどういう風に進むのか開発者も見通しやすいはずです。
でも、AからBやCに進むシーン遷移(同「並列のシーン遷移」)を定義するのにManagerSceneによる定義では対応できず、そういう場合はシーンをreplaceするほうが有効**だと思います。
理由は下記のとおりです。
「並列のシーン遷移」の場合、どこかにif文なりcase文を書くわけで、ManagerSceneに定義してexit('nextlabel')するよりわかりやすいのではないかと思います。
また、exitのラベル指定範囲はManagerSceneにて定義した中なので、多数のシーンがあって、それらにいろいろなところから遷移可能とするとなると1つのManagerSceneに全部ラベル定義する必要があり、ManagerSceneの見通しが付きにくくなるという難点もあります。
例を挙げます。まことに申し訳ないのですが…「先に参考にさせていただいた例」を引き合いに出します。
「先に参考にさせていただいた例」だと「ゲーム全体の流れ」のところもManagerSceneを使っていて、全部exitしているようにみえますが、まさに「並列のシーン遷移」なので、これを「私が思うところ」のルールに合わせると下記のようになります。
/* メインシーケンス …はシンプルに*/
phina.define("ps.MainSequence", {
superClass: "phina.game.ManagerScene",
init: function() {
this.superInit({
scenes: [
{
label: "load",
className: "ps.LoadingScene",
arguments: { stageId:0 },
},
{
label: "title",
className: "ps.TitleScene",
},
],
});
this.on('finish',function() {
//ぐるぐるループ
this.app.replaceScene(ps.TitleScene());
});
}
});
phina.define("ps.TitleScene", {
//選択したシーンに「置き換える」
(略)
case 'Arcade':
app.replaceScene(ps.ArcadeMode1Sequence());
break;
(略)
}
//アーケード1シーンを定義する。
phina.define("ps.ArcadeMode1Sequence", {
superClass: "phina.game.ManagerScene",
init: function() {
this.superInit({
scenes: [
{
label: "Arcade1-1",
className: "ps.ArcadeDokan",
arguments: { stageId:1 },
nextLabel: "Arcade1-2",
},
{
label: "Arcade1-2",
arguments: { stageId:1 },
className: "ps.ArcadeUmi",
},
],
});
this.on('finish',function() {
//通常の流れなら次のレベルへ
this.app.replaceScene(ps.ArcadeMode2Sequence());
});
}
}
ps.ArcadeMode2Sequenceは省略
//アーケード3シーンを定義する。
phina.define("ps.ArcadeMode3Sequence", {
superClass: "phina.game.ManagerScene",
init: function() {
this.superInit({
scenes: [
{
label: "Arcade3-1",
className: "ps.ArcadeDokan",
arguments: { stageId:3 },
nextLabel: "Arcade3-2",
},
{
label: "Arcade3-2",
arguments: { stageId:3 },
className: "ps.ArcadeUmi",
},
],
});
}
}
こうしておくと、ゲーム展開次第でScene1の途中から一気にScene3とするようなスーパーマリオ的な展開が可能となる(Scene1のどこかでreplaceScene(ps.ArcadeMode3Sequence())をすればいい)ので、自由度が広がりますし、なにより開発者としてどこまでがワンセットなのかが分かりやすいと感じています。
#最後に
Qiitaに投稿するのは初めてですし、もしかすると「当たり前」なのかもしれませんが、Scene遷移の方法にかなり苦労したので書いてみました。
ボードゲームのデザインパターンみたいのがあれば教えてください。