見出し画像

RPGツクールプラグイン制作過程紹介 第15回

大変長らくお待たせしました。前回はスキルリストウィンドウを実際に操作できるようにしたところまででしたね。こんな感じでした。

操作できるようになったのはいいんですが、このウィンドウ…あまりにも邪魔ですよね! そこで第15回となる今回は、このウィンドウを非表示にする処理を組んでいきたいと思います。

ウィンドウの非表示

コアスクリプトにおいて、あるウィンドウを非表示にする処理は大変によく用いられています。Scene_Mapにおいても、メッセージウィンドウや選択肢ウィンドウなどが通常は非表示にされていますよね。ですのでこれらの処理を参考にしてみましょう。

rmmz_windows.js4902行目

これはメッセージウィンドウのクラスであるWindow_MessageterminateMessageというメソッドです。terminate(終わらせる)という名前が示す通り、メッセージの終了時に呼び出される処理です。この中でclose()という関数が呼び出されていますが、これがウィンドウを閉じる関数なのです。この関数を実行するとウィンドウはただ消えるのではなく、徐々に小さくなるアニメーションを伴って閉じられます。こうすることで動きがついて見栄えが良くなるわけですね。実際の動きを見てみましょう。

もちろんこの反対の関数であるopen()は徐々に大きくなるアニメ付きでウィンドウを表示状態にするものです。

つまりclose()を実行すれば現在表示されっぱなしになっているスキルリストウィンドウ等を閉じることができるはずですね。
それならさっそく実装してみましょう…と言いたいところですが、そのためのトリガーをどうするのかを考える必要があります。
「トリガー」とはツクールのイベントでも使われている言葉ですが、処理を発火させるための引き金、つまりそれを満たすことで処理が実行される条件のことです。
ウィンドウを非表示にするといっても、必要なときにまで非表示になってしまっては元も子もないわけです。プレイヤーがこのウィンドウを使いたいと感じたときにだけ表示されるようにしたいですよね。
この場合トリガーとして考えられるのは「プレイヤーがある特定のキー(ボタン)を入力したとき」です。例えばメニュー画面はメニューボタン(escキー右クリックなど)を押すことで開かれますよね。それと同様に、専用のボタンを押したときにウィンドウが開かれるようにすれば使いやすいインターフェースになってくれるのではないでしょうか。

というわけで、これからボタン入力の処理を作っていくことにします。

コアスクリプトのボタン入力処理

ボタン入力処理というといかにも難しそうな印象を受けますが、コアスクリプトではさまざまな場面で使われていますのでそれを参考にすれば大丈夫でしょう。というわけで、まずはツクールの標準機能においてボタン入力を受け取っている処理を探し、それを参考にしてみましょう。

ボタン入力さえ使われていればどこでもいいのですが、上記でも例示したメニュー画面は典型的なボタン入力処理です。なのでこれを参考にしてみることにします。Visual Studio Code(以下VSC)にてmenuで検索してみましょう。

たくさん出てくる!

さすがに検索結果が多いですね…。これのどこを見るべきかというと、まず今何を探しているのかを思い出してみます。マップ上のメニュー画面呼び出し処理を探しているんでしたよね。つまりScene_Mapの関数を探しているわけです。なので注目すべきはrmmz_scenes.jsファイル内のScene_Map関連の定義箇所です。

rmmz_scenes.js以外を閉じて見やすくした

rmmz_scenes.jsファイル内のScene_Mapあたりを探してみると、menuという言葉が含まれる関数が多数出てきます。これを一つひとつ見ていけば目的の処理が見つかりますが、結論から言うとisMenuCalledという関数が入力関連の実際の処理を行なっています。

rmmz_scenes.js961行目

この中にInput.isTriggered("menu")という記述があります。これがコアスクリプトに用意されている、入力を判定するための関数なのです。この関数はボタン名を引数に取り、そのボタンが押されていればtrueを、押されていなければfalseを返します。

