前回の続きであり、最後です。
これまで4回の中で、今回がダントツで難しいのではないかと思います。新たな要素がどんどん出てきます。前回までの内容がバッチリだという前提で書き進めますので、もしわかりにくい箇所などあれば遠慮なく @miz_vhb にリプライやマシュマロなどを送ってください。
⇩前回
⇩初回
また、別記事で『VSCodeと自動整形』に触れています。今回の記事は、自動整形でjsファイルを整頓することができるという前提で書いているので、自動整形を導入していない方は、自動整形を導入してから今回の内容に挑んでください。
今回は、本番も本番。実際にシナリオで使えるようにします。
完成図はこちら。
シナリオ中にメニューバーを表示
まずは、前回までタイトル画面で動作確認をしていたので、今度はシナリオ冒頭でメニューバーを表示するようにします。
一旦、タイトル画面に書いたコードをそのままシナリオ冒頭に移しましょう。
まず、タイトル画面は最初のコードの状態に戻します。以下のようになります。
data/scenario/title.ks
[cm]
@clearstack
@bg storage ="title.jpg" time=100
@wait time = 200
*start
[button x=135 y=230 graphic="title/button_start.png" enterimg="title/button_start2.png" target="gamestart"]
[button x=135 y=320 graphic="title/button_load.png" enterimg="title/button_load2.png" role="load" ]
[button x=135 y=410 graphic="title/button_cg.png" enterimg="title/button_cg2.png" storage="cg.ks" ]
[button x=135 y=500 graphic="title/button_replay.png" enterimg="title/button_replay2.png" storage="replay.ks" ]
[button x=135 y=590 graphic="title/button_config.png" enterimg="title/button_config2.png" role="sleepgame" storage="config.ks" ]
[s]
*gamestart
;一番最初のシナリオファイルへジャンプする
@jump storage="scene1.ks"
そして、前回書いていたコードをシナリオファイル data/scenario/scene1.ks に移します。
せっかくなので、「;メニューボタンの表示 @showmenubutton」の下あたりに入れておきましょう。わかりやすいですし。(実際は、冒頭ならどこでも大丈夫です。)
data/scenario/scene1.ks
;メニューボタンの表示
@showmenubutton
; メニューバー
[image name="menu_bar" layer="0" storage="menu_bar.png" width=200 height=auto x=1080 y=-370 visible=true]
[button name="menu_bar" target="*click" layer="0" graphic="menu_bar_btn.png" width=200 height=auto x=1080 y=10 visible=true exp="document.querySelectorAll('.menu_bar').forEach(function(element) {element.classList.add('open')})"]
[button name="menu_bar" layer="0" graphic="menu_bar_save.png" width=170 height=auto role="save" x=1095 y=-265]
[button name="menu_bar" layer="0" graphic="menu_bar_load.png" width=170 height=auto role="load" x=1095 y=-200]
[button name="menu_bar" layer="0" graphic="menu_bar_log.png" width=170 height=auto role="backlog" x=1095 y=-135]
[button name="menu_bar" layer="0" graphic="menu_bar_config.png" width=170 height=auto role="sleepgame" storage="config.ks" x=1095 y=-70]
*click
この状態で、ゲームをプレビューしてみてください。「はじめから」プレイすると、閉じている状態のメニューバーが表示されます。
……が、メニューバーを開くことができません。どうしてでしょうか?
なぜかというと、fixが有効になっていない( = fixボタンではない)からです。
fixとは?
固定ボタン(セーブボタンなどの常に表示しておくボタン)にするかどうか。trueまたはfalseで指定します。通常の選択肢ボタンはfalse(デフォルト)。選択肢ボタンとは異なり、固定ボタンはそれが表示されている間も画面をクリックしてふつうにシナリオを読み進めることができます。trueを指定すると、fixレイヤという特殊なレイヤにボタンが配置されます。fixレイヤに追加した要素を消す場合は[clearfix]タグを使います。fixにtrueを指定した場合は別のstorageのtargetを指定して、そこにボタンが押されたときの処理を記述する必要があります。fixにtrueを指定した場合、コールスタックが残ります。コールスタックが消化されるまではボタンが有効にならないのでご注意ください。
……ざっくり要約すると、「fixが有効になっていない」とは、「ゲームで固定表示される状態になっていない」という意味です。
ティラノスクリプトでは、SAVEやLOGなどのボタンが自動でfixボタンとして使うことができるようになっています。
よって、今回はメニューバーを開閉するボタンも、SAVEやLOADと同じように「fixボタン」にしてしまいましょう。
※ これからの作業は、ほんのうっかりのミスでゲームが動かなくなるほどのバグを生み出す可能性があります。慣れないうちは、ゲームプロジェクトをコピペするなどしてバックアップをとり、変なコードを書いてしまってもすぐに復元できるようにしておくことをオススメします。
roleって何?
ボタンをfixボタンにしたい場合、以下の方法があります。
roleで役割を与える(例: SAVE、LOAD、LOGなど)
roleはsleepgameとしつつ、別ファイルを読み込む(例: CONFIGなど)
今回は、上記のうち、「roleで役割を与える」という方法でやっていきましょう。こっちの方が、不具合を起こしにくいからです。
その前に、「roleで役割を与える」というのはなんぞや、という部分を説明しておきます。
実際のroleつきボタンを見てみましょう。data/scenario/title.ks を開いて、以下のコードを見つけてください。
data/scenario/title.ks
[button x=135 y=320 graphic="title/button_load.png" enterimg="title/button_load2.png" role="load" ]
これは、タイトル画面のロードボタンです。一番最後に、「role=”load”」と書いてありますね。
「role=”load”」は、「このボタンには”load”の役割を持ってもらいますよ」という意味です。
このボタンタグ、よく見ると「storage」や「target」のような、シナリオのジャンプ先に関するアレコレが書かれていません。それなのになぜ「LOAD」画面を開くのかというと、このボタンは「”load”の役割」を持っており、クリックすると「”load”の役割」にしたがって「LOAD」画面を開くからです。
roleには、”save”や"backlog"なども存在します。試しに、先ほどのrole="load"をrole="backlog”に書き換えて、ゲームをプレビューしてみてください。タイトル画面で「つづきから」をクリックすると、バックログが開かれると思います。「”backlog”の役割」を果たしているんですね。
そんな感じで、ティラノスクリプトには「role」という機能があります。
ティラノスクリプトでは、標準で「save」「load」「backlog」などのroleがありますが、なんと、roleは自分で作ることもできるんです。
よって、今回は「メニューバーを開閉できる」というroleを自分で作ってしまいましょう!
前準備
まず、CSSを書き換えます。
前回まで、メニューバーをopen状態にするcssだけを書いていましたが、close状態にする場合のCSSを書きましょう。また、ちょっとわかりやすいコードに変えましょう。
前回書いた @keyframes menuOpen などを消して、以下のコードに書き換えます。
tyrano/css/animate.css
.menu_bar.open {
transform: translateY(300px);
transition: transform 0.5s;
}
.menu_bar.close {
transform: translateY(0);
transition: transform 0.5s;
}
これは、menu_bar かつ open であれば、0.5秒かけて300px下に移動、menu_bar かつ close であれば、0.5秒かけて0の位置に戻る、という意味です。
なんとな〜く雰囲気がわかればOKです。
新たな関数を定義
ここで、ちょっとややこしくなります。JavaScriptの本格的な部分に入っていきます。
tyrano/plugins/kag/kag.menu.js を開いてください。
ここに、ティラノスクリプトのゲームの肝となることがたくさん書かれています。……中身を読んでも、何が何だかわからないですよね。ぶっちゃけ、私も腰を据えて読まないと何を書いているのかわかりません。
試しに、「displayLog:」という文字を見つけてください。
tyrano/plugins/kag/kag.menu.js
displayLog: function () {
var that = this;
this.kag.stat.is_skip = !1;
$("<div></div>");
...以下略
ここには、backlogを表示するためのアレコレが書かれています。……多分。何が書かれているかまでは理解しなくてOKです。とにかく、こうやって「その気になればここに機能に関することを書けるんだな〜」ということがわかればOKです。
JavaScriptには、「関数」という考え方があります。
「関数」を厳密に説明しようとすると難しいのですが……、今回は「色々なコードをまるまるパックに詰めたもの」くらいの認識で大丈夫です。高校で数学を学んでいる方は、関数といえば「f(x)」というものをイメージすると思いますが、プログラミングの関数もそれと概ね一緒です。
ここで、「メニューバーを開閉する関数」というものを作っておくことにします。
というわけで、私たちもこのファイルに関数を書き足しましょう。
tyrano/plugins/kag/kag.menu.jsファイルの、一番下を見てください。以下のようなコードが見つかると思います。
tyrano/plugins/kag/kag.menu.js
test: function () {},
};
これは空っぽの関数ですね。開発者の方が、ここに色々書き足していいですよ〜って意味で書いてくれているのかな。……わからないけど。
というわけで、この2行を削除し、以下のコードに書き換えます。
このファイルは大事なコードがたくさん書かれています。ちょっと緊張しますが、慎重にやってくださいね。
tyrano/plugins/kag/kag.menu.js
test: function () {},
toggleMenuBar: function () {
document.querySelectorAll(".menu_bar").forEach(function (element) {
if (element.classList.contains("open")) {
element.classList.remove("open");
element.classList.add("close");
} else {
element.classList.remove("close");
element.classList.add("open");
}
});
},
};
/* これより下にコードがなければOK */
前回まで、メニューバーのボタンに「exp="document.querySelectorAll(〜」みたいなことを書いたと思います。今回は、あの時書いたものをここに書き写しています。順番に何を書いたか見ていきましょう。
toggleMenuBar: function () {
これは、「toggleMenuBar」という名前の関数を書き始めますよ〜という意味です。関数は、{}; の中に書きます。
一番最後の行の「};」のところまでは、「toggleMenuBar」という名前の関数の中身です。名前は必ずしも「toggleMenuBar」である必要はなく「gosenchoenhosi」とかでもいいのですが、わかりやすくするために今回は「toggleMenuBar」という名前にしておきましょう。ちなみに「toggle」には「切り替える」のような意味があります。openだったものをcloseに、closeだったものをopenにするので、「toggleMenuBar」という名前にしました。
関数の中身を見てみましょう。
document.querySelectorAll(".menu_bar").forEach(function (element) {
if (element.classList.contains("open")) {
element.classList.remove("open");
element.classList.add("close");
} else {
element.classList.remove("close");
element.classList.add("open");
}
});
これを隅々まで解説すると長くなるので、今回はざっくり解説にします。詳しい解説を求められる方はChatGPTなどに聞いてみてください。これくらいの関数なら、正確に解説してくれると思います。
要約すると以下のような意味になります。
「menu_bar」というクラスのもの全部に対して、
「open」であるなら「open」を削除し、「close」を追加
「close」であるなら「close」を削除し、「open」を追加
menu_barがopenならcloseに、closeならopenに、openならcloseに……。シンプルな機能ですね。
これで、ティラノスクリプトに「toggleMenuBar」という関数を定義することができました。
roleの設定
今度は、roleを作ります。
今はまだ、「メニューバーを開けたり閉じたりするtoggleMenuBarという関数」が生まれただけで、これを誰が担当するかは決まっていません。この点を決めていきましょう。
tyrano/plugins/kag/kag.tag.js を開いてみてください。色々書いていますが、以下のコードが見つけられるでしょうか。
tyrano/plugins/kag/kag.tag.js
case "backlog":
that.kag.menu.displayLog();
break;
「displayLog」という文字、見覚えがありますね。
これは、要約すると「もしroleが backlog であれば、displayLogを実行します」という意味になります。
これのおかげで、「role=”backlog”」のボタンは、displayLogを行う = バックログを表示する ということができるようになります。
これと同じように、「もしroleが toggke_menu_bar であれば、toggleMenuBar を実行します」というコードを書いてしまいましょう。
このファイルのどこかに、case "auto":とか、case "sleepgame":とか書いてあるところがあるはずです。
tyrano/plugins/kag/kag.tag.js
case "auto":
1 == that.kag.stat.is_auto
? that.kag.ftag.startTag("autostop", { next: "false" })
: that.kag.ftag.startTag("autostart", {});
break;
case "sleepgame":
j_button.trigger("mouseout");
if (null != that.kag.tmp.sleep_game) return !1;
that.kag.tmp.sleep_game = {};
_pm.next = !1;
that.kag.ftag.startTag("sleepgame", _pm);
}
これの、真ん中の方の「break;」と「case "sleepgame":」の”間”に、コードを追加してください。
以下のようになります。
tyrano/plugins/kag/kag.tag.js
case "auto":
1 == that.kag.stat.is_auto
? that.kag.ftag.startTag("autostop", { next: "false" })
: that.kag.ftag.startTag("autostart", {});
break;
// 以下を追加!
case "toggle_menu_bar":
that.kag.menu.toggleMenuBar();
break;
// ここまで追加!
case "sleepgame":
j_button.trigger("mouseout");
if (null != that.kag.tmp.sleep_game) return !1;
that.kag.tmp.sleep_game = {};
_pm.next = !1;
that.kag.ftag.startTag("sleepgame", _pm);
}
コードを3行追加しました。書いていることは非常にシンプルです。
case "toggle_menu_bar":
role が "toggle_menu_bar" の時、という意味です。
that.kag.menu.toggleMenuBar();
toggleMenuBar(さっき書いたやつ)を実行します。
break;
終わり!の合図。
ここまできたら、もう完成したも同然です!
role付きのfixボタン
あとは、メニュー開閉ボタンを書き換えるだけでOKです。
data/scenario/scene1.ks
;メニューボタンの表示
@showmenubutton
; メニューバー
[image name="menu_bar" layer="0" storage="menu_bar.png" width=200 height=auto x=1080 y=-370 visible=true]
[button name="menu_bar" layer="0" graphic="menu_bar_btn.png" width=200 height=auto role="toggle_menu_bar" x=1080 y=10]
[button name="menu_bar" layer="0" graphic="menu_bar_save.png" width=170 height=auto role="save" x=1095 y=-265]
[button name="menu_bar" layer="0" graphic="menu_bar_load.png" width=170 height=auto role="load" x=1095 y=-200]
[button name="menu_bar" layer="0" graphic="menu_bar_log.png" width=170 height=auto role="backlog" x=1095 y=-135]
[button name="menu_bar" layer="0" graphic="menu_bar_config.png" width=170 height=auto role="sleepgame" storage="config.ks" x=1095 y=-70]
メニューバーのボタンを、以下のように書き換えました。
[button name="menu_bar" layer="0" graphic="menu_bar_btn.png" width=200 height=auto role="toggle_menu_bar" x=1080 y=10]
着目すべきは、やっぱり「role="toggle_menu_bar"」の部分。これで、"toggle_menu_bar"という役割を持つfixボタンになりました。
これで、ゲームをプレビューしてみてください。
\( ˙ᗜ˙)ノ!
ゲームが進行しても、メニューバーの開閉ができるようになりました。
これでメニューバーの実装ができました。お疲れ様でした!
もっと品質を高めるために
これで終えてもいいかもしれませんが……、動作確認を繰り返すうちに、ちょっと気になるところが増えてくるかもしれません。
例えば、『メッセージウィンドウの非表示を行った時(スペースキークリックでできます)、メニューバーの背景だけ残ってしまう』とか。
こういう気になる部分を少しずつ解決していって、より自分の満足がいくクオリティを目指していきましょう。先ほど説明した「tyrano/plugins/kag/kag.tag.js」など、きっとどこかにヒントがあるはず。
『メッセージウィンドウの非表示を行った時、メニューバーの背景だけ残ってしまう』という問題を解決したかったら、どうするでしょうか。私なら、以下のように問題を深掘りしていきます。
メッセージウィンドウの非表示はどのようにして行われているのか(コードはどこに書いている?)
メニューバーの背景を消す方法はある?(JavaScriptやCSSなどで消せる?)
メッセージウィンドウの非表示に、同時にメニューバーの背景を消すようにできる?(メッセージウィンドウの非表示のコードに、メニューバーの背景を消すコードを追加できる?)
今回、このやり方は解説しませんが、解決したコードをGitHub上に公開しておきます。参考にしてみてください。
最後に
元々、この技術記事は、記事というよりは日記のように毎日アウトプットすることを目標に書いていました。短時間でパッと書く、という趣旨で進めているので、わかりにくいところや乱雑なところも多かったと思います。
それでも、たくさんの方に読んでいただけたようで、とても嬉しかったです。
反響の大きかった記事は、清書してブログに投稿しようと思います。
もしよろしければ、「この記事お気に入りです!」などあればマシュマロや感想レターなどいただけると幸いです。
ここまで読んでいただき、ありがとうございました。