ノードパスを理解しよう

ℹ️ 留意事項

この記事は Godot 3から Godot 4 へ内容の書き換え中です。 Godot4では存在しない変数、関数が含まれている場合があります。もしその場合はリポジトリのIssuesまでご報告ください。

課題

「無効なノード参照」、これはGodotヘルプチャンネルで最も頻繁に報告される問題の一つです。ほとんどの場合、以下のようなエラーメッセージとして表示されます。

Invalid get index ‘position’ (on base: ’null instance’).

解決策

問題の核心は「nullインスタンス」部分にあり、これがGodot初心者にとって最も混乱を招く要因となっています。

この問題を回避するには、「ノードパス」の仕組みを理解することが重要です。

ノードパスの理解

シーンツリーはノードで構成されており、これらは親子関係によって接続されています。ノードパスとは、このツリー構造を辿りながらあるノードから別のノードへ移動する際に通る経路のことです。

例として、シンプルな「プレイヤー」シーンを考えてみます。

alt alt

このシーンのスクリプトは Player ノードに実装されています。もしスクリプトが AnimatedSprite ノードに対して play() メソッドを呼び出す必要がある場合、そのノードへの参照が必要となります。

get_node("AnimatedSprite").play()

get_node() 関数の引数は、対象ノードへのパスを表す文字列です。ここではスクリプト実行中のノードの子要素を指定します。指定したパスが無効な場合、厄介な null instance エラーが発生するほか(さらに「ノードが見つかりませんでした」というメッセージも表示されます)。

ノード参照を get_node() で取得する状況は非常に頻繁にあるため、GDScript にはそのためのショートカットが用意されています。

$AnimatedSprite.play()
情報

get_node() 関数は、対象ノードへの 参照 を返します。

ではここで、より複雑なシーンツリーを見てみてください。

alt alt

もし Main スクリプトが ScoreLabel にアクセスする必要がある場合、以下のパスを使用してアクセスできます。

get_node("HUD/ScoreLabel").text = "0"
# or using the shortcut:
$HUD/ScoreLabel.text = "0"
ヒント

ドル記号($)を使用した場合、Godotエディタがパスを自動補完します。またシーンタブでノードを右クリックし、「ノードのパスをコピー」を選択する方法もあります。

ノードへのアクセス先がツリー階層の上位にある場合はどうすればよいでしょうか?その場合は get_parent() 関数を使うか、パス指定に "../" を使用することで親ノードを参照できます。上記の例で、ScoreLabel から Player ノードを取得するには。

get_node("../../Player")

分解して説明します。パス「"../../Player"」は、次のように解釈されます。

  1. まず一つ上の階層にあるノード(HUD)を取得します
  2. さらにもう一段階上の階層に移動し(Main)、その中から子ノードであるPlayerを特定します
ヒント

見覚えがありますか? ノードパスの仕組みは、オペレーティングシステムのディレクトリパスと完全に同じです。スラッシュ/は 親と子 の関係を示し、..は「1つ上の階層に移動」の意味です。

相対パスと絶対パスの違い

上記の例はすべて相対パスを使用しています。これは現在のノードを起点として、目的地までの経路をたどる形式です。ノードへのパスは絶対パス形式で指定することも可能で、この場合シーンのルートノードを基点とします。

例えば、プレイヤーノードが絶対パスで指定されている場合は。

get_node("/root/Main/Player")

/rootget_tree().root を介してもアクセスできますが、これはシーンのルートノードではありません。これはデフォルトで常に SceneTree に存在するビューポートノードです。

注意事項

上記の例は問題なく動作しますが、後々問題を引き起こす可能性のある注意点があります。以下のような状況を想像してみてください。Playerノードにはhealthプロパティがあり、これをUI内のどこかにあるHealthBarノードに表示したいとします。プレイヤースクリプトに次のように書いたとします。

func take_damage(amount):
    health -= amount
    get_node("../Main/UI/HealthBar").text = str(health)

初期段階では問題なく機能するかもしれませんが、これは非常に「脆弱」な方式であり、簡単に破綻する可能性があることを意味します。この種の構成には主に2つの重大な問題があります。

  1. プレイヤーシーン単体でのテストは不可能です。プレイヤーシーンを単独で実行した場合や、UIを持たないテストシーンで使用した場合、get_node() 行が原因でクラッシュが発生します。
  2. UIの変更はできません。UIのレイアウトを変更するか設計を見直すことにした場合は、パスが無効になるため、必ず修正してください。

この理由から、シーンツリーの 上位 方向へノードパスを指定する操作は避けるべきです。前述の状況では、プレイヤーのHPが変化した時にシグナルを発行するように変更すれば、UIコンポーネントはそのシグナルを受け取って自身の状態を更新できます。この方法なら、ゲームのソースコードに影響を与えることなくノードを自由に再配置・分離することが可能になります。

まとめ

ノードパスの使い方をマスターすれば、必要なノードを簡単に参照できるようになります。慣れればnull instanceエラーメッセージに悩まされることもなくなります。