Inputというのはキーバード・ゲームパッドの入力に関する処理を行う静的クラスですが、このisTrigeredとよく似た関数が他にも存在します。それらはいずれもボタンが押されているかどうかを判定するという点は同じなのですが、「押され方」を区別するために使い分けられています。以下にその一覧とぞれぞれの違いを簡単にご紹介します。

  • Input.isPressed(keyName): keyNameが現在押されているかどうかを判定する、最も基本的な関数です。

  • Input.isTriggered(keyName): keyNameが押された瞬間のみ、条件を満たしたと判定する関数です。

  • Input.isRepeated(keyName): keyNameが継続的に押されているかどうかを判定する関数です。isPressedと違い、ボタンが押しっぱなしになっていたとしても途切れ途切れでtrueを返します。

  • Input.isLongPressed(keyName)keyNameが長押しされているかどうかを判定する関数です。具体的には、24フレーム以上押されていれば長押しとみなされます。

なおこれらに関するより詳細な解説は、英語ではありますが公式のAPIドキュメントでも確認できます。

ツクールに詳しい方は、上の3つを見て何かを連想するかもしれませんね。これらはイベントコマンド条件分岐ボタンに指定できる条件と同じです。

イベントコマンド条件分岐ボタン

ボタン
指定のボタンが[押されている][トリガーされている][リピートされている]を基準とします。
押されている
ボタンが押されている間ずっと、条件を満たしていると判定します。
トリガーされている
ボタンが押された瞬間のみ、条件を満たしたと判定します。
リピートされている
ボタンが押しっぱなしになっているとき、押された瞬間、24フレーム後、その後6フレームごとに、条件を満たしたと判定します。

RPGツクールMZ ヘルプより

実際に、この条件分岐の実装には上記の関数がそれぞれ使われているのです。

rmmz_objects.js10001行目

今回のように「プレイヤーがボタンを押した瞬間に処理が実行される」タイプの入力は、コアスクリプトではisTriggeredが使われます。なぜisPressedではないのかというと、もしisPressedを使った場合プレイヤーがボタンを押している間ずっと呼び出され続けてしまうからです。プレイヤーがあるボタンを押してから離すまでの間、少し「間」がありますよね。例えば10フレーム押され続けていたとします。isPressedを判定に使った場合、10回同じ処理が呼び出されることになりますが、メニューであれば10回もメニューが開いたり閉じたりすることになります。これがどれだけ煩わしいかは言うまでもないでしょう!
それに対してisTriggeredであれば押した瞬間の1回しか実行されないので、このような処理には最適というわけです。先ほど見たisMenuCalledという、メニュー呼び出しボタンが押されているかどうかを判定する関数に使われているのはこのような理由なのです。

ところでInput.isTriggered(keyName)などの引数であるkeyNameは、ボタン名を表す文字列です。引数として使用可能な文字列(ボタン名)および対応するキー/ボタンは以下の通りです(「○○キー」はキーボードを、「○○ボタン」はゲームパッドを表します。また、ボタンはXBoxコントローラーにおける名称ですが、それ以外のゲームパッドの同じ場所に配置されているボタンと互換性があります)。

  • "ok": enterキー、スペースキー、Zキー、Aボタン

  • "escape": escapeキー、insertキー、Xキー、テンキー0

  • "cancel": Bボタン

  • "menu": Yボタン

  • "pageup": pageupキー、Qキー、LBボタン

  • "pagedown": pagedownキー、Wキー、RBボタン

  • "tab": tabキー

  • "shift": shiftキー

  • "control": controlキー、altキー

  • "up": 上矢印キー、テンキー8、上方向ボタン

  • "down": 下矢印キー、テンキー2、下方向ボタン

  • "left": 左矢印キー、テンキー4、左方向ボタン

  • "right": 右矢印キー、テンキー6、右矢印ボタン

