ツクールMVでキーの高速入力に対応する

はいどーもこんにちは。
みなさんツクールMVでアクションゲー作ってますか?
僕は作ろうとしていました。

本日の記事はアクションゲーを作成している方のための記事です。
ちなみにUnity版の記事はちょっと前に書きました。
Unity版の記事もサラッと流し読みしていただくとベストです。

ツクールMVのキー入力処理について

ツクールMVにおけるキー入力は、基本的にUnityと似たような感じです。
つまりupdate関数内をグルグルと回し、キー入力を受け取ります。
アクションゲームを作っている方なら、Inputクラスを少し改造し、独自のキーに対応させたって人も多いんじゃないでしょうか?

この入力方式の問題点はすでにUnityの記事でも書いた通り、高速入力に対応できないことです。
つまり、フレームレートが下がったとき、押されたキーの取りこぼしが発生します。
また、フレームレートが下がらずとも秒間100キーほど入力すると、自然と取りこぼしが発生します。

秒間100キー入力するケース

Unityの記事のおさらいですが、取りこぼしがあると困るケースを考えてみましょう。

もしあなたの作っているゲームが「一秒間に100キーも押すはずがないし、仮に多少キーの取りこぼしがあったとしても無視できる」のであれば、ツクールにデフォルトで用意されているInputクラスを使用して問題ありません。
実際、RPGツクールMVという名の通りのRPGを作っているのなら、取りこぼしがあってもほとんどのケースで問題ないでしょう。

では逆に「瞬間的に100key/sec出す可能性が十分にあるか、フレームレートが下がっても押されたキーを取りこぼすことは絶対にあってはならない」場合とは何でしょうか?
例えばタイピングゲームですね。
トップタイパーは瞬間的に100key/sec出すことも珍しくありません。
つまりあなたがツクールでタイピングゲームを作りたいなら、Inputクラスは使えません。
別の方式を検討すべきです。

アクションゲームの場合も考えてみましょう(タイピングゲームを作る方はあんまりいないでしょうしw)。
細かくかつ正確な入力が要求され、かつ一時的にフレームレートが落ちる可能性が十分にある場合、Inputクラス以外の利用を検討する価値は十分あります。
例えば格闘ゲームばりの複雑なコマンドによって必殺技が出るゲームがあるとしましょう。
必殺コマンド入力中、画面にド派手なエフェクトが発生し、環境によってはFPS20ほどに落ちることが普通だとします。

この場合、Inputクラスを用いていたとすると(特に意識しなければInputクラスを利用することになります)「確かに正確に入力したはずなのに、必殺技が失敗して発動しなかった」ということになります。
これはユーザーにとって非常なストレスですよね。
たとえFPSがガクッと落ちたとしても、正常にキー入力を受け取れるシステムを用意しておくべきです。

他には格ゲーですかね。
まあいないとは思いますが、もしもあなたがRPGツクールで格ゲーを作ろうとしているなら、キー入力に関しては細心の注意を払うべきでしょう。

取りこぼしを確認してみる

なにはともあれ、キーの取りこぼしを実際に確認してみましょう。
以下のコードをプラグインとして取り込んでください。

マップ上で決定キーを押すと、コンソール画面(F8キーを押したら表示されるやつ)に決定キーを押した回数を表示するプログラムです。
実際に試してみてください。

試せましたか?
連打の達人でもない限り、決定キーを押した回数と、コンソール画面の数字が一致していると思います。

ではシフトキーを押してみてください。
シフトキーを押すと、img/facesフォルダからActor1という画像を読み込み、表示させます(デフォだとActor1という画像が入っていると思いますが、もし存在しない場合は適当なファイルに置き換えてください)。
この動作は一万回繰り返されます。

一気にFPSが下がったことがわかると思います(パソコンがハイスペックすぎて下がらないという方は、何度かシフトキーを押してください)。
この状態で決定キーを連打してください。
実際に押した回数と、コンソール画面に表示される数字の数が一致しないことがわかると思います。

ここまで極端にFPSが落ちることは稀だと思いますが、フレームレートに依存したキー入力の取得は多かれ少なかれ「取りこぼしが発生する可能性がある」という問題を潜在的にはらんでいます。

なお、SceneManager._deltaTime = 1.0 / 60.0;はフレームレートを60にしますよーという程度の意味です。
環境を変えている方がいるかもしれないので一応書いておいただけです。

問題の解決方法

それではどのようにキー入力を受け取ればよいのでしょうか?
Unityの場合はOnGUI関数で受け取ったキーの一覧を保存しておき、Update関数が実行されたタイミングでその一覧を「実際に押されたこととして」シミュレートすることで問題を解決しました。
ツクールMVでも同じことをすればよいのです。

と言ってもOnGUI関数なんてないので、その辺は適宜ツクール向けに変えなければなりませんが……。

まずは以下のコードをプラグインとして保存し、ゲームに導入してください。

