• Godot Help
  • What's wrong with these resource references?

I have a CharacterBody2D that has a Resource. That resource has an @export reference to a CharacterBody2D. If I click and drag the original CharacterBody2D into the resource in the inspector, everything appears to work. However any code that I call on that CharacterBody through the resources does nothing.
If I set the reference through code instead of the inspector during the CharacterBody _ready() method, then all calls to the CharacterBody though the resource works perfectly.
I found that if I print() the CharacterBody that was set in the inspector, and then print it again after setting it though code, I end up with two different ID's even though they should, in theory, be the same.
Any insight would get greatly appreciated. Cheers!

class_name ResourceContainer
extends Resource
@export var resource_a: CharacterBody2D

class_name MyCharacter
extends CharacterBody2D
@export var resources: ResourceContainer
func _ready():
print(resources.resource_a) #result: <CharacterBody2D#27128759470>
resources.resource_a = self
print(resources.resource_a) #result: CharacterBody2D:<CharacterBody2D#27229422759>

  • award replied to this.
  • TypicalTom The resource is in fact coming from res://. It's "stored" in the tscn file that defines your scene. When that tscn is loaded at startup, the resource object will be created as well as all of the objects it refers to. So in your case a new CharacterBody2D will be created. It will however be initialized with property values your original character body had because you assigned the original object to the reference held by the resource object. Godot saved those property values in the tscn file. It cannot save the actual object itself though.

    TypicalTom

    Resources in res:// can't refer directly to objects inside of a scene. They can only refer to the root, because that is what gets saved in res:// If you need that direct functionality, try exporting a NodePath and loading that at runtime.

    class_name ResourceContainer
    extends Resource
    @export var path: NodePath

    Thanks for the reply. If I understand what you're saying then I think I need to clarify (or I'm missing something).
    The resource isn't coming from res://, it's a script that extends Resource and is declared as a field in the first node of my scene (CharacterBody2D). That makes it a part of the scene right? So shouldn't I be able to reference other nodes in that scene to that resource though the inspector?

    • xyz replied to this.

      TypicalTom The resource is in fact coming from res://. It's "stored" in the tscn file that defines your scene. When that tscn is loaded at startup, the resource object will be created as well as all of the objects it refers to. So in your case a new CharacterBody2D will be created. It will however be initialized with property values your original character body had because you assigned the original object to the reference held by the resource object. Godot saved those property values in the tscn file. It cannot save the actual object itself though.

        xyz Ooooh interesting. Okay, I think I'm following along. Disappointing I can't do it that way but not the end of the world.
        Thanks for the explanation!

        Resources can live in the scene file rather than res://. Take this example:

        Here I created a TestNode script that holds two TestResource references.
        _res_1 I created by right-clicking the field and selecting New TestResource. _res_1 lives in my scene.
        _res_2 I created by right-clicking in the file system and selecting Create new Resource.... _res_2 lives in res://

        Both TestNode._res_1 and TestNode._res_2 are just references. The data themselves for those resources are actually elsewhere.
        We can see res_1 has a path that points to the scene file. res_2 has a path that points directly to a resource.

        If I open up the scene in a text editor, it looks like this:

        
        [gd_scene load_steps=5 format=3 uid="uid://bkf16rjaesl84"]
        
        [ext_resource type="Script" path="res://test_node.gd" id="1_kmmcv"]
        [ext_resource type="Script" path="res://test_resource.gd" id="2_ups2k"]
        [ext_resource type="Resource" uid="uid://f7nlj7i4unbb" path="res://test_resource.tres" id="3_lnf6o"]
        
        [sub_resource type="Resource" id="Resource_a7l4x"]
        script = ExtResource("2_ups2k")
        target = Object(Node2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"clip_children":0,"light_mask":1,"visibility_layer":1,"z_index":0,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(0, 0),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"script":null)
        
        
        [node name="Node2D" type="Node2D"]
        script = ExtResource("1_kmmcv")
        _res_1 = SubResource("Resource_a7l4x")
        _res_2 = ExtResource("3_lnf6o")
        
        [node name="Node2D" type="Node2D" parent="."]

        We can see that there is an ext_resource which points to our test_resource.tres, which is where the data for that resource lives. Then we can see a sub_resource with info that contains all of our data for that resource right there in our scene file. It's a very long line, for that reason. Then we see our Node with our script, and our _res_1 and _res_2 use SubResource and ExtResource respectively.

        Hopefully this helps understand how even though a Node in a scene can have a reference to a file in res://, that file isn't actually in the scene itself when we save it.

        Edit to mention: Still 99% sure that even the TestResource that lives in the scene isn't pointing to the actual Node2D in our scene, but to a new one it constructs.

        • xyz replied to this.

          award Still 99% sure that even the TestResource that lives in the scene isn't pointing to the actual Node2D in our scene, but to a new one it constructs.

          It is pointing to the assigned object. Its property values are saved to tscn and used to initialize newly created object when project is run.

          You may have misunderstood what I was referring to. Here the assigned TestReference object has its property values saved to tscn and used to initialize a newly created object when the project is run. It ALSO saves all of the values of the Node that the TestReference is pointing to. I've expanded the test a little bit:

          class_name TestNode
          extends Node
          
          @export var _res_1:TestResource
          @export var _res_2:TestResource
          @export var _node_ref:Node
          class_name TestResource
          extends Resource
          
          @export var target:Node



          and the resulting .tscn from all of that

          [gd_scene load_steps=5 format=3 uid="uid://bkf16rjaesl84"]
          
          [ext_resource type="Script" path="res://test_node.gd" id="1_kmmcv"]
          [ext_resource type="Script" path="res://test_resource.gd" id="2_ups2k"]
          [ext_resource type="Resource" uid="uid://f7nlj7i4unbb" path="res://test_resource.tres" id="3_lnf6o"]
          
          [sub_resource type="Resource" id="Resource_a7l4x"]
          script = ExtResource("2_ups2k")
          target = Object(StaticBody2D,"_import_path":NodePath(""),"unique_name_in_owner":false,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"light_mask":1,"visibility_layer":1,"z_index":0,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"position":Vector2(0, 0),"rotation":0.0,"scale":Vector2(1, 1),"skew":0.0,"disable_mode":0,"collision_layer":1,"collision_mask":1,"collision_priority":1.0,"input_pickable":false,"physics_material_override":null,"constant_linear_velocity":Vector2(0, 0),"constant_angular_velocity":0.0,"script":null)
          
          
          [node name="Node2D" type="Node2D" node_paths=PackedStringArray("_node_ref")]
          script = ExtResource("1_kmmcv")
          _res_1 = SubResource("Resource_a7l4x")
          _res_2 = ExtResource("3_lnf6o")
          _node_ref = NodePath("Node2D/Node2D/StaticBody2D")
          
          [node name="Node2D" type="Node2D" parent="."]
          
          [node name="StaticBody2D" type="StaticBody2D" parent="Node2D"]
          
          [node name="Node2D" type="Node2D" parent="Node2D"]
          
          [node name="StaticBody2D" type="StaticBody2D" parent="Node2D/Node2D"]

          I assigned the same deeply nested StaticBody2D to both the TestNode and the TestResource which lives inside the .tscn.
          You can see that the TestResource sub_resource does not have a path to the StaticBody2D at all. It has an entire copy of the node I assigned to it.

          The TestNode node, on the other hand, merely contains a NodePath pointing to the correct path of the StaticBody in the scene which I assigned to it.

          Does that then make more sense what I'm trying to say? 😂 I don't always choose the correct or best terminology...

          • xyz replied to this.

            Moral of the story: Don't assign Nodes to Reference @exports unless what you want is a copy of all of its values.

            award I understand perfectly what you're saying. I know how the thing works 🙂

              xyz So you're saying that for the TestResource assigned to TestNode._res_1 here, TestResource.target DOES point to the node at "Node2D/Node2D/StaticBody2D" and not just a copy of it? Where is that info stored then?

              • xyz replied to this.

                award Yes, but only in the temporary resource object created in the editor. It would make no sense to create a whole new copy of a node when you do that assignment, wouldn't it. When the scene is saved then the data is copied from the referenced node to the tscn file.

                So it's important to make a distinction here between runtime(or editor) references, and serialized references.

                Note that you can still have "regular" @export variables that hold references to nodes because node-to-node referencing within a scene can be serialized. However when you put such a reference into a resource, the serialization cannot be done because serialized resource cannot refer to a node inside serialized scene. So data is just copied from the referenced node to the tscn file during serialization aka saving.

                That makes sense. So it's a reference in memory, then a copy in file. But then what about when the scene is reloaded from file? Still a copy, I would assume, since it has no way to find the node it was originally referencing.

                • xyz replied to this.

                  award Right. You can easily test it out. Assign some node to the resource's property in the editor. It will actually refer to that node. The filed will show its name (and the path on mouseover). Save the scene and reload the project. The field in the inspector will now be blank (but not empty) holding a reference to an anonymous orphan node that was created on load. And that node was initialized with properties saved from the actual scene node assigned in previous session. Same happens when you run the project.