【ツクールMV】エラー情報の見方

みなさんツクールしてますか?
ツクールでプラグインを作っていると、エラーが出ることなんてしょっちゅうですよね。
今回はそんなプラグイン制作者を対象とした記事ではありません
「プラグインを導入したらエラー出ちゃったよ~」「プラグインをちょこっと改造したんだけど、よくわからないエラーが英語で表示されたよ~」といった感じの方が、エラーを見ても慌てなくて済むようにする記事です。

なお、注釈で小難しいことを書いている場合がありますが、飛ばしてもらっても支障はありません。
なるべく平易な言葉で書くようにしているため、用語をあえて単純化している場合が多々あることをご了承ください。

事前準備

まずは以下のプラグインを導入してください。

デベロッパツール管理プラグイン(トリアコンタンさん作)
デバッグ支援プラグインツミオ作☆

どちらのプラグインにも何やら長々と説明が書かれていますが、読まなくても大丈夫です。
今回はこれらのプラグインで提供されている一部の機能を使うだけです。

注*デバッグ支援プラグインはdebug.htmlをプロジェクトフォルダ(index.htmlのあるフォルダ)に設置する必要があります。

デベロッパツールを起動させてみる

デベロッパツールを開いてゲーム制作をしている方なら、以下のような画面に出会ったことは幾度もあるかなと思います。

もし「この画面なに?」と思ったなら、先に紹介したプラグイン導入後、ゲームを起動してください。
何やら怪しいウィンドウが起動するかなと思います。
これがデベロッパツールです。
プラグインを導入しなくともF8キーを押せば開きますが、ワンステップが面倒なので僕はデベロッパツール管理プラグインを導入して「開始時に起動」させることをオススメします)。
今後、このデベロッパツールを駆使してエラー情報を見ていきます
テストプレイ時には必ず開くようにしてくださいね。

エラーをわざと発生させてみる

エラーをわざと発生させてみましょう。
まずは下記のコードを新しいプラグインとして導入してください。
プラグインは.js形式で保存すれば基本はOKなのですが、.jsファイルの作り方がわからない場合は、適当なプラグインの最後にコードを追加してください。
それでも多分問題なく動きます。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);

this.hogeee();
};

このプラグインを導入したあと、ゲームを普通に起動し、新規にゲームを始めます。
その後にメニュー画面を起動すると、こんなエラー画面が表示されるはずです(細かい表示は環境によって変わります)。

画面下部に

・エラーが発生した可能性のあるプラグイン名
・エラーが発生した可能性のある行

が表示されていますね。
これは僕のプラグイン(DebugToolEx.js)の機能です。
示された行数をチェックすると、this.hogeee();と合致するかなと思います。
実際、今回のエラーの原因はこの行です。
僕のプラグインがうまく捕捉してくれたようですね。

エラーの内容を解読する

画面には黄色い文字で“undefined is not a function”と書かれていますね。
これを直訳すると「未定義は関数ではありません」ということです。
「意味わかんねーぞこのハゲー!」という罵詈雑言が飛んできそうですので、もう少し簡単に言い直します。
「存在していない関数を呼ぼうとしたよ」ということです(ただし、これは厳密には正しくありません)。
実際、今回作成してもらったプラグインにはhogeeeというメソッド(関数)はどこにも宣言していません。

注*関数が何かわからない場合でもそのまま読み進めてください。

つまり、このthis.hogeee();を適切な関数名に置き換えるか、あるいは不要な場合は削除してしまえばエラーは解決することになります。

注釈
“undefined is not a function”の本当の意味は、「undefinedを関数として呼ぼうとしていますが、undefinedは関数ではないため関数としては呼べませんよ」ということです。console.log();などでthis.hogeeeの中身をチェックしてください。undefinedとなっているはずです。
undefinedはプリミティブ型であり関数ではないため、関数として呼ぼうとする(()で関数として呼ぶことを意味する)とエラーが出るのです。

デベロッパツールでエラー内容を解読する

「なんや、ツミオはんのプラグイン使えば、エラーチェックもお茶の子さいさいやないか!」と思った方もいるかと思います。
では、今度はDebugToolEx.jsをの機能を使わずにエラーチェックをしてみましょう。
これができるようになれば、エラーが出たとき適切に対処できるようになります

先ほどと同じようにエラーを発生させてみてください(メニュー画面を開く)。
デベロッパツール(なんか英語がいっぱい表示されてるウィンドウのことです)に、以下のようなエラーログが表示されているかなと思います。

「うわああああああああ意味わかんねえええええええ!」と慌てないでください。
一つ一つ見ていきましょう。

一行目に”undefined is not a function”と書いてありますね。
先の項で説明した通りです。
この行は簡単なエラー原因を示すだけであり、一行目の情報だけでは役に立たないことがほとんどです。

