Cannnot read property ‘setTransparent’ of undefinedはなぜ起こる?

エラー情報の見方の補足記事です。
今回はツクール公式フォーラムの以下のスレッドを対象とします。

【MVスクリプト質問】setTransparentについて

Cannnot read property ‘setTransparent’ of undefinedの意味

質問者さんは、以下のようにおっしゃっています。

起動後にTypeErrorとなり Cannnot read property ‘setTransparent’ of undefined と表示されてしまいます。

では、この英語の一文の意味はなんでしょうか?
簡単に訳してみます。

未定義のsetTransparentを読み取ることができません。

はて、どういうことでしょう。
詳しく見ていきます。

this.character(i)の返すもの

元コードのthis.character(i)が怪しさプンプンですよね(質問者さんのコードのやつです)。
このメソッドの実装を見てみましょう。

Game_Interpreter.prototype.character = function(param) {
if ($gameParty.inBattle()) {
return null;
} else if (param < 0) { return $gamePlayer; } else if (this.isOnCurrentMap()) { return $gameMap.event(param > 0 ? param : this._eventId);
} else {
return null;
}
};

おそらく質問者さんはマップ上でのイベントを想定していますから、注目すべきはelse if (this.isOnCurrentMap())の部分です。
どうやら$gameMap.eventメソッドを呼んでいるようですね。
このメソッドの実装は以下のようになっています。

Game_Map.prototype.event = function(eventId) {
return this._events[eventId];
};

つまりマップ上のイベントオブジェクトを返しているのですね。

undefinedが返される理由

上記のコードを見てわかる通り、this.character(i)はただマップイベントのオブジェクトを返すだけです。
マップイベントが存在するか存在しないかのチェックは入りません。
つまり、eventId(ここではi)が存在しないイベントを指定していたとしても、エラーは吐きません。
代わりにundefinedすなわち未定義を返すだけです。

エラーが出る理由

エラー情報の見方でも書いたように、このundefinedはプリミティブ型であってオブジェクトではありません。
言い換えると、プロパティを持つことができません。

注*ひょっとすると無理やり持たせることもできるかもしれませんが……。普通は持たせません。

ここまで理解できたところで、先ほどのエラーをもう一度見てみましょう。
「Cannnot read property ‘setTransparent’ of undefined」が原文です。
僕は「未定義のsetTransparentを読み取ることができません」と訳しましたが、より原文に近い訳にしてみましょう。
「undefinedのsetTransparentプロパティを読み取ることができません」

ワオ!
まさにエラー情報は正しくエラーを伝えてくれていました!

まとめ

まとめます。
「Cannnot read property ‘setTransparent’ of undefined」が出たのは、マップ上に存在しないイベントにアクセスしようとしたからです(this.character(i)←このコードのiがイベント番号だと思ってください)。
このiの部分はマップ上に存在するイベントの数以内である必要があります。
そうでなければ、this.character(i)は存在しないものにアクセスしようと試み、結果としてundefinedという値を返します。
このundefinedという値にはsetTransparentというメソッドがないため、「Cannnot read property ‘setTransparent’ of undefined」というエラーすなわち「未定義のsetTransparentを読み取ることができません」が出てしまいます。

以上のことを言い換えれば、iの値がマップ上に存在するイベントの数以内であれば何の問題もないわけです。
あとは僕が回答として示したコードの通りです。
一つ目のコードは、そもそも存在する分だけしかループを回さない方法です。

var mapId = $gameMap.mapId();
var events = $gameMap.events();
events.forEach(function(event, index) {
var keyA = [mapId, index, 'A'];
var keyB = [mapId, index, 'B'];
$gameSelfSwitches.setValue(keyA, false);
$gameSelfSwitches.setValue(keyB, false);
event.setTransparent(false);
});

二つ目は、ループさせる回数は固定にする代わり、ループの中でthis.character(i)が定義されているかどうかをチェックしています。

var mapId = $gameMap.mapId();
for(var i = 0; i <= 400; i++) { var keyA = [mapId, i, 'A']; var keyB = [mapId, i, 'B']; if(this.character(i)){ $gameSelfSwitches.setValue(keyA, false); $gameSelfSwitches.setValue(keyB, false); this.character(i).setTransparent(false); } }

このチェックにより、undefinedな値に対して「setTransparentしてくれ!」とお願いすることがなくなります。
つまりエラーが解消されます。

以上、公式フォーラムであった実際の事例について少し詳しめに解説してみました。
少しでもお役に立てれば幸いです。

ほな、また。

追記:
以下細かい話です。
興味ない方はスルーしてください。

JavaScriptにおいてプリミティブ型undefinedは特別なオンリーワンの値です。
また、undefinedはundefinedであり、それ以上でも以下でもありません。
それはちょうど、1という数字が1という数字以上でも以下でもないことと同列です(なお、ボックス化により1という数字はオブジェクトのように振る舞うこともできます)。
したがってエラー内容を「未定義」と訳すとかえってわかりにくい気がします(なぜならundefinedの訳は「未定義」であってもundefinedは定義されているから)。
エラー情報にundefinedという文字が含まれていた場合、「undefinedというオンリーワンの値に対して何か働きかけ、その結果エラーが出たのだ」と考える方が僕はしっくりきます。
つまり僕はundefinedをundefinedと訳したい(訳してねーよって?w)。

あ、この追記は僕が勝手に考えてることなので、間違ってるかもしれません。
プロJavaScripterの解説を待たれよ!(ググったら出るかも)

フォローする