Help us understand the problem. What is going on with this article?

RPGツクールMVの構造を丸裸にする

More than 1 year has passed since last update.

RPGツクールMV Advent Calendar 2016 12日目!

※この記事は、プラグイン・スクリプトの書き方の基本がわかっている人に向けて書いています。
「ツクールMVのプラグインの作り方を知りたい!」と思っている方には、弓猫さんの記事
『RPGツクールMV』で、一人でも多くの人がプラグインを自作できるようになる記事
(RPGツクールMV Advent Calendar 2016 5日目)がわかりやすくて入門に最適なのでおすすめです。

※記事公開からほぼ丸一年が経過しましたが、ここに書かれている内容は今でもほとんど同じです。
RPGツクールMV(というかRPGツクールシリーズ)は過去の仕組みの継続をとても重視するタイプのプロダクトなので、本記事を始めとした「昔に書かれた情報」も有効に活用できることが多いのはメリットのひとつですね!


単にツクールMVを改造したいorプラグインを作りたいと思った時は、
まずコアスクリプトを検索していじりたい記述がされている部分を見つけ、
それを直接なりプラグインなりで改変するのが良いと思います。
要するに必要な時に必要な知識を得るスタイルですね。
(上の弓猫さんの記事でもこの方法が推奨されています)

ただもし、あなたがプラグイン作家として活躍したいとか、
イベントコマンド主体ではなくスクリプト主体で複雑なゲームを作りたい場合は、
手っ取り早くRPGツクールMV全体の構造をざっくりと知っておきたいと思うかもしれません。

私も結構いじりたがるタイプで、手探り(=コアスクリプト読破)による理解で苦労したので
そのうち全体の構造をまとめた記事が出るんじゃないかと思っていましたが、
一年経ってもまだ無いようなので適当に書いてみることにしました。

コアスクリプトのファイル

まず、RPGツクールMVの本体プログラムとも言える「コアスクリプト」を
構成しているファイルは何なのでしょうか。
今回のツクールはHTML5形式ですので、プロジェクトのindex.htmlを見ればそこがわかります。

index.html
<script type="text/javascript" src="js/libs/pixi.js"></script>
<script type="text/javascript" src="js/libs/pixi-tilemap.js"></script>
<script type="text/javascript" src="js/libs/pixi-picture.js"></script>
<script type="text/javascript" src="js/libs/fpsmeter.js"></script>
<script type="text/javascript" src="js/libs/lz-string.js"></script>
<script type="text/javascript" src="js/rpg_core.js"></script>
<script type="text/javascript" src="js/rpg_managers.js"></script>
<script type="text/javascript" src="js/rpg_objects.js"></script>
<script type="text/javascript" src="js/rpg_scenes.js"></script>
<script type="text/javascript" src="js/rpg_sprites.js"></script>
<script type="text/javascript" src="js/rpg_windows.js"></script>
<script type="text/javascript" src="js/plugins.js"></script>
<script type="text/javascript" src="js/main.js"></script>

ご覧の通りすべてjsフォルダのJavaScriptファイルを読み込んでいます。
(実際にはこの下にさらにプラグイン一覧でONになっているプラグインファイルが並びます)

ただし、上5つのlibsフォルダに入っているファイルは
ツクール開発部とは無関係に作られたライブラリ(無料ソフトみたいなもの)ですので、
実際にツクールの本体とみなすべきは下の8つのファイルです。

その中でも、plugins.jsはエディタにより自動生成されてプラグイン一覧を記述しているだけで、
main.jsは最初にplugins.jsに書かれたプラグインファイルを読み込んで
コアスクリプトを起動する関数を叩いてるだけ(大事ですが)なので実質上は
rpg_で始まる6つのファイルがRPGツクールMVのコアスクリプトといえます。

6つのファイルの役割分担

さて、コアスクリプトが6つのファイルに分かれていることがわかりました。
これらのコードはそれぞれの役割に応じて結構きれいに分割されており、
私なんかは眺めているだけで幸せな気分になります。(←危険人物)

そんな変態さんでなくても、各ファイルの役割がわかっていれば
「この要素をいじりたい時はどのファイルに目をつけるべきなのか」が
すぐわかるようになるので、把握しておくメリットは結構あります。
というわけで以下ではそれぞれのファイルの役割を説明していきます。

rpg_core.js

主に画像、音声、入力などに関する最も基本になるクラス(←わからない方は
「データのまとまり」程度の意味にとらえてください)が定義されているファイルです。

そしてここからが大事なのですが、おそらくほとんどの場合、
プラグインを作る時にこのファイルに書かれた内容を変更する必要はありません。
余りにも基本的すぎて具体的なゲームの処理についてはまだ書かれていませんし、
どうも方針として画像の効率的な処理などの難解な記述はこのファイルに押し付ける
傾向があるらしく、未だに私も理解できない部分があります(笑)