二行目を見てください。
“at Scene_Menu.create (TestPlugin.js:20)”と表示されているかなと思います(数値は環境によって変わります)。
最後の丸括弧の部分に注目してください。
これ、先ほどチェックしたファイル名+行数になっていませんか?
そうです。これがエラーの原因を示す行なのです!
“at Scene_Menu.create”は、Scene_Menu.createにおいて発生したよー的な意味です。
実際、プラグインにはScene_Menu.prototype.createとありますね。
まさにビンゴです。

他の行も見てみる

さてデベロッパツールでもエラーの内容を確認することができました。
しかし、「他の行には何が書いてるんや?」と疑問に思った方もいるかと思います。
落ち着いて見ると、ファイル名はまちまちですが、ほとんど「SceneManager.ほにゃらら」となっていることがわかりますね。
各行の詳細は割愛しますが、このエラーログはスタックトレースと言って、下から上に読んでいきます
まあ実際のところ下から上に読むより、上から下に読むことの方が多いと思うのですが(だからこういう表示順なんですし)、このスタックトレースは原因となった関数がどこでどう呼ばれたのかを示しています。
つまり直接の原因となった行以外の情報も書かれてあるのですね。
このことを知らないと、「うわ! いっぱいエラーが表示された!」と慌ててしまうかもしれません。

DebugToolEx.jsのエラー捕捉機能について

「他の行の情報? そんなん邪魔やんけ!」と思うかもしれませんが、これはこれで便利です。
ちなみにですが、その「邪魔やんけ!」と思われる表示の一部を削除し、スタックトレースの見方がわからなくても理解しやすいように書き直したのがDebugToolEx.jsのエラー捕捉機能です。
これは除外リスト(興味のある方はNTMO.DTE.PluginManager.prototype.createExcludedListあたりを見てください)に登録されているファイルを省き、エラー情報を表示しています。
つまり、一般的にエラーの原因となる可能性が低いファイル(コアスクリプトなど)をあらかじめ除外してエラーログを表示させているのですね。
たったそれだけのことしかしていませんので、スタックトレースを適切に読めるならば、この機能は不要です。
不要なのですが、パッと見たときにエラーの行数がすぐわかるので僕は愛用しています。

その他のエラーについても見てみる

さてここまででエラーの基本的な読み方の説明は終わりました。
ここからはケース別に見ていきます。
僕が実際に遭遇したケースや、ツクマテさんなんかで「このエラーの意味がわからない」と質問があったケースについて取り扱っています。

Maximum call stack size exceeded

最初に作ったエラー発生用のコードを以下のように置き換えてください。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);

};
var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);
};

実行後、メニュー画面を開いてみましょう。
何やらエラーが出ましたね。
黄色い文字を確認すると、“Maximum call stack size exceeded”と書かれてあります。
この意味は「最大コールスタックサイズを超えてしまいました」というものなのですが、まあ意味わかりませんよね。
ゲーム画面のエラーログを見てみると、なにやら同じ行数が何度も表示されていることがわかると思います。
デベロッパツールでも同じです。

なにが起きてんねんと言えば、関数(メソッド)上書きするためにvar _Scene_Menu_create = Scene_Menu.prototype.create;と関数の内容を一時的に確保しているのですが、その後でまた全く同じ変数名で関数の内容を確保してしまっているのが原因です。
公式ヘルプには「動作を変更したいメソッドを、必要に応じてローカル変数に保存した後、再定義します。」とあります。
実際、これはツクールでプラグインを作成するときのイディオムとなっている節があります
これはこれでいいのですが、ローカル変数名同士が衝突すると、先ほどのようなエラーが出てしまいます。

回避方法は色々とありますが、今回の場合だと同じ関数を二回も再定義しないことで解決するでしょう。

注釈
場合によっては別の変数に関数を保存したり、即時関数で囲ってやったりします。
が、プラグインの改変に慣れていない方が一番ひっかかるのが、何度も関数の再定義をしてしまうことだと僕は考えています。
一つのプラグインは即時関数で囲み、関数の再定義は一度までというルールを決めておけば今回のようなエラーは回避できるでしょう。
また、”Maximum call stack size exceeded”は再帰関数を使う場合にもよく起きますが、再帰関数をバリバリに使うような人はエラーを適切に読めるでしょうから、ここでは無視します。

Cannot read property ‘Symbol(Symbol.iterator)’ of null

お次は以下のコードに書き換えてください。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);

var array = null;
for(var value of array){
alert(value);
}
};

例のごとく起動し、メニュー画面を開くと“Cannot read property ‘Symbol(Symbol.iterator)’ of null”というエラーが表示されたかなと思います。
このエラーの意味は、「中身が空のSymbol(Symbol.iterator)プロパティは読み込めません」ということです。はい、意味わかりませんね。

何が原因かと言えば、for-ofは列挙可能なオブジェクト(iterableオブジェクト)にしか使えないのですが、今回はnullという値に対して使おうとしています(var array = null;)。
これがエラーの原因です。

iterableオブジェクトが何かということを話すと長くなるので省略します。
代わりに、このエラーが発生する可能性がある場合について書きます。

