イベントを実装してみる

はいどーもニートですこんばんは。
みなさんツクールでイベント使ってますか?
使ってますよね。
本日紹介するのはそのイベントではなく、プラグインで使えるイベントのお話です。

イベントってなんやねん

ここで言うイベントは、例えばツクール製のゲームで選択肢が表示されているとき、OKボタンを押したらなにがしかの処理がおこなわれますよね。
このOKボタンを押すことをイベントと呼びます。
そして、イベントが発生したとき実行されるものがコールバック関数です。
ツクールMVのウィンドウでは、何がしかの処理を呼び出すための機能は、大本のWindow_Selectableクラスで定義されています。
で、Window_Selectableを継承した各クラスのインスタンスにおいて、イベントハンドラーが登録される作りになっています。

Window_Selectable.prototype.setHandler = function(symbol, method) {
this._handlers[symbol] = method;
};

Window_Selectable.prototype.isHandled = function(symbol) {
return !!this._handlers[symbol];
};

Window_Selectable.prototype.callHandler = function(symbol) {
if (this.isHandled(symbol)) {
this._handlers[symbol]();
}
};

このコードの使い方は今回は触れません(なんか前に書いた記憶あるし)。
じゃあ今回は何するんだと言えば、どこぞの画面に上下左右のボタンを押したとき、何かしらのイベントを発火するようにしたいと思います。

Window_Selectableの改造

「上下左右のボタンを押したときに何かしらイベントを発火させるなら、例えばWindow_MenuクラスのcursorUp()に処理追加させればいいんじゃね?」と思うかもしれません。
確かにこれでも全く問題ないのですが、今回はお勉強ということで、ひとつWindow_Selectableを改造してみましょう。
あと、ひょっとすると、インスタンスごとに実行したい内容が変わるかもしれません。
「インスタンスごとに実行したい内容が変わる」というのは、同じウィンドウでもシーンごとに異なるイベントを発火させたい場合などですね。
そんなとき今回の改造方法は便利です。

というわけで早速いってみましょう。
まず初期化している箇所にこんなコードを追加します。

var _Window_Selectable_initialize = Window_Selectable.prototype.initialize;
Window_Selectable.prototype.initialize = function(x, y, width, height) {
_Window_Selectable_initialize.call(this,x, y, width, height);

this.eventListener = {
callbacks:[],
register:function(fn){
this.callbacks.push(fn);
},
onDown:function(){
for(var f of this.callbacks){
f();
}
}
};
};

大事なのはthis.eventListenerの部分です(他は上書きするときの定形みたいなものです。詳細は公式のヘルプを読むか、ググってください)。
これなんやねんと言うと、イベントハンドラーを登録したり、登録されたイベントハンドラーを実行(コールバック)するためのオブジェクトです。
今回は簡略化のためにonDown()すなわち下キーが押されたときのイベントしか追加しませんが、やろうと思えば複数のイベントを追加させることもできます。
ただコードが煩雑になるので、今回はこんな感じで。
気が向いたら複数対応版も書くかも書かないかも。

さてeventListenerがオブジェクトであることは大丈夫ですよね。
このオブジェクトの中にcallbacksプロパティがあります。
これは、登録されたイベントを保持するための配列です。

registerメソッドは、文字通りイベントハンドラーを登録するためのメソッドです。
やってることは単純で、引数で受け取った関数をcallbacks配列に追加してるだけです。
最後のonDownメソッドは、追加された関数を順番に実行するメソッドです。

シンプルですね。
最後に、先ほど作ったeventListenerオブジェクトが呼び出されるようにしておきます。

var _Window_Selectable_cursorDown = Window_Selectable.prototype.cursorDown;
Window_Selectable.prototype.cursorDown = function(wrap) {
_Window_Selectable_cursorDown.call(this, wrap);

this.eventListener.onDown();
};

これでカーソルがダウンされるたび、eventListenerオブジェクトのonDownメソッドが呼ばれます。
前準備は完了です。

イベントハンドラーを登録する

今回はメニュー画面のステータスウィンドウにイベントハンドラーを登録させてみましょう。
さっそくですがコードです。


var _Scene_Menu_createStatusWindow = Scene_Menu.prototype.createStatusWindow;
Scene_Menu.prototype.createStatusWindow = function() {
_Scene_Menu_createStatusWindow.call(this);
this._statusWindow.eventListener.register(this.onHoge.bind(this));
this._statusWindow.eventListener.register(this.onHoge2.bind(this));

this.messageHoge = 'ほげえええええ';
};

Scene_Menu.prototype.onHoge = function() {
console.log(this.messageHoge);
};

Scene_Menu.prototype.onHoge2 = function() {
console.log('吾輩がほげ2である。');
};

イベントハンドラーを登録しているのは

this._statusWindow.eventListener.register(this.onHoge.bind(this));
this._statusWindow.eventListener.register(this.onHoge2.bind(this));

この二行です。

this._statusWindowというのは、ステータスウィンドウのインスタンス(の参照)です。
ステータスウィンドウのクラスはWindow_Selectableを継承しているため、先ほど作ったeventListenerオブジェクトも扱えます
bind(this)は、onHogeやonHoge2のthis参照の参照先を変えているだけです。
このbindをしないと、onHogeを実行したときにthis.messageHogeがうまく取得できなくなります。
詳しく確かめたい方は、bindを外した上で、各onHogeの中でconsole.log(this)とでもやってみてください。

実行してみる

ゲームを実行したあと、コンソール画面を開いてください。
タイトル画面で下キーを押しても何もなりませんね。
そのままスタートして、メニューを開いて、ステータスウィンドウで下キーを押してみてください。
今度はコンソール画面に「ほげええええ」だとかなんとか表示されましたね。

これがイベントです!
ツクールに標準でついているsetHandlerメソッド(最初に紹介したやつ)を使っても似たようなことが問題なくできますが、違いが一つあります。
それは、setHandlerメソッドはsymbolごとにメソッドを一つ登録することです。
今回僕が制作したものは、メソッドはいくつでも登録可能です。
C#で言うマルチキャストデリゲートみたいなもんでしょうか。
ただ、この違いが何の役に立つのかは、僕もゲーム制作経験が薄いのでぶっちゃけわかりませんw
が、全く同じ機能のものを作っても仕方がないと思ったので、今回のような形になりました。

イベントのいいところは、イベントハンドラーを登録しなければonDownイベントは何も実行しないことです。
これがもし直接cursorDownメソッドに処理を追加していたとしたら、全てのインスタンスが同じ処理を実行してしまいます。

あとはそうですねえ。
全てのインスタンスが同じ処理を実行してほしい場合は何の問題もありませんが、もしも各インスタンスで処理を変えたいとき、if文で処理をわけるより、イベントを使ったほうがスマートな場合があることを覚えておくとよいかもしれません。

とはいえ僕はまだまだうまいことイベントを使えていません。
というかイベントの仕組みを理解するのに二ヶ月くらいかかったド素人です。
JavaScriptのイベントを勉強し始めたのはごく最近(ちゅーか3日くらい前)ですが、C#でのデリゲートやイベントの仕組みの理解は本当に苦労しました。
いや実はまだ理解できていないのかもしれない……。
そんな感じすらあります。
というわけでまだまだ勉強は続けていきますよ。

最後になりましたが、パーフェクトJavaScriptで勉強したことをまとめたくなったのでこんな記事を書きました。
ちゃんちゃん。

フォローする