RPGツクールプラグイン制作過程紹介 第14回
前回は既存のScene_Skillから様々な関数をコピーしてきました。これによってマップ上に表示されるウィンドウもScene_Skillとほぼ同じになりました。今回はそのウィンドウを実際に操作できるようにしていきたいと思います。
前回のおさらい
まず現状の動作を確認しましょう。
ご覧の通りウィンドウは表示されているのみで何の動作もしません。右上の「魔法」というコマンドをクリックしようとしましたが、何の反応もなく代わりにマップでの通常の操作と同様、その座標までプレイヤーが移動しました。ということは、次の2つの項目を解決する必要がありそうですね。
ウィンドウを操作可能にする
プレイヤーがクリックに反応しないようにする
1から順番に見ていくことにしましょう。
スキルタイプウィンドウの処理
まず、そもそもなぜウィンドウが反応しないのでしょうか。前回、主要な関数をScene_Skillからコピーしたはずなのですが…改めてコードを確認してみましょう。
よく考えてみたらこの関数にはエラー防止のためにコメントアウトしている箇所がありましたね! Scene_Skillにおいて必要な処理を外しているのですから、正常に動かないのも無理はありません。ますはこのコメントアウトを解除しましょう。
そもそもコメントアウトしていたコードはどのような処理なのでしょうか? 何やらsetHandlerという関数がずらりと並んでいますが…とりあえずこの関数で検索してみます。
setHandlerが含まれる箇所がずらりと出てきましたが、rmmz_scenes.js内にあるのはいずれも関数呼び出しであり定義箇所ではありません。rmmz_windows.jsの方を確認しましょう。
一番上の検索結果が定義箇所ですね! 早速見てみましょう。
どうやらsetHandlerというのはWindow_Selectableというクラスに定義されている関数のようです。その内容はとてもシンプルですね…まず、this._handlersというプロパティは何なのでしょうか? 上の方を検索してみます。
Window_Selectableのinitialize、つまり初期化関数内で値が代入されていました。{}は空のオブジェクトを表すリテラルですね。this._handlersプロパティにはオブジェクトが代入されているということです。
ところでWindow_Selectableというのは、コアスクリプトに用意されている多くのウィンドウクラスのスーパークラスです。Selectableという名の通り、ウィンドウ内の項目をカーソルで選択するための機能が定義されています。前回作成した処理で作成するようになったWindow_SkillTypeやWindow_SkillListはどちらもそのような機能を持っていますが、それはWindow_Selectalbeのサブクラスだからなのです。ですのでこれらのクラスにはsetHandler関数が継承されているはずです。
ここで先程のsetHandlerを再掲します。
Window_Selectable.prototype.setHandler = function(symbol, method) {
this._handlers[symbol] = method;
};
this._handlersはオブジェクトなので、setHandlerの第一引数であるsymbolをキーとして第二引数であるmethodを代入している、ということになります。
これだけを見ても正直ピンときませんね。では実際にこの関数がどのように使われているのかを見てみましょう。今回の関数のコピー元であるScene_SkillのcreateSkillTypeWindow関数内で使われていますから、改めて見てみましょう。
this._skillTypeWindow.setHandler("skill", this.commandSkill.bind(this));
setHandlerはcreateSkillTypeWindow関数内で4回使われていますが、どれも同じような使われ方をしているので最初の行だけ切り取ってみました。
先程のsetHandlerの定義と合わせて考えると、これは以下の処理をしているということになります。
bindというのはJavaScriptの組み込み関数であり、大雑把にいうと関数をコピーする関数です。厳密には違いますが、ここではその程度の理解で大丈夫です。
ではその関数の呼び出し元であるcommandSkillというのは何でしょうか。検索してみましょう。
検索するとこの関数の定義文が見つかりましたので、そこにジャンプしました。これはScene_Skillの関数のようです。その内容についてはここではあまり気にしなくて構いません。
ここで重要なのは、commandSkillがScene_Skillの関数である、ということです。ここで改めて、今注目しているcreateSkillTypeWindowの内容を詳しく見てみます。
setHandlerというのはthis._skillTypeWindowに対して呼び出されていました。_skillTypeWindowに代入されているのはWindow_SkillTypeのインスタンスです。つまりこれは、スキルタイプ選択ウィンドウにcommandSkillをコピーして渡している、という処理だったのです。
コピーするぐらいなら最初からWindow_SkillTypeにcommandSkill関数を定義しておけばいいじゃないかと思えますが、なぜこのようなことをしているのかというといわゆる「関心の分離」のためです。MVCモデルという言葉をご存知でしょうか。大まかにいえば、プログラムにおける機能をモデル(Model)、ビュー(View)、コントローラ(Controller)という3種類に分類し、それらを明確に分離しておく、というプログラミングにおけるベストプラクティスの一つです。この作法には様々なメリットがあるのですが、ここでは「近い種類の機能を分けておくと管理しやすい」程度の理解で問題ありません(詳しくは「MVCモデル」で調べてみてください)。
ツクールのコアスクリプトはこのMVCモデルに忠実に従って構築されています。というのも、「モデル」はゲームオブジェクト(rmmz_objects.js)、「ビュー」はスプライト(rmmz_sprites.js)およびウィンドウ(rmmz_windows.js)、「コントローラ」はシーン(rmmz_scenes.js)およびマネージャ(rmmz_managers.js)、といった具合にそれぞれ分けているからです。ウィンドウはビューを担う存在ですが、ビューに表示以外の機能を持たせるのはMVCモデル的には好ましくありません。その代わりウィンドウにコントローラを担うシーンの関数をコピーすることで、ウィンドウ内の項目を押したときの反応をウィンドウ自身から呼び出せるようにしてある、ということなのです。
ここまでsetHandlerについて調べてきましたが、つまりウィンドウに対して何か操作をしたときに呼び出される処理を渡す、という処理だったのです。その処理であるcommandSkill関数はまだScene_Mapに定義していません。そのためエラーになってしまうのです。ということで、早速commandSkillをコピーしましょう。
Scene_SkillからcommandSkillをコピーしてFieldAction.jsに貼り付け、クラス名をScene_Mapに変更しました。
ところでcreateSkillTypeWindow内でsetHandlerにbindでコピーしている関数はcommandSkillだけではありませんでしたね。
"cancel"に渡しているpopScene、"pagedown"に渡しているnextActor、"pageup"に渡しているpreviousActorも同様にコピーしないとエラーが起きてしまうでしょう。
順番に見ていくことにします。まずはpopSceneで検索してみます。
内容は非常にシンプルですね。SceneManagerのpopという関数を呼び出しているだけのようです。結論から言うと、これは「現在のシーンを終了し、前のシーンに戻る」処理を行なっています。
MZの基本動作では、メニューから「スキル」コマンドを選択するとスキルの使用シーンに移行します。ではスキルシーンにてキャンセルボタンを押すとどうなるかと言えば、メニューに戻りますよね。これがpopSceneの処理なのです。
ではこの関数をそのままScene_Mapにコピーすればいいと思われるかもしれませんが、それでは正常に機能しません。なぜなら今回はマップ上でシーンを切り替えずに直接スキルウィンドウを呼び出しているからです。Scene_Mapから「元のシーンに戻る」処理を行なっても、マップよりも前のシーンはありませんので何も起きません。
ではキャンセルボタンを押すとどのような処理が行われるのが望ましいのでしょうか。それはもちろん「スキルウィンドウを閉じる処理」です。現在のところスキルウィンドウは開かれっぱなしであり、スキルを使いたい場面以外ではとてもジャマですよね。なので開かれているウィンドウをすべて閉じる処理を割り当てましょう。それは後ほど対応しますので、とりあえず今は以下のようにコメントアウトしておきます。
それではその下の2つはどうでしょうか。bindされている関数の名前で検索してみます。
nextActorとpreviousActorは対をなす関数のため、すぐ近くにあります。結論から言えば、これらは「メニュー上でアクターを切り替える処理」を行う関数です。MZの基本動作では、メニュー画面から呼び出せる各種画面においてキーボードのpagedownキーやゲームパッドのRBボタン(XBoxコントローラーにおける名称)を押すとパーティ内の次のアクターに切り替わりますよね。反対に、pageupキー/LBボタンを押すと前のアクターに切り替わると思います。これらはnextActor/previousActor関数が担う処理だったのです。
それではこの処理は今回のプラグインにも必要なのでしょうか。結論から言うと必要です。というのも、アクターを切り替える処理が他にないからです。メニュー画面であれば、「スキル」コマンドを選択したあとアクターを選択します。するとそのアクターがスキル使用画面のアクターになりますよね。このアクター選択に相当する操作がマップには存在しないため、これらの処理がなければアクターを切り替えることができなくなってしまうのです。
というわけでこの2つの関数もコピーしましょう。また、これらの関数はどちらも3行目にonActorChangeという関数を呼び出していますが、それはすぐ下に定義されています。
これは単にカーソル音を鳴らすだけというシンプルな関数ですが、これがないとエラーになってしまいます。ですのでこちらも忘れずにコピーしましょう。
FieldAction.jsにコピーしたあと、クラス名をScene_Mapに変更しています。
これで準備が整いました。早速テストしてみましょう!
きちんとスキルタイプウィンドウがクリックに反応していますね!
クリックしたあと、下のスキルリストウィンドウにカーソルが表示されています。これはスキルリストウィンドウがアクティブになったからです。それは先程コピーしたcommandSkillが呼び出している関数によるものです。
this._itemWindowというのはスキルリストウィンドウのことです。activateというのはウィンドウクラスに定義されている関数であり、これをウィンドウに対して実行するとそのウィンドウがアクティブになります。アクティブなウィンドウはカーソルを動かしたり、選択している項目を決定したりすることができます。これは複数のウィンドウが表示されるシーンでは非常に重要な処理です。このactivte関数や反対に非アクティブ化するdeactivate関数を使って常にアクティブなウィンドウを一つだけに絞らないと、方向キーを動かした時に複数のウィンドウでカーソルが動いてしまうのです。
ちなみにその下にあるselectLastというのは最後に選択した項目を選択する、という処理の関数です。いわゆるカーソルの記憶を実行するために使用されています。
スキルリストウィンドウの処理
これでスキルタイプウィンドウは動作するようになりましたので、続いてスキルリストウィンドウにも同様の処理をしていきましょう。ここからは繰り返しになるのでやや説明を割愛して進めていきます。
onItemCancelにあるdetermineItemという関数は、Scene_SkillのスーパークラスであるScene_ItemBaseの関数です。こちらもコピーします。
useItemもコピーしましたが、この関数からもScene_Mapにはない関数が多数呼び出されています。それらもまとめてコピーしましょう。
Scene_SkillからplaySeForItemとuserを、Scene_ItemBaseからitemとapplyItem、checkCommonEvent関数をそれぞれコピーしてきました。もちろんこれまで通りこれらのクラス名はScene_Mapに変更します。
とりあえずここまでできたらテストしてみましょう。
スキルリストウィンドウもクリックに反応するようになりました! ただしウィンドウ内のスキルをクリックするとエラーになってしまいます。
どうもapplyItemの中のitemTargetActorsという関数に関連してエラーになっているようです。結論から言うとこれはスキルの対象となるアクターを返す関数なのですが、この関数はコピーしていません。じゃあ今まで通りコピーすればいいじゃないかと思われるでしょうが、この関数は本当にこのプラグインに必要なのでしょうか? このプラグインが提供するのは「フィールド上でスキルを使用可能にする機能」です。そのスキル対象となるのは「プレイヤーの目の前にあるイベント」です。itemTargetActorsというのは通常のスキル使用シーンにおけるパーティ内のスキル対象アクターなのですが、マップ上でスキルを使用する場合アクターを対象に取ることはありません。ですのでこの処理は丸ごと不要になります。というわけで、これに関連した部分を削除します。
アクターを対象に取らないということで、アクターを対象にしたときの処理は他の関数でも不要ですので削除します。まずはdetermineItemを見てみましょう。
中央付近にあるif (action.isForFriend())という箇所に注目してみましょう。これは、アクション(スキル)が味方を対象にする場合、という意味の条件分岐です。繰り返しになりますがアクターを対象に取ることはしないので、これも削除しましょう。
ifブロックがなくなったので、else以下の内容がそのまま続く形になりました。続いてuseItemも見てみます。
一番下にあるthis._actorWindow.refresh()はアクター選択ウィンドウに関する処理ですので、これが残っているとエラーになってしまいます。もちろん削除しましょう。
さあ改めてテストしてみましょう!
エラーなくスキルを選択することができるようになりましたね! 《ファイアⅠ》を使用するとマップ上で炎が出てロウソクに火がついていますが、これは以前作成した通りコモンイベントを呼び出すことにより実行されています。スキルに設定されているコモンイベントはapplyItem関数内のapplyGlobalから呼び出されますが、これは対象となるアクターを必要としません。そのため対象アクターを選択しなくてもエラーにならずに済むのです。
まとめ
今回はマップ上でスキルを実際に使用できるところまで組んでみました。お疲れ様でした。
あるシーンクラスから使えそうな関数をコピーすることはお手軽に機能を移植できるので便利なテクニックなのですが、コピー先のクラスによっては修正が必要なこともあります。クラス特有の処理をよく理解することが大切ですね。
最初にあげた課題のうち、2(プレイヤーがクリックに反応しないようにする)は未解決です。実は最後の動画はウィンドウ内をクリックしてスキルを選択するとその座標まで移動してしまいうまくロウソクの前まで誘導できないので、キーボードを使って操作していました。次回はこの課題を解決していきたいと思います。
それではまた次回お会いしましょう!
GitHub
この記事が気に入ったらサポートをしてみませんか?