ただし残りの5ファイルからこのファイルの関数が呼び出されていることが多いので、
内部構造は理解しなくても良いですが
頻出の関数については「それを呼び出すとどういう結果になるか」はある程度理解する必要があります。

なおなぜかこのファイルのクラスのみ、ツクールのヘルプを開いて
一番下の「JSライブラリ」という章を開くと説明が載っています。全部英語ですけど。
(プラグイン作家としてはどちらかというと残りの5ファイルの解説が欲しいのですが…難儀です)

以下、いくつか重要な入力関係のクラスを紹介します。

Input

このクラスはツクールにおける入力(キーボード、コントローラー)を管理しており、
Input.isPressed('ok')の形で「ok(決定キー)という名前のボタンが押されているか」を判定できます。
(名前と実際のボタンの対応についてはInput.keyMapperに書かれています)

また、Input.isTriggered('キー名')で「そのキーが押された瞬間か」判定できますので、
これを応用すればスクリプトで簡単にキーが押された瞬間だけ条件分岐できたりしちゃいます。

面白いのがInput.isRepeated('キー名')で、これは押しっぱなしの時
「カッ…カカカカカカ!」って形でtrueが返ってきます。ウインドウでカーソルを動かす時のアレです。

TouchInput

これはツクールにおけるクリックとタップによる入力を管理するクラスです。
TouchInput.isPressed()で左クリックorタップされているかどうか判定できます。
(もちろんisTriggeredや右クリック判定などもあります、詳しくはツクールのヘルプを見てね)

本来のJavaScriptでは別々に判定されているクリックとタップを
一つの関数で判定できるようにしてくれる素晴らしいクラスです(何)

rpg_managers.js

主にゲームの情報を管理するクラスで、XxxxxManagerというクラス名で統一されています。

DataManager

ここではすべてのグローバル変数が宣言されています。
$data...$game...などがそれですが、いっぱいあって最初は面食らいます。
特に$dataMap$gameMapに至っては後半の名前が同じで
「どう使い分けてるんだ」と思われるかもしれません…が、簡単です。

$dataのつく変数は、ゲーム起動時にdataフォルダに保存されている
***.jsonファイルからそのまま読み込まれます。
(ちなみにツクールエディタでの編集作業というのは、概ねこのjsonファイル群の中身を書き換えているだけです。
「エディタ使わずにjsonから直接読み書きしたら便利!!」みたいな記事が時々出てくるのはこのため)
つまり、ゲームプレイ中は不変です。(追加したプラグインが何かしない限り)
(例:$dataMap.events[1].pages[0].listには、
   「イベントID1番のキャラの1ページ目のイベントコマンドのリスト」が入っています)

一方、$gameのつく変数は、もう少し下で出て来るrpg_objects.jsのクラスであり、
ゲーム中に変更されることがあります。
(例:$gameMap.event(1).xには「イベントID1番のキャラの現在のx座標」が入っています)

BattleManager

バトルの処理についてはこれとScene_Battleの間でかなりややこしいやり取りが存在するようですが、
これについてはRPG ツクール MV の戦闘の実装という記事がわかりやすいのでそれを見てください!

rpg_objects.js

主にゲームの情報を管理するクラスで、Game_Xxxxxというクラス名で統一されています。

マップ(Game_Map)やプレイヤー(Game_Player)、
マップイベント(Game_Event)やコモンイベント(Game_CommonEvent)などの状況や
各キャラクターのステータス(Game_Actor)とそのパーティ(Game_Party)、
アイテム(Game_Item)や敵(Game_Enemy)とその群れ(Game_Troop)、
スイッチ(Game_Switches)・変数(Game_Variables、←ツクール側の方ね)などの
ゲームの主要な要素とその振る舞い方の定義はここに集まっています。

Managerとの大きな違いは、
「ニューゲーム」のタイミングで新しいオブジェクトが再生成されて
似た名前のグローバル変数(Game_Mapなら$gameMapなど)に代入されること

そしてセーブデータとして保存されること(ただしはいくつかは例外、後述)です。

ゲームごとに一つだけあればよいのがmanagerで、
セーブデータごとに一つ必要なのがobjectと思えばよいでしょう。

グローバル変数

$game系のグローバル変数は$gameTemp,$gameSystem,$gameScreen,$gameTimer,
$gameMessage,$gameSwitches,$gameVariables,$gameSelfSwitches,$gameActors,
$gameParty,$gameTroop,$gameMap,$gamePlayer
の13個です。
ちなみにその内$gameTemp,$gameMessage,$gameTroopの3つはセーブされず、
残りの10個はセーブされます。