まずいきなり大前提を崩しちゃいますが、通常プラグイン作者は上記のようなnull代入したものをfor-ofで回しちゃうような書き方はしません
仮にやったとしても、普通はデバッグ時に気が付きます。つまり公開済みのプラグインでnullをfor-ofで使用することなことはしません
あくまでも上記のコードはエラーを起こすための簡単な例です。

「でも似たようなエラー起きたことがあるよ」と思う方がいるかもしれませんね。
原因をご説明しましょう。

例えばプラグインパラメーターで「いくつでも数値を登録できます」的なものがあったとしましょう。
あなたは何も登録しなかったとします。
このとき、そのプラグインパラメーターの値はnullです(正確には、プラグインで空の値をどう扱うかによります。undefinedかもしれませんし、”0″かもしれません)。
そしてそのnullが設定されているパラメーターがfor-ofで回されると、エラーが発生します!

つまり、プラグイン制作者は値が空の場合を想定していなかったのです。
この場合、nullチェックを事前におこなえば問題なく動作することが大半です。
nullチェックとは例えば以下のような感じです。

if(array == null){
return;
}

ただ、先ほども少し触れたように、空の値をプラグイン内でどう扱っているかはプラグインによって異なります。
また、nullチェックをおこなうことで別のエラーが発生する場合もあります。
よくわからない場合はプラグイン制作者に「こんなバグがありましたよ」と報告してみるのがよいでしょう。

変数のスコープに気をつけよう

残りはその他のよくあるエラーを少しだけ書こうと思います。
この項目以降の対象は自分でプラグインを書こうと思っている人です。

まずは変数のスコープです。
スコープそのものについての説明はしませんので、スコープという言葉がわからない場合はググるか、飛ばしてください。

JavaScriptのスコープはやや特殊です。
例えば以下のコードを実行してみてください(例によってメニュー画面を起動します)。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);
for(var i = 0; i < 10; i++){ //何もしない } console.log(i); };

実行結果はどうなると思いますか?
他の言語経験者なら、「for文のスコープを通り過ぎたら変数iは消滅する。したがってiはundefinedであり、エラーが表示されるはずだ!」と思うでしょうか?
しかし、実際には10が表示されます。

JavaScriptのスコープは以下の三つがあります(ここではletを考慮しない)。

・グローバルスコープ
・関数スコープ
・evalスコープ

つまり、forループ内で宣言された宣言された変数iは、forループが終了したあとも生きているのです。
言い換えれば、varで宣言された変数はブロックスコープを持たないのです。
これは初見殺しの仕様だと思っているのですが、慣れたらまあ何でもない(若干不便ですが)ことなので、慣れていきましょう!

なお、letで変数を宣言すればブロックスコープを持つことができます。
が、コアスクリプトでletを使っているコードをあまり見ないので、僕はvarで統一しています(この辺は好みかなあ?)。

thisの参照先に気をつける

「バカな! 確かに定義したのに、なぜundefinedなのだ!」
プラグインを制作していると、こんなことを一度は経験すると思います(僕だけ?w)。
この不可解なエラーの原因のほとんどは、僕の場合はthisの参照先がおかしいことでした。

例えば以下のようなコードの実行結果を想像してください。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);
this.hello = 'hello';
var hoge = function(){
console.log(this.hello);
}();
};

コンソール画面には何が表示されると思いますか?
「そんなの決まっている。helloだ!」と思った方、残念です。
エラーが出て終わります、これ。

原因は、this.helloのthisがScene_Menuクラスのインスタンスを指しているのではなく、全然関係ないものを指しているからです。
確かめる方法は簡単です。
hoge関数(実際は無名関数)の中でconsole.log(this);とでもしてください。
このコードをきちんと実行させるには、以下のようにthisが参照するオブジェクト(の参照)を適切にセットします。

var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);
this.hello = 'hello';
var hoge = function(){
console.log(this);
console.log(this.hello);
}.call(this);
};

コンソール画面にthisの値がきちんと表示されていますね。
また、this.helloも想定したとおりに表示されています。

と、ちょっとややこしくなりましたが、肝要なのはちゃんと定義したのにエラーが出る場合、thisの参照先を疑ってみることです。

終わりに

いかがでしたでしょうか。

少しコアな内容も扱いましたが、なんだかんだ言って僕もまだプログラミング歴半年です。
ペーペーです。
間違ったことを平気で書いている場合もあるので、その点ご了承ください。
とはいえ、特に前半部分はプラグイン制作者以外にも役立つ情報かなと思っています。

最後に……。
多くの場合、エラーは有益な情報源となります。
慌てず騒がずきちんと読み解くことさえできれば全く怖いものではありません。
一人でも多くの人がエラーに対して適切に対処できるようになり、エラーアレルギー(今考えた造語です)から解放されることを祈っています。

と、それらしいことを言ったところで終わります。
ほな、また。

追記:補足記事的なもの書きました。

フォローする