『RPGツクールMZ』の入力機器周り
『RPGツクールMZ』はキーボード、ゲームパッド、マウス、タッチスクリーンの入力に対応していて、それ自体は非常に良いことですが、デメリットとしてプログラムがややこしいことになっています。
今回そのややこしい入力周りのコードを読み解く場合の一助となる記事を書ければな…というか自分の理解のための記事です!(いつもそうだという話もある)
『RPGツクールMZ』で自作のインタフェースを実装したいという場合には、意外と役に立たない知識であることを事前にご了承ください(笑)
自作インタフェースの作成に関しては次の記事を参照してください。
『RPGツクールMZ』シーンのウィンドウ生成を調べてみた
『RPGツクールMZ』非公式JavaScriptリファレンスとJavaScript - MDN、適宜これらのリファレンスのページにクラスなどリンクしていこうと思います。
基本的な用語については『RPGツクールMZ』用語集も参照ください。
入力機器ごとのAPI
JavaScript はキーボードとマウスの入力を前提として設計されましたが、その後ゲームパッドやタッチスクリーンに対応する拡張が行われています。
『RPGツクールMZ』での扱いを知る前に、JavaScript での扱いをおさらいしておきましょう。
キーボード
JavaScript 初期から入力に対応していますが、とりあえず入力できればいいという感じで用意されたものなので使い勝手は良くなく、騙し騙し使っているというのが現状です。
イベント発生タイミングはkeydown
とkeyup
しかなく、キーボードのディレイ(リピート開始までの時間)やリピート(押しっぱなしで連続入力になる間隔)の設定もありませんし、現在キーが押されているか離されているかすらわかりません(なおkeypress
は非推奨機能)
ちなみに直接のキーボードの機能ではありませんが、日本語入力モードの切り替えもできません。
使いやすいキーボード APIの提案もなされていますが、2025年現在も実験段階です。
なので必要な機能は『RPGツクールMZ』のコアスクリプトの方で拡張してあります。
ゲームパッド
JavaScript はゲームパッドの入力を受け取るためのクラスを持っています。
Safariが2017年に対応して、ひととおりのプラットフォームで使えるようになったという、わりと新し目の機能です。
なので、2015年発売の『RPGツクールMV』はもちろん2020年の『RPGツクールMZ』でもボタン割当てや複数のゲームパッドへの対応などもなく「一応使えないこともないですよ」ぐらいの対応度です。
マウス
マウスの他トラックボールなどのポインティングデバイス共通のAPIです。
ボタンの数やスクロールホイールの入力方向など、ハードの差が大きいので、全部対応しようとすると意外に大変です。
イベントはmouseUp``mouseDown``mouseClick``dblclick
またmouseEnter``mouseOver
などなど、それなりに充実しています。
大抵はボタンを押すために使うので、マウスの機能というより画面上のボタンの機能として理解した方がわかりやすいかもしれません。
タッチスクリーン
タッチスクリーンによる入力に対応する部分です。
マウスの入力に対応していれば、大抵の部分で不都合なく動作しますが、一部タッチ入力専用の処理が必要になり、そのためのAPIも用意されています。
イベントはtouchend``touchmove``touchcancel``touchstart
があります。
マウスのようにポインタがなく、タッチとドラッグしか存在しないので、マウスオーバー(ポインタがボタン上に乗る)が発生しないところに気を使う必要があります。
具体的にはマウスだと1回クリックで決定するのが、タッチだと2回タッチで決定になるというパターンです。
入力チェックはどこから呼ばれているのか
SceneManager.updateInputData()
メソッドを見てみましょう。
SceneManager.updateInputData = function() {
Input.update();
TouchInput.update();
};
『RPGツクールMZ』はSceneManager
がフレーム実行の起点なので、そこから入力関連のオブジェクトも呼ばれています。
1フレーム1/60秒で呼ばれていて、これはアクション性のないRPGとしては十分な入力検知間隔と言えます。
Inputクラス
キーボードとゲームパッド入力は Inputクラスが担っています。
このクラスはメソッドやプロパティがすべて静的( Static )な、いわゆるユーティリティクラスです。
ちなみにアナログスティックに直接対応はしていません。
Input.update()
Input.update()
を見てみましょう。
Input.update = function() {
this._pollGamepads();
if (this._currentState[this._latestButton]) {
this._pressedTime++;
} else {
this._latestButton = null;
}
for (const name in this._currentState) {
if (this._currentState[name] && !this._previousState[name]) {
this._latestButton = name;
this._pressedTime = 0;
this._date = Date.now();
}
this._previousState[name] = this._currentState[name];
}
if (this._virtualButton) {
this._latestButton = this._virtualButton;
this._pressedTime = 0;
this._virtualButton = null;
}
this._updateDirection();
};
まず_pollGamepads()
が呼ばれています。
ゲームパッドからの入力状態を_gamepadStates
プロパティに登録し、同時に対応したキーボードからの入力として_currentState
に登録します。
_currentState
プロパティにkeydown
が行われた時に押されたキーが登録され、keyup
で離されたキーが削除されるという方法で「現在押されているキー」を判定(Input.isPressed()
)しています。
なお、keydown
とkeyup
のイベントはInput._setupEventHandlers()
メソッドで受け取るように設定されています。
Input._setupEventHandlers = function() {
document.addEventListener("keydown", this._onKeyDown.bind(this));
document.addEventListener("keyup", this._onKeyUp.bind(this));
window.addEventListener("blur", this._onLostFocus.bind(this));
};
_previousState
プロパティには直前の状態が入っているので、_currentState
プロパティとの差をチェックすることにより「最後に(ON/OFFが)変更されたキー」が判別できます。
加えて_pressedTime
プロパティにはキーが押され始めてからの時間が入っているので、それを使って「キーが押された直後」を判定(Input.isTriggered()
)します。
その他にも_pressedTime
はisRepeated()
でキーリピートの判定にも使われています。
この辺のトリガやキーリピート処理は JavaScript 本体でやっておいて、パラメータの変更だけで使えるようにして欲しかった部分ですが、仕方がないのでこのようにコアスクリプトで地味に実装してあるわけです。
_virtualButton
プロパティには実際に押されたキーではなくGUI上のボタンがクリックされた場合に送られてくるキーが登録されているので、何か登録されていたら_latestButton
に代入してそのキーが押されたものとして後の処理を行います。
最後に_updateDirection()
が呼ばれています。
ここではこれまでの入力から、入力方向を判定し8方向の_dir8
プロパティと4方向の_dir4
プロパティに保存します。
これらはInput.dir8
およびInput.dir4
プロパティからテンキー対応の入力方向(たとえば↓は2)として取り出せます。
Inputクラスのメソッド・プロパティ
以上に書いたように、現在の入力情報がInput
クラスに保存されています。
そして、次の表にまとめたメソッド・プロパティをチェックすることで取り出し入力を反映した動作がなされます。
メソッド・プロパティ | 説明 |
---|---|
Input.isPressed(keyName) |
押されている |
Input.isTriggered(keyName) |
トリガーされている |
Input.isRepeated(keyName) |
リピートされている |
Input.isLongPressed(keyName) |
長押しされている |
Input.dir8 |
8方向入力 |
Input.dir4 |
4方向入力 |
Input.date |
最後に入力された時刻(ミリ秒) |
ちなみにInput.dir4 === 8
とInput.isPressed("up") === true
はほぼ同じ意味になります。
ただし、Input.dir4
は押しっぱなしをチェックするのでプレイヤーキャラクターの移動入力に使われます。
またInput.virtualClick()
メソッドを使うことで、前述のようにスクリプトから擬似的なボタン入力が発生可能です。
keyNameのテーブル
さてここでメソッドでチェックされているkeyName
は何かというと、以下のプロパティに定義されている文字列です。
キーボードからの入力(キーコード)を『RPGツクールMZ』が扱っているkeyName
に変換するための対応表です。
Input.keyMapper = {
9: "tab", // tab
13: "ok", // enter
16: "shift", // shift
17: "control", // control
18: "control", // alt
27: "escape", // escape
32: "ok", // space
33: "pageup", // pageup
34: "pagedown", // pagedown
37: "left", // left arrow
38: "up", // up arrow
39: "right", // right arrow
40: "down", // down arrow
45: "escape", // insert
81: "pageup", // Q
87: "pagedown", // W
88: "escape", // X
90: "ok", // Z
96: "escape", // numpad 0
98: "down", // numpad 2
100: "left", // numpad 4
102: "right", // numpad 6
104: "up", // numpad 8
120: "debug" // F9
};
必要な機能をあらかじめ割り当ててあるので使いやすくなっています。
しかしキーボードからの入力はこれらのキーだけしかチェックしていないので、キーボードを使っていても、いちいち文字を選んで決定という回りくどい入力方法が必要になっています。
同様にゲームパッドからの入力(キーコード)を『RPGツクールMZ』が扱っているkeyName
に変換するための対応表です。
Input.gamepadMapper = {
0: "ok", // A
1: "cancel", // B
2: "shift", // X
3: "menu", // Y
4: "pageup", // LB
5: "pagedown", // RB
12: "up", // D-pad up
13: "down", // D-pad down
14: "left", // D-pad left
15: "right" // D-pad right
};
キーボードとほぼ同じkeyName
が割り当てられていることがわかります。
こうすることで、ゲームパッドかキーボードどちらかに対応しておけば、もう一方でも違和感なく操作できるというわけです。
利用方法
『RPGツクールMZ』ではWindow_Selectable
でキー入力をチェックして、ウィンドウ内の操作に利用する、あるいはGame_Player
でプレイヤーキャラクターを操作するという使い方がなされています。
Input
クラスの具体的な使い方の参考となるプラグインとしてトリアコンタンさん作の、キー入力によってスイッチを切り替える'KeySwitch.js'があります。
ダウンロード : https://github.com/triacontane/RPGMakerMV/tree/mz_master/KeySwitch.js
このプラグイン使えば、大抵の場合は自分でスクリプト書く必要ない気もしますね(笑)
TouchInputクラス
マウスとタッチスクリーン入力は TouchInputクラスが担っています。
また画面の状態にかかわらず入力が取れるキーボードと違い、入力を受け取るボタンのような画面上の部品が必要なのでそちらの知識も必要になります。
TouchInput.update()
TouchInput.update()
を見ていきましょう。
TouchInput.update = function() {
this._currentState = this._newState;
this._newState = this._createNewState();
this._clicked = this._currentState.released && !this._moved;
if (this.isPressed()) {
this._pressedTime++;
}
};
キーボードと比べるとかなりシンプルです。
1フレームが過ぎる間に起きたイベントによる変化は_newState
プロパティに記録されているので、それを_currentState
プロパティにコピーしてチェックするだけ。
マウスの動作はどうやって知るのかというと、JavaScript にはマウスの各種動作に対応したイベントが用意されています。
幸いなことにキーボードに比べるとその種類は潤沢です。
TouchInput._setupEventHandlers()
メソッドでマウスで発生する各種イベントを対応するイベントリスナ(メソッド)と結びつけています。
TouchInput._setupEventHandlers = function() {
const pf = { passive: false };
document.addEventListener("mousedown", this._onMouseDown.bind(this));
document.addEventListener("mousemove", this._onMouseMove.bind(this));
document.addEventListener("mouseup", this._onMouseUp.bind(this));
document.addEventListener("wheel", this._onWheel.bind(this), pf);
document.addEventListener("touchstart", this._onTouchStart.bind(this), pf);
document.addEventListener("touchmove", this._onTouchMove.bind(this), pf);
document.addEventListener("touchend", this._onTouchEnd.bind(this));
document.addEventListener("touchcancel", this._onTouchCancel.bind(this));
window.addEventListener("blur", this._onLostFocus.bind(this));
};
で、イベント発生に対応したメソッドの中で_newState
プロパティを書き換えているので、1フレーム経過した間の状況はupdate()
で_currentState
プロパティに退避されることになります。
タッチスクリーンとマウスとで異なる部分は、マウスの場合「タッチせずにカーソルが動く状態がある」ぐらいなので、入力機器による差も最小限です。
TouchInputのメソッド・プロパティ
後は、必要なところで_currentState
プロパティを調べればマウスでの操作が可能になるというわけです。
チェック用の専用のメソッドは次のとおりです。
メソッド・プロパティ | 説明 |
---|---|
TouchInput.isClicked() |
クリックされている |
TouchInput.isCancelled() |
右クリックされている |
TouchInput.isTriggered() |
トリガーされている |
TouchInput.isPressed() |
押されている |
TouchInput.isRepeated() |
リピートされている |
TouchInput.isReleased() |
離されている |
TouchInput.isLongPressed() |
長押しされている |
TouchInput.isMoved() |
マウスドラッグ中か |
TouchInput.isHovered() |
マウスが移動中か |
ただし最初に書いたとおり、キーボードと違ってボタンなどのGUI部品とのやり取りを考えなければいけません。
Sprite_Clickable
マウス入力を受け取っているもののひとつはSprite_Clickable
クラスです。
次のようにいくつかの子クラスを持っており、実際は子クラスとして運用されていますが、使い方は共通しています。
update()
→processTouch()
メソッドでTouchInput
の状態をチェックして、自身に関係あるマウスの状態(ボタン上でクリックなど)ならば、適切なメソッドを呼びます。
メソッド・プロパティ | 説明 |
---|---|
onClick |
クリックされた |
onMouseEnter |
ポインタが入った |
onMouseExit |
ポインタが出た |
onPress |
押された |
Window_Selectable
ウィンドウ周りでマウス入力を受け取っているのが、Window_Selectable
です。
こちらも実際に使われているのは子クラスの方ですが、基本的な動作はすべてWindow_Selectable
に含まれています。
Window_Selectable
(子クラスは大量にあるので省略。詳しく知りたい場合はリンク先を参照ください)
update()
→processTouch()
メソッドでTouchInput
の状態をチェックして、自身に関係あるマウスの状態(ボタン上でクリックなど)ならば、適切なメソッドを呼びます。
メソッド・プロパティ | 説明 |
---|---|
onTouchCancel() |
キャンセルされた |
onTouchOk() |
決定された |
onTouchSelect() |
選択された |
onTouchOk()
メソッドで項目がクリックされた他、実行可能な状態かのチェックをします。
onTouchOk()
メソッドの中ではhitIndex()
メソッドでクリック(タッチ)された場所に存在する項目の番号を取得します。
hitIndex()
メソッドの中ではさらにhitTest()
メソッドで、クリック位置がどの項目の枠内であるかの判定をしています。
以上で、クリックされた箇所の項目の番号を決定して、クリックの処理を実行するわけです。
クリックの処理はprocessOk()
メソッドが担っていて、これはキー入力での選択でも呼ばれる処理です。
まとめ
具体的な動作は各 Window_〇〇 クラスや、Scene_〇〇 クラスの実装でなされていますが、今回は割愛しました。
というか、まだよく分かってないので書けません。
最初のあたりで書きしましたが、JavaScript の(キーボード)入力関連の作りは雑で、入力処理の前のかなりの部分を自前で実装する必要があります。
幸い、今回解説したようにその部分は『RPGツクールMZ』がやってくれています。
そのうち、入力の処理を自前で実装する方法もかけたら良いなと思っています(書けたら書くは書かないの法則)
レッツエンジョイ ツクールライフ!
Discussion
MouseEventなら dblclick くらいあってもよさそうなものだが……。 HTMLのDOMと違ってないのか。
ありがとうございます。JavaScript にも dblclick ありますね。
書き換えておきます。