今回の処理に関して、スキルリストウィンドウの表示状態を切り替えるためのボタンをどれにするのか、というのを事前に決めておく必要があります。このボタンは、Scene_Mapで既に使われているボタン…つまりメニューボタンとは別のボタンでなければなりません。となると必然的にtabcontrolshiftあたりが候補になるでしょう。どのボタンにすべきかはプラグインを使うユーザーの好みもありますのでプラグインパラメータで選べるようにすると親切だと思いますが、まずは暫定的にcontrolを使うことにします。

ところでここまでInput.isTriggeredに関する説明を続けてきましたが、isMenuCalledにはもう一つの関数が判定に使われていることを覚えておいででしょうか。

isMenuCalled(再掲)

TouchInput.isCancelled()という関数が並べられていますね。 || は「または」ですのでInput.isTriggeredTouchInput.isCancelled()のどちらか一方でもtrueであれば全体としてtrueになります。ではTouchInput.isCancelled()とは何かというと、タッチにおけるキャンセル操作です。Inputがキーボード・ゲームパッドの入力を管理するクラスであるのに対し、TouchInputはマウス・タッチの入力を管理する静的クラスなのです。
この場合のキャンセル操作とはマウスであれば右クリック、タッチであれば二本指タップを表します。これらの操作が行われていればtrueを、そうでなければfalseを返す関数なのです。
というわけでisMenuCalledとは、以下のいずれかに一つでも当てはまっていればtrueを、一つも当てはまっていなければfalseを返す関数である、ということになります。

  • キーボードのescapeキー、insertキー、Xキー、テンキー0のいずれかが押された瞬間である

  • ゲームパッドのYボタンが押された瞬間である

  • マウスの右ボタンが押された瞬間である

  • 二本指でタップされた瞬間である

このようにコアスクリプトはキーボード、ゲームパッド、マウス、タッチという主要な入力デバイスのすべてに対応するマルチインターフェース仕様になっています。
ツクールで作られたゲームは、ブラウザゲームとしてもデスクトップアプリとしても、あるいはスマホアプリとしてもリリース可能です。なのでどのような入力方式にも対応できるようにしているというわけですね。よってプラグイン作者も、ボタン入力を要求する処理をプラグインに導入する場合、極力上記全ての入力方式に対応できるようにした方が望ましいと言えるでしょう。
例えば仮に、今回の操作がマウスにしか対応していないとします。その場合、キーボード派のプレイヤーやゲームパッド派のプレイヤーはウィンドウを開閉できなくなってしまいます。マウスに持ち替えればいいとはいえストレスになりますし、それが積み重なれば最悪の場合プレイをやめてしまうかもしれません。
入力はゲームとプレイヤーを結ぶ最も重要なインターフェースですので、プラグイン作者もそれを意識した方がよいでしょう。

ここまでコアスクリプトに用意されている入力に関連した関数を見てきましたが、いよいよ実際にそれを使ってウィンドウの開閉処理を組んでいきます。

ボタン入力処理の実装

まずは「controlが押された瞬間、ウィンドウを閉じる」処理を行う関数を用意します。これはマップ上の操作ですので、Scene_Mapのメソッドとして作ります。関数の名前はupdateSkillWindowsとします。スキルに関するウィンドウ群を更新する関数であるからですね。
以下のような枠を用意します。

Scene_Map.prototype.updateSkillWindows = function() {
        
};

まず必要なのは、controlが押されているかどうかを判定する処理、つまりif文です。以下のようにします。

Scene_Map.prototype.updateSkillWindows = function() {
    if (Input.isTriggered("control")) {
            
    }
};

先ほど確認したInput.isTriggeredを記述し、その引数を"control"にしました。ボタン名は文字列にする必要がありますので、""を忘れないようにしましょう。
これだけで条件判定については用意できましたので、続いてその条件が満たされた場合に実際に行う処理…ウィンドウを閉じる処理をifブロック内に追加します。

…とその前に、現在どのようなウィンドウをマップ上に表示しているのかをおさらいしましょう。

ウィンドウと参照可能なプロパティ名

前回までの作業で、この4つのウィンドウを表示するようにしていたんでしたね! ですのでこれらのウィンドウを参照しているプロパティ名を追記していきます。