これは先ほどのコードを少し改良したものです。
決定キーを押すと「決定キー押された回数:i」と表示されます(iは押した回数)。
この決定キーの取得にはInputクラスを使用しています。

また、何らかのキーを押すと「何らかのキー押された回数:i」と表示されます(iは押した回数)。
このキーの取得にはInputクラスを使っておらず、keydownイベントとkeyupイベントを使用しています。
キーは押された一回だけ取得してほしかったのですが、環境によってはkeypressイベントがキーを押し続けている間ずっと取得するらしいので、とりあえずこうしています(ツクールMVのスタンドアロンはキーをずっと取得していました)。
あんまりよく調べていないので、詳しい仕様は不明ですw

肝心の中身ですが、難しいことはしていません。
keydownイベントで押されたフラグをONにし、keyupイベントでフラグをOFFにしているだけです。
キーの数もいくつか調べていないので、とりあえず256個取得しています。
AキーからZキーまでならとりあえず問題ないでしょう。
きちんと調べてないので、今回はZキーを使ってくださいw

さてこのkeydownイベントですが、これは即時実行されます。
つまりupdate関数を介しません。

実際に実行してみてください。
ゲームのマップシーンに移動後、適当にZキーを連打してください。
「決定キー押された回数:i」と「何らかのキー押された回数:i」が一致するはずです。

次にShiftキーを押してFPSを下げたあと、再びZキーを連打してください。
「決定キー押された回数:i」と「何らかのキー押された回数:i」が一致しないはずです。
具体的には、即時実行される「何らかのキー押された回数」の方が回数が多いはずです。

もうおわかりですね。
この「何らかのキー押された回数」を参照し、update関数で「そのキーが押されたこととして」シミュレートするのです。

シミュレート方法を考える

さてぼんやりと仕様が決まってきました。
では具体的にはどのようにして実装したらよいでしょうか?

簡単なサンプルプログラムを作ったので、公開してみます。
https://github.com/Tsumio/rmmv-plugins/blob/master/plugins/SuperFastInput.js

このプログラムは、keyDownイベントで取得したキーをparam.inputedKeys配列に保存し、シーンのupdateメソッド内のcaptureInputメソッドでシミュレートしています。
その後、updateメソッドの最後でparam.inputedKeys配列の中身をreleaseInputメソッドで空にしています。
空にしている理由は、updateメソッドの終わりで空にしておかないと、次のループでまた同じキーがシミュレートされて都合が悪いからです。

captureInputメソッドを見てください。
今回はお試しなのでZキーにしか対応させていませんが、同じ要領で他のキーのシミュレートも可能です。

注*param.inputedKeysには256種類のキーデータを保持していますが、僕自身がkeyCodeの仕様に詳しくないため、実際のところ何を保存しているのかはよくわかっていません。実際に作成する場合、シフトキーなどがどうなっているのかきちんと調べてから利用することをオススメします。

また、Shiftキーを押したとき、決定キーとの同期をわかりやすくさせるために全てのカウンターを0にしています。

それでは実行してみてください。
シフトキーを押したあと、決定ボタンを連打しましょう。
多少のラグがありますが、コンソール画面に正しく回数が表示され、漏れもないことが確認できるかなと思います。
ラグの原因は、updateメソッドの実行されるタイミングがまばらだからです。
しかしたとえラグがひどかろうとも、update関数が実行されたタイミングで保持していたキー入力をシミュレートするため、きちんと全てのキー入力を処理することができます。

「何らかのキー押された回数」が即時実行された(onkeydownイベント)結果ですが、その後きちんとupdateメソッド内で「決定キー押された回数」が同じだけ実行されていることがわかるはずです。
つまり、フレームレートに依存しないため、FPSがガクッと下がってもキー入力の漏れは起こりません。

なお、このプログラムはあくまでもサンプルです。
実際の使用では不都合な部分も出るかなと思います。
気が向いたらちゃんとしたの作りますw

keydownイベントで即実行してはダメなのか?

「わざわざupdateメソッド内でシミュレートせずとも、keydownイベントで即結果を反映させてはダメなのか?」と思う方もいるかなと思います。
しかしInputクラスは通常、update関数内で処理されます。
したがって、シミュレートするタイミングもupdate関数内が最適でしょう。
即時実行した結果、予期しない不具合の原因となるかもしれません。
また、いろいろなシーンで使い回すなら、結局のところupdateメソッド内でシミュレートさせた方がラクチンです(イベント内でシーンを把握して場合わけするのは面倒だし汚い)。

おわりに

RPGツクールMVでアクションゲーを作っている方も多いと思うので、今回こんな記事を長々と書いてみました。
少しでも参考になれば幸いです。

なお、本文ではkeydownイベントを使っていますが、即時実行されるタイプのものなら別にkeydownイベントじゃなくても問題ないです。
何が即時実行で使えるのかよくわからなかったので、今回はkeydownイベントを使っただけですw
ほなそんな感じで。

フォローする