【Godot】GDScriptのエラー: The class "ClassName" couldn't be fully loaded (script error or cyclic dependency). について

概要

GDScriptを書いているとよく見る「The class "ClassName" couldn't be fully loaded (script error or cyclic dependency).」について解説します。

エラーを調べる

GDScriptでスクリプトから別のスクリプト(のクラス)を参照するときに、発生することがあるエラーです。

エラー文の括弧内にもある通り、以下の2つが原因の可能性が高いです。

  1. Script error (スクリプトエラー)
  2. Cyclic dependency (循環依存エラー)

スクリプトエラー?

例えばScriptAからScriptBを参照しているとき、ScriptBで何かエラーが起きています。 そっちを直すと解決することがあります。

循環参照エラー?

こちらが本題です。

以下の2つのスクリプトを書くとどちらかで cyclic dependencyエラーが発生します。

  • ScriptA.gd
class_name ScriptA

func _ready():
    var b = get_node("/Root/ScriptB") as ScriptB
    b.method_b()

func method_a():
    print("method_a")
  • ScriptB.gd
class_name ScriptB

func _ready():
    var a = get_node("/Root/ScriptA") as ScriptA
    a.method_a()

func method_b():
    print("method_b")

GodotはGCではなく参照カウンタ方式のメモリ管理なので、循環参照するのはよろしくないです。でもクラスメンバに持つだけでなく、メソッド内で一時的に循環参照するのもダメなのはどうしてかなと。

原因

エラーの原因はどうも循環参照ではなく、スクリプトのロード順の問題とのことらしいです。

C/C++でヘッダ同士がお互いをインクルードしてはいけないやつに似ていますが…。

解決方法

片方のスクリプトから型情報を消します。

class_name ScriptB

func _ready():
    var a = get_node("/Root/ScriptA")
    a.method_a()

func method_b():
    print("method_b")

えって思うかもしれませんが、GDScriptは基本的に動的型付け言語なので問題は無いです。

このようにすると型の解決がパース時ではなく実行時になるため、スクリプトのロード順の問題は起きません。

型情報を失うと型チェックやインテリセンスが使えなくなるのでちょっと…ってなるのですが、現状ではこうするしかないようです。

C/C++では前方宣言で解決できるのですが、GDScriptにそのような仕組みは無いようですね…

Godot4では解決?

Godot4ではその辺考えなくてもよくなるとの噂を聞いたのですが、果たしてどうなるのでしょうか 今後にGodot Engineに期待しましょう。