GDScriptでネームスペースっぽい使い方をする
概説
独自のクラスを沢山実装していると、クラス名の衝突などが心配になることがあるかと思います。そんな時のために、GDScriptでネームスペースっぽいものを実現してみる実験です[1][2]。
実装例
以下の例では、NameSpaceAとNameSpaceBというクラスを実装します。どちらにもChildという名前の定数を宣言し、スクリプトファイルを読み込みます。NameSpaceA, Bの違いはクラス名と、参照しているスクリプトファイルのみです[3]。
extends Node
class_name NameSpaceA
const Child = preload("res://namespace_a_child.gd") # A用のスクリプトを読み込む
extends Node
class_name NameSpaceB
const Child = preload("res://namespace_b_child.gd") # B用のスクリプトを読み込む
NameSpaceA, Bがそれぞれ参照しているスクリプトは以下の通りです。ログを出力するtest()関数を宣言しただけのすごく単純なものになっています。AとBでは出力される文字列だけが違うことがわかると思います。
extends Node # A用のスクリプト
func test():
print("foo")
extends Node # B用のスクリプト
func test():
print("bar")
動かしてみる
実際に動かしてみましょう。以下のノードは読み込まれると、NameSpaceA, Bの両方のChildを初期化し、test()関数を実行します。
extends Node
func _ready():
var a = NameSpaceA.Child.new()
a.test() # "foo"と出力されます
var b = NameSpaceB.Child.new()
b.test() # "bar"と出力されます
ちゃんと動きました。
static関数を使ってみる
読込先に変数を保持しないのであれば、関数をstaticにしてみても良さそうです。
# 上述のA用のスクリプトの関数をstaticにしたもの
# B用のスクリプトは割愛します
extends Node
static func test():
print("foo")
これを動かしてみます。
extends Node
func _ready():
var a = NameSpaceA.Child.test() # "foo"と出力されます
var b = NameSpaceB.Child.test() # "bar"と出力されます
こちらもちゃんと動きました。
内部クラスでもできる
定数 + スクリプトファイルではなく、内部クラス(インナークラス / サブクラス)でも同じように実装ができます。以下のコードでは、冒頭で示したNameSpaceA & 参照先のスクリプトと全く同じ処理を、内部クラスを使って実装しています。
単純な実装ならば、スクリプトファイルが別れずに済むし内部クラスを使う方がいいかもしれません。定数を使った実装は、フォルダ構造の変更に弱いというデメリットもあります。また、定数でのスクリプト読み込みはloadが使えず、preloadに限定されるため、スクリプトを読み込む順番次第ではエラーが出る可能性もあります。
extends Node
class_name NameSpaceA
class Child extends Node: # インナークラスを宣言
func test():
print("foo")
おわり
今すぐ役に立つのかと言われれば微妙かもしれませんが、クラス名が衝突しそうな状況では使えそうです。ちなみに、Godot 4.0時点では定数と内部クラスのどちらを使った実装でもコード補完も動きます。
-
一応ネームスペース機能のリクエストは出ていますが、活発に議論されている様子はなく
4.0時点では実装される予定はないです。https://github.com/godotengine/godot-proposals/issues/1566 ↩︎ -
重複しないクラス名を付ければいいだけですが…。 ↩︎
-
ちなみにGodotのスタイルガイドでは、
loadやpreloadでスクリプトを読み込む際には(変数や)定数名をPascalCaseで宣言することとなっています。それ以外の場合、定数はCONSTANT_CASEです。 ↩︎
Discussion