しかしrpg_objects.jsには他にもGame_Eventなどのオブジェクトが定義されています。
これらは直接グローバル変数になっているのではなく、
$gameMap$gameScreenなどに格納されているのです。

以下によく必要になるオブジェクトの取得の仕方を書きます。

$gameActors.actor(アクターID)      //指定したアクターIDのGame_Actor
$gameParty.members()[i]           //先頭からi番目のGame_Actor(ただし、iは0始まり)
$gameMap.event(マップイベントID)   //指定したマップイベントIDのGame_Event
$gameScreen.picture(ピクチャーID)  //指定したピクチャーIDのGame_Picture

$gameMap._interpreter             //並列処理「以外」のイベント用のGame_Interpreter
$gameTroop._interpreter           //戦闘中のイベント用のGame_Interpreter
$gameMap.event(ID)._interpreter   //マップイベントの並列処理用のGame_Interpreter
$gameMap._commonEvents[ID]._interpreter  //コモンイベントの並列処理用のGame_Interpreter
//※Game_Interpreterについては「マップの流れ」で少し解説します

rpg_scenes.js

主にゲームの「シーン」を管理するクラスで、Scene_Xxxxxというクラス名で統一されています。

これについてはめちゃくちゃわかりやすい記事を書かれた方がいます。
RPGツクールMVのランタイムコードを読む - Sceneを理解する
私が説明するより、こちらを見てください!

rpg_sprites.js

主にゲームの「画像の加工&表示」を担当するクラスで、Sprite_Xxxxxというクラス名で統一されています。

例えばSprite_Characterというクラスではゲーム内の座標に応じて
キャラクターを描画し、歩いていれば一定時間ごとに右足や左足を出した画像を表示する、
といった処理を担当しています。

Sprite_Buttonを除き、入力への反応すらしません。
本当に「画像を、どのように、表示するか」だけに注目したシンプルなクラスが集まっています。

rpg_windows.js

主にゲームの「ウィンドウの処理」を担当するクラスで、Window_Xxxxxというクラス名で統一されています。

ここでいうウィンドウとは、本当にあのドラクエとかのウィンドウです。
ツクールは典型的なマルチウィンドウシステムを採用しているので、
メッセージウィンドウやステータス画面などあらゆるところに「ウィンドウ」が出現します。

ウィンドウをどう表示するかや、幅や高さをどうするか、画像を配置するのか、
キー入力されたらどう反応するかなどの処理は、このファイルで定義されています。

で、結局どこをいじればいいの

一言で言うと、ウィンドウ関連はrpg_windows.js画像表示関連はrpg_sprites.js
ゲームシステムはまずrpg_objects.js→そこになかったらrpg_managers.jsを見るという感じです。

rpg_core.jsはさっき言ったように具体的なシステムや表示に関する記述ではありませんし、
rpg_scenes.jsは独自のメニュー画面や新しいミニゲームを作るので無い限り
いじる必要が無いことが多いです。
(公式プラグインのEnemyBook.js、Gacha.jsなど、Sceneを作っている例もあります。
 そういった大規模なプラグインを作りたい時はそれらを参考に作りましょう。)

プラグインとセーブ

「プラグインで新たに定義したデータをセーブしたい!」という時は、
$gameで始まるグローバル変数に保存しないといけません。
(色んな方のプラグインを見てると、基本的に$gameSystemに保存するのがお作法のようです)
何故かと言うと、先程も一度触れたようにセーブデータに保存されるのは(Configなどを除いては)
rpg_objects.jsに定義されているいくつかのオブジェクトだけだからです。

「ゲームの状態」さえ記録されていれば画像の描画状況などはすべて記録しなくても再構築できるからですね。
この辺に「画面表示はspritesとwindows」「セーブする情報はobjects」「セーブしない情報はmanagers」
と切り分けた意味が現れててきれいですね。恍惚とします、脳汁がでます。(←危険人物、危険人物!)

ゲームの流れ

さて、これで「どんなものがあるか」についてはわかりました。
しかしツクールの理解でもう一つ欠かせない(そしてコアスクリプトを見ていても
すぐにはわからない)のはゲームの「流れ」の理解です。
すなわち、どんな関数がどこから呼び出されてゲームが動いていくのか…

大きく「初期化」と「ループ」の2つにわけて以下に簡単に書きます。

初期化の流れ

window.onload (ブラウザがページを読み込んだ時に起動する)
->SceneManager.run (main.js)
->Graphics,WebAudio,Input,TouchInputを初期化 (SceneManager/rpg_managers.js)
->Scene_Bootを起動
->requestAnimationFrame(すぐ下で説明)にSceneManager.updateを登録
 以降は「ループ」の流れに従う

