マガジン

「Uncaught ReferenceError」が出たゲームの直し方

遊びをつくる

執筆にClaudeを使用しています。

壊れたビー玉コースを四枚の記録カードと虫眼鏡を使って調べ、修理する手元
記事のテーマをもとに制作したAI生成イラスト。

ブラウザのコンソールに Uncaught ReferenceError: ○○ is not defined と表示されたとき、ゲームは動きを止めます。画面が真っ白になる、キャラクターが動かない、スコアが更新されない——症状はさまざまですが、原因の構造は共通しています。「存在しない名前を呼び出した」という一点です。 ReferenceError は「名前の解決に失敗した」エラーです。JavaScriptエンジンが変数・関数・クラスの名前を探したけれど、どこにも見つからなかったときに投げられます。TypoError(型の誤用)や SyntaxError(文法ミス)とは異なり、コードの構文は正しくても、実行時に名前が存在しないと発生します。この性質を理解しておくと、どこを調べるべきかの見当が早くつきます。 この記事では、ゲーム制作でよく起きるパターンに絞って「なぜ起きるか」「どこを直すか」「再発を防ぐには」を順番に説明します。

この記事の目次
  1. エラーメッセージから読み取れること
  2. ReferenceError が起きる四つのパターン
  3. 原因の特定を速くする手順
  4. 再発を防ぐ書き方の習慣
  5. 次の行動

エラーメッセージから読み取れること

コンソールに表示されるメッセージの構造は次のようになっています。

Uncaught ReferenceError: score is not defined
    at updateScore (game.js:42:5)
    at game.js:78:3

最初の行が「何という名前が見つからなかったか」、その下のスタックトレースが「どのファイルの何行目で呼ばれたか」を示しています。まず score is not defined の score という名前と、game.js:42 という行番号を手元にメモします。この二つを持ってコードを開けば、調査の出発点が決まります。

ブラウザの開発者ツール(F12キーまたは右クリック→「検証」)を開き、Consoleタブを確認してください。エラーの行番号リンクをクリックすると、Sourcesタブで該当箇所が直接開きます。これがもっとも速い確認方法です。

ReferenceError が起きる四つのパターン

パターン1:単純なスペルミス

もっとも多い原因です。score と書くべきところを Score や scroe と書いていると、JavaScriptは別の名前として扱います。変数名は大文字・小文字を区別するため、Player と player は異なる名前です。

確認方法は、エラーに出た名前でコード全体を検索し、定義(let・const・var・function)と呼び出しのスペルを見比べることです。エディタの「すべて検索」(VS CodeならCtrl+Shift+F)が便利です。

パターン2:スコープの外から呼んでいる

let や const で宣言した変数は、宣言したブロック({})の外から読めません。次のケースがゲームでよく起きます。

javascript
function startGame() {
  let playerHP = 100;
}

function takeDamage() {
  playerHP -= 10; // ReferenceError: playerHP is not defined
}

playerHP は startGame 関数の中だけに存在するため、takeDamage からは見えません。複数の関数で共有したい状態は、関数の外側(モジュールのトップレベル)で宣言する必要があります。

javascript
let playerHP = 100; // 外に出す

function startGame() { playerHP = 100; }
function takeDamage() { playerHP -= 10; }

ゲームの「状態」を管理する変数(HP・スコア・ステージ番号など)は、ファイルの先頭近くにまとめて宣言しておくと、このミスを防ぎやすくなります。

パターン3:読み込み順の問題

HTMLファイルで複数の <script> タグを使っているとき、後から読み込まれるスクリプトで定義した関数を、先に読み込まれるスクリプトから呼ぼうとすると ReferenceError になります。

html
<script src="main.js"></script>   <!-- ここで setupEnemy() を呼ぶ -->
<script src="enemy.js"></script>  <!-- setupEnemy はここで定義 -->

この場合は enemy.js を先に書くか、main.js 側の呼び出しをページ読み込み完了後(DOMContentLoaded イベントや window.onload)に遅らせることで解決します。

ZIPファイルで複数のJSファイルをまとめている場合も同じ原則が当てはまります。HTMLの <script> タグの順番を依存関係に合わせて並べ替えてください。

パターン4:非同期処理のタイミングずれ

fetch や loadImage などの非同期処理でデータを取得し、コールバックや then の外で使おうとすると、変数がまだ代入されていない状態で参照することになります。厳密には undefined が返る場合が多いですが、const で宣言した変数を後から代入しようとしていたり、宣言自体が非同期ブロック内にある場合は ReferenceError になります。

javascript
async function loadAssets() {
  const imageData = await fetch('sprites.json').then(r => r.json());
}

drawSprite(imageData); // ReferenceError: imageData is not defined

非同期処理で得たデータは、その処理が完了した後に実行される場所(await の後、または .then() の中)でしか使えません。ゲームの初期化ロジックを async 関数にまとめ、await で順序を制御するのが現実的な対処です。

原因の特定を速くする手順

  1. コンソールのエラーメッセージから「見つからなかった名前」と「行番号」を確認する
  2. その行番号でコードを開き、問題の名前がどう書かれているかを目視する
  3. その名前が定義されている場所をエディタで全文検索する
  4. 定義が見つかった場合は「スコープ」と「読み込み順」を確認する
  5. 定義が見つからない場合はスペルミスか、定義し忘れを疑う

この順で調べると、ほとんどのケースは5分以内に原因が見つかります。「なんとなくコードを眺める」より、エラーメッセージの情報を入口にして絞り込む方が確実です。

再発を防ぐ書き方の習慣

var の代わりに let と const を使うことを基本にしてください。var はスコープが関数単位でブロックを無視するため、意図しない場所から参照できてしまい、バグの発見が遅れます。let と const はブロックスコープなので、使える範囲が明確になり、ReferenceError が早い段階で出て気づきやすくなります。

ESLint などの静的解析ツールをエディタに導入すると、未定義の変数を使っている箇所を保存時に警告してくれます。ゲームの規模が大きくなるほど、実行前に問題を発見できる仕組みの価値が上がります。

また、ゲームの状態変数を一つのオブジェクトにまとめる書き方も有効です。

javascript
const gameState = {
  playerHP: 100,
  score: 0,
  stage: 1,
};

こうするとどこからでも gameState.score のようにアクセスでき、スコープの問題が起きにくくなります。名前も gameState という一つの変数に集約されるため、スペルミスが起きる箇所を減らせます。

次の行動

ブラウザのコンソール(F12)を開いた状態でゲームを動かし、ReferenceError が出ている行番号に直接ジャンプして確認してみてください。エラーの名前と行番号を手がかりに、この記事の四つのパターンと照らし合わせると原因が特定できます。修正したゲームをアソバースに投稿する際は、別ブラウザや別タブでも動作確認をしてから公開すると、読者への影響を最小限にできます。