1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Godot] Resourceインスタンスの使いまわしに要注意(Local to Sceneの使い方)

Last updated at Posted at 2024-10-14

まず結論

Godotで使われるResourceは、Local to Sceneを適切に設定しないと、Materialなどのインスタンスは使い回されるので注意。

スクリーンショット 2024-10-14 21.41.56.png

概要

Godotを使っていると自然に気づくことなのだけれど、Materialなど、各ノードのパラメータ等で指定するResourceは、何もしなければ別のノードでも使い回される。

スクリーンショット 2024-10-14 21.47.15.png

↑Resourceの見分け方は、「> Resource」がついているのが特徴。

例えば、ある作成済みのノードをコピーして、元のノードのマテリアルを編集すると、新しいノードでもその値が使われる。つまり、いわゆるシャローコピー (Shallow Copy) の状態になっている。

これを避ける簡単な方法として、「ユニーク化」がある。

スクリーンショット 2024-10-14 21.50.25.png

ユニーク化すれば、別のリソースとなるので、別のノードで別のパラメータなどを指定しても、リンクすることはない。

これで万事解決か?と思っていると、実は同じノードをPrefab的に、何度もリスポーンさせると、リソースの使い回しが発生する

検証用コード

検証のために、以下のような簡単なプロジェクトを作ってみる。(執筆時点でGodot 4.3にて検証。)

スクリーンショット 2024-10-14 21.40.41.png

まずメインシーンとなる test_scene.tscn は以下の通り。

スクリーンショット 2024-10-14 21.40.10.png

ここで 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がマテリアルに設定してある。

スクリーンショット 2024-10-14 21.40.28.png

スクリーンショット 2024-10-14 21.40.24.png

スクリーンショット 2024-10-14 22.01.13.png

検証用コードの実行

さて、このコードを素朴に実行すると、次のようになる。

スクリーンショット 2024-10-14 22.02.07.png

初回だけにブレンディングをADDに変更したにもかかわらず、リソースが使い回された結果、すべてに適用されてしまっている。

しかも、これらのリソースはコピーされたわけではなく同じインスタンスを指しているので、一つの値を変えると残り全ても変わってしまう。

Local to Sceneの有効化

では次に、Local to Sceneをオンにした上でもう一度実行する。

スクリーンショット 2024-10-14 22.02.18.png

スクリーンショット 2024-10-14 22.02.40.png

今度は、ちゃんと初回だけがADDになって、2回目以降は通常のブレンディングになっている。

ちなみに Local to Scene が何を意味するかはドキュメントに書いてあり、今回の目的通り、各インスタンスが同じものを指すのではなくちゃんとコピーしたい場合にtrueにするとある。

スクリーンショット 2024-10-14 21.41.56.png

(これを再帰的にやってくれるオプションがあれば、さらに子のリソースもディープコピー (Deep Copy) されて便利な気もするけど、そういうオプションってあるのだろうか…?)

まとめ

今回はマテリアルを例として取り上げたものの、すべてのResourceについて同じことがいえるので、例えば動画・音声プレイヤーなどで再生対象のファイルを動的に変えたりするようなシチュエーションでは、注意が必要。

特に、ゲームの2回目の実行時は、一度使われたものを再度リスポーンすることが多いので、これにハマってしまうケースがあるので要注意。

他の言語のシャローコピーやディープコピーと同じく、どちらが良いかはケースバイケースなので、例えば今回のようにコピーするのではなく、ready()のときに必要なパラメータをリセットするコードを呼ぶ、などとの使い分けが必要。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?