ループの流れ

requestAnimationFrame (ブラウザが画面を描画したい時に自動的に呼び出す関数)
->SceneManager.update (rpg_managers.js)
->上で紹介したSceneの記事のルールに従い、1/60秒毎に現在のシーンを処理
 大抵の場合、Scene_Xxxxx.update (その場合、さらに以下が続く)
 ->Sceneは、自身の処理+すべての子供(this.addChild()で登録されたsprites,windows)をupdate
 ->呼び出されたsprites,windowsはさらに自身+子供をupdate
->最後に再びrequestAnimationFrameにSceneManager.updateを登録
(しばらくして)再びrequestAnimationFrameが呼び出される→以降ループ

なかなか複雑な流れをしてますが、
大事なことはscenes,sprites,windowsのupdateという関数はきっかり1/60秒毎に呼び出される
this.addChild()を忘れなければ)ということです。
また、Game_MapやBattleManagerなどのupdateも担当するシーンが呼び出していることが多いので、
基本的にupdate=1/60秒毎と理解して良いです。(BattleManagerは違いますが)

また、これを裏返して考えるとゲーム中のすべての処理は(初期化を除いて)
update関数から始まっていることがわかるので、「この関数、いったいどこから呼ばれているんだ…」
とか迷ってしまった時はまずそのクラスのupdateを探す→無ければ関連しているクラスのupdateと
辿っていくと見つけやすいです。
(関数を辿りたいだけなら調べたい関数にconsole.log(new Error().stack)を仕込んでもよい)

マップの流れ

バトルの方は複雑な分、詳細に説明している記事があったのでリンクを貼って楽をさせて頂きました。
その代わりといってはなんですが、バトル並みかその次に複雑な構造をしている
マップについて詳しく流れを書いてみました。

Scene_Map.update (ここ以前は省略しているが、「ループの流れ」の通り)
->$gameMap,$gamePlayer,$gameTimer,$gameScreenをupdate
->$gameMapはさらに、自分のGame_Interpreter→スクロール処理
 →マップイベント(IDが小さい順)→コモンイベント(IDが小さい順)
 →乗り物→遠景の順でupdate

Game_Interpreterはイベントコマンドを順番に処理する機構を持つクラス。
 「文章の表示」などの各種コマンドを実行する、ウェイトが発生したら待つなど。

特にイベントについては「マップイベントとコモンイベントとプラグインがお互いに連携する」
というシチュエーションもあって動作する順番が大切だったりするのですが、
これを見てもよくわかりません(笑)以下でもう少し解説します。

トリガーが「並列処理」でない時はイベントは$gameMapのinterpreterに実行内容リストを渡して代行させます。
$gameMapは複数のinterpreterを持ちませんので、これにより
「並列処理」以外のイベントは1フレーム内に2つ以上同時に実行されないことになります。
その一方で、「並列処理」の時はそれぞれのイベントがupdateされたタイミングで
自分が持つinterpreterを使って
updateします。

つまり、イベントについてだけ抜き出してまとめると1/60秒毎に
「並列処理」以外のイベント(同時に一つまで)→並列マップイベント(IDが小さい順)
→並列コモンイベント(IDが小さい順)
の順番で実行されているわけです。ややこしいい!!

上級者向け情報

以下は、オブジェクト指向を知ってる方向けの情報です。

継承

ツクールでの継承パターンは、以下のような感じです。

//JavaScriptではこの関数がコンストラクター
function Derived() {
    this.initialize.apply(this, arguments); //initializeに移譲
}

Derived.prototype = Object.create(Base.prototype); //←DerivedはBaseを継承
Derived.prototype.constructor = Derived;

Derived.prototype.initialize = function() {
    Base.prototype.initialize.call(this); //←これでsuper.initialize()的な意味になる
};

なお新たなオブジェクトをセーブデータとして保存させたい場合、
そのクラスをグローバル空間で定義していないとうまくロードできません。
これはJsonExクラス(rpg_core.js)の仕様です。

依存関係

Scene->Spriteset or Window->Game_Xxxxxと一方向的に依存させるのがお作法なようです。
特にGame_Xxxxxに余計なオブジェクト(Spriteなど)をしまうと
前述の理屈でセーブデータが無駄に大きくなるのでやめましょう。

イベントコマンドのthis

イベントコマンド「スクリプト」や条件分岐:スクリプトのthisGame_Interpreterを指しています。
そのキャラの座標とかはGame_Eventにしまわれているので、取りたい場合は
this.character(0).xなどで取得しましょう。
(移動ルートの設定:スクリプトのthisGame_Eventだったりします)

おわりに

もうなんか記事書くの疲れたので締めくくる言葉なんて思いつきませんが楽しくツクールしようぜ!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした