3
\$\begingroup\$

I'm working on a FPS game in Godot, and I want to be able to drop a flare on the ground at the player's position.

I created a scene with a "flare", which right now is basically just a sphere mesh instance and an omni light child.

In my player.gd I have something like this:

var instancedFlare = preload("res://flare.tscn")

func _physics_process(delta):
    if Input.is_action_just_pressed("drop_flare"):
        print("Flare!")

which prints "Flare!" to the output when I hit F or Y on the controller.

How can I spawn an object and place it at the player's position in the world? I've looked for some tutorials, but I haven't managed to find the answer yet.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Note: asking for help with on-topic problems is okay here, but for future reference, asking for tutorials, references and resources is not okay. I've edited to remove that part out of your question. \$\endgroup\$
    – Pikalek
    Apr 19 at 17:47

2 Answers 2

4
\$\begingroup\$

Spawning nodes dynamically works the same, whether it's 2D or 3D, and you kind of were on the correct track:

  • Load the scene as a template.
  • Instantiate said scene template as a new node.
  • Add the new node to your scene's node tree.

Note the syntax slightly changes, depending on whether you're on Godot 3 or Godot 4.

# Load the scene as a template
var packedFlare = preload("res://flare.tscn")

func _process(delta): # There's no need to time this with physics
    if Input.is_action_just_pressed("drop_flare"):
        # Instantiate the scene template as a new node.
        var new_flare = packedFlare.instantiate()

        # Here you can adjust things not related to the scene
        # ...

        # The actual parent node could be some different node to collect all flares, that's up to you.
        # For this example I pick the player's parent.
        var parent = self.get_parent()
        
        # Add the new node to your scene's node tree.
        parent.add_child(new_flare)

        # Now change stuff related to the scene structure (like position)
        new_flare.global_position = self.global_position
```
\$\endgroup\$
4
  • \$\begingroup\$ This does seem to work. I am getting a weird error from this line: newflare.global_position = self.global_position that says player.gd:64 @ _physics_process(): Condition "!is_inside_tree()" is true. Returning: Transform3D() But it does put the flare object into the world, which is perfect! \$\endgroup\$
    – AndyD273
    Apr 19 at 20:20
  • 1
    \$\begingroup\$ @AndyD273 I keep getting the error Condition "!is_inside_tree()" is true. Returning: Transform3D() \$\endgroup\$
    – Theraot
    Apr 19 at 23:13
  • \$\begingroup\$ @Theraot Thanks, that helped. I needed to change the order because it was trying to set the global position before adding it as a child, and so it was "outside the tree". I changed it to add the child to the parent, and then set the position to self.global_position and that stopped it from throwing the error. \$\endgroup\$
    – AndyD273
    Apr 21 at 4:38
  • \$\begingroup\$ Ah, my bad! Something like global position is obviously dependent on the scene hierarchy, which is why it caused the error you observed. If you've got stuff like this, you have to change it once the node is inside the scene. \$\endgroup\$
    – Mario
    Apr 22 at 5:24
3
\$\begingroup\$

This is your PackedScene:

const FlareScene := preload("res://flare.tscn")

You instance it like this:

var flare_instance := FlareScene.instantiate()

You add it to the scene tree as a child of the player like this:

add_child(flare_instance)

Now, that would be adding it as a child of your player, which means it would move with it, and you likely don't want that.

The simplest solution is to set it as top level:

flare_instance.top_level = true

Alternatively, you would not add the instance as a child of the player but of something static (e.g. the root of the stage scene), and you need to figure out the appropiate transform for it.

First a clarification: transform is relative to the parent node, and global_transform is relative to the world origin.

Now, if we add a physics node it will interact right away (report collision, etc... This might be an issue, for example if it is a projectile that destroys itself on collision), so we want to add it in the correct transform. But before we have added it to the scene tree, it does not have a global_transform...

Setting the global_transform outside of the scene tree is not an error, but it sets transform instead (which might result in objects spawned in an unexpected position).

So:

  • Have a Node3D, in this case: flare_instance.
  • Have a position where you want to spawn it. I'll take it form a Node3D I'm calling spawn_marker (it could be the player or a something else - I'm imagining it is a Marker3D child of the player that represents the position where you want to spawn the flare_instance relative to the player)
  • Take the global_transform where you want to placed it, which I'll call: wanted_transform.
  • Have another Node3D that will be the parent, I'll call it spawn_parent.
  • Figure out the appropiate transform and and set it.
  • Then add the Node as child of that parent.

Like this:

var flare_instance := FlareScene.instantiate()
var wanted_global_transform := spawn_marker.global_transform
var spawn_parent := get_parent_node_3d()

var new_transform := spawn_parent.global_transform.affine_inverse() * wanted_transform
flare_instance.transform = new_transform

spawn_parent.add_child(flare_instance)

I hope this makes sense.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .