プレイヤーがHPを持ち、HUDがHPバーを表示し、インベントリがアイテムを使い、アイテムがプ レイヤーを回復する。ここまでは自然です。
しかし実装しているうちに、Player が HUD を直接触り、HUD も Player を参照し、ItemData が Player を覚え、Inventory も ItemData を持つ、という状態になりがちです。動いている間は問題に見えませんが、UIを差し替えた瞬間に落ちる、別シーンでは参照が見つからない、どこでHPが変わったか追えない、という形で苦しくなります。
この記事では、循環参照 と 依存関係の絡まり を分けて整理します。メモリ管理の話だけでなく、初心者〜中級者が実際のゲーム制作で詰まりやすい「どのノードがどのノードを知っていてよいのか」を中心に扱います。
この記事でわかること
- 循環参照と循環依存の違い
NodeとRefCountedで問題の出方が違う理由- 直接呼び出し、シグナル、Autoload、WeakRefの使い分け
- アイテム、HUD、ステータス効果を安全に設計する考え方
まず「参照」と「依存」を分ける
参照 は、あるオブジェクトが別のオブジェクトを変数として持っている状態です。
var player: Player
依存 は、あるコードが別のコードの存在やメソッド名を知っている状態です。
player.heal(20)
循環が問題になるのは、単に変数があるからではありません。AがBを知り、BもAを知り、さらに互いのメソッドを呼び合うようになると、変更に弱くなります。
Player -> HUD
HUD -> Player
この形だと、Player は HUD の存在を前提にします。HUDがないテストシーンや、ボス戦専用UIに差し替えたシーンで壊れやすくなります。さらにHUD側もPlayerを前提にしているため、「どちらを先に用意するか」という初期化順の問題も出ます。
依存関係を整理するときは、まず次の問いを置きます。
このオブジェクトは、本当に相手の存在を知る必要があるのか?
NodeとRefCountedで何が違うのか
循環参照の話で混乱しやすいのは、Godotのオブジェクトがすべて同じ仕組みで解放されるわけではない点です。
| 種類 | 例 | 解放の考え方 | 循環で起きやすい問題 |
|---|---|---|---|
Node 系 | CharacterBody2D, Control, Node2D | シーンツリーから外す、queue_free() する | 参照切れ、初期化順、強い結合 |
RefCounted 系 | Resource, 独自の計算用クラス | 参照されなくなると解放される | 相互参照で解放されにくくなる |
言葉だけだとイメージしにくいので、2つの「解放のされ方」と「循環で起きる問題」を並べてみます。
