まず結論
Godotで使われるResourceは、Local to Sceneを適切に設定しないと、Materialなどのインスタンスは使い回されるので注意。
概要
Godotを使っていると自然に気づくことなのだけれど、Materialなど、各ノードのパラメータ等で指定するResource
は、何もしなければ別のノードでも使い回される。
↑Resourceの見分け方は、「> Resource」がついているのが特徴。
例えば、ある作成済みのノードをコピーして、元のノードのマテリアルを編集すると、新しいノードでもその値が使われる。つまり、いわゆるシャローコピー (Shallow Copy) の状態になっている。
これを避ける簡単な方法として、「ユニーク化」がある。
ユニーク化すれば、別のリソースとなるので、別のノードで別のパラメータなどを指定しても、リンクすることはない。
これで万事解決か?と思っていると、実は同じノードをPrefab的に、何度もリスポーンさせると、リソースの使い回しが発生する。
検証用コード
検証のために、以下のような簡単なプロジェクトを作ってみる。(執筆時点でGodot 4.3にて検証。)
まずメインシーンとなる test_scene.tscn
は以下の通り。
ここで TestSceneController
(test_scene_controller.gd
) は以下の通り。
class_name TestSceneController
extends Node
@export var vw: int = 640
@export var vh: int = 480
@onready var scene_root: Node2D = self.get_parent()
var is_first_time = true
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _spawn_obj() -> void:
var res: Resource = load("res://test_node.tscn")
var obj: Sprite2D = res.instantiate()
obj.position = Vector2(randf_range(0, vw), randf_range(0, vh))
if is_first_time:
var mat: CanvasItemMaterial = obj.material as CanvasItemMaterial
mat.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD
is_first_time = false
scene_root.add_child(obj)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey:
if event.is_pressed():
if event.keycode == KEY_A:
_spawn_obj()
このコードは、A
キーが押されるたびにランダムな位置にTestNode
を作る。ただし、初回だけマテリアルのブレンディングをADDにする。
ちなみにTestNode
(test_node.gd
) は、以下のようなシンプルなSprite2Dで、CanvasItemMaterial
がマテリアルに設定してある。
検証用コードの実行
さて、このコードを素朴に実行すると、次のようになる。
初回だけにブレンディングをADDに変更したにもかかわらず、リソースが使い回された結果、すべてに適用されてしまっている。
しかも、これらのリソースはコピーされたわけではなく同じインスタンスを指しているので、一つの値を変えると残り全ても変わってしまう。
Local to Sceneの有効化
では次に、Local to Sceneをオンにした上でもう一度実行する。
今度は、ちゃんと初回だけがADDになって、2回目以降は通常のブレンディングになっている。
ちなみに Local to Scene が何を意味するかはドキュメントに書いてあり、今回の目的通り、各インスタンスが同じものを指すのではなくちゃんとコピーしたい場合にtrue
にするとある。
(これを再帰的にやってくれるオプションがあれば、さらに子のリソースもディープコピー (Deep Copy) されて便利な気もするけど、そういうオプションってあるのだろうか…?)
まとめ
今回はマテリアルを例として取り上げたものの、すべてのResource
について同じことがいえるので、例えば動画・音声プレイヤーなどで再生対象のファイルを動的に変えたりするようなシチュエーションでは、注意が必要。
特に、ゲームの2回目の実行時は、一度使われたものを再度リスポーンすることが多いので、これにハマってしまうケースがあるので要注意。
他の言語のシャローコピーやディープコピーと同じく、どちらが良いかはケースバイケースなので、例えば今回のようにコピーするのではなく、ready()
のときに必要なパラメータをリセットするコードを呼ぶ、などとの使い分けが必要。