Scene_Map.prototype.updateSkillWindows = function() {
    if (Input.isTriggered("control")) {
        this._statusWindow
        this._skillTypeWindow
        this._itemWindow
        this._helpWindow
    }
};

そしてそれらすべてにclose()を追記します。

Scene_Map.prototype.updateSkillWindows = function() {
    if (Input.isTriggered("control")) {
        this._statusWindow.close();
        this._skillTypeWindow.close();
        this._itemWindow.close();
        this._helpWindow.close();
    }
};

これでcontrolが押された瞬間に4つのウィンドウを閉じる処理は実装できましたが、これだけでは動作しないのです。というのも、この関数は定義されただけで実際にはまだどこからも呼び出されていないからです。
ということは呼び出す必要がありますが、どこで呼び出すべきなのでしょうか?
結論から言ってしまうと、このような処理はシーンクラスのupdate関数に追加すべきです。updateとは毎フレーム呼び出される関数であり、すべてのシーンクラスに用意されています。ツクールのイベントで言うと並列処理のようなものとお考えください。並列処理は、条件さえ満たしていれば毎フレーム(1秒間に60回)実行されますよね?
プレイヤーがいつボタンを押すのかは完全にプレイヤーの意思次第であって、作者は想定することができません。ということはどのタイミングで判定すべきなのか不明ということになってしまいます。ではどうすればいいのかというと、とにかく毎フレーム判定してしまえばいつかはプレイヤーがボタンを押した瞬間に巡り合えるはず、という考えがコアスクリプトにおいては定石となっています。いわば総当たり作戦ですね。そのため原則として、ボタン入力処理はupdate関数内で呼び出されています。

なのでまずはScene_Mapupdate関数を探しましょう。検索すればすぐに見つかります。

rmmz_scenes.js711行目

まずはこれをFieldAction.jsにコピーしましょう。
これがScene_Mapにおいて毎フレーム実行されているupdate関数です。何やら色々な処理が並んでいますが、ここではあまり重要ではありません。この中のどこかで先ほど定義したupdateSkillWindowsを呼び出したいのですが、どこがいいのでしょうか? …実は順番はあまり重要ではないのですが、とりあえず一番上に置くことにします。

updateSkillWindowsを一番上に配置

関数の実行ですので()をつけることをお忘れなく。これで動くはずなのですが、以前にもご紹介した通りこの書き方はプラグインとしてはあまり好ましくありません。Scene_Mapupdate関数の内容を完全に上書きしてしまっているからですね。このままでは他のプラグインと競合しやすくなってしまいますので、定数に元の処理を逃がします。

定数に退避させた

元の処理内容を_Scene_Map_prototype_updateという定数に代入した上で、それをupdate関数内で呼び出しています。こうすることで競合が防ぎやすくなります。

それでは早速試してみましょう!

4つのウィンドウを閉じられるようになりましたね! これでようやく邪魔なウィンドウ群を非表示にできました!

ですがまだ終わりではありません。大きく分けて3つの課題がまだ残っています。

  1. ウィンドウを閉じることはできるが、その後開くことができない

  2. ゲームパッド・マウス・タッチ操作に対応していない

  3. control以外のキー/ボタンを選べない

これらの課題の解決には次回挑むことにします。

まとめ

今回はウィンドウ群を閉じる操作を組んでみました。お疲れ様でした。

ツクールのコアスクリプトはさまざまな入力形式に最初から対応しているという点が大変に優れています。このおかげでゲーム作者は特に意識しなくてもさまざまな公開形態でゲームをリリースし、プレイヤーの好みに応じた入力デバイスを使ってもらうことができるようになっています。
その仕組みを読み解けば、プラグインにおいても各種入力方式に対応させることができるようになるのです。

次回は上記の通り今回の残課題に取り組んでいきます。
それではまた次回お会いしましょう!

GitHub

この記事が気に入ったらサポートをしてみませんか?