Hello, I am trying to instantiate several "bullets" around a scene and want them to form a circular shape around this scene.

My approach to doing this was that I would create a function in which I could decide;

  • The scene to instantiate around (spawnerObject)
  • Which scene to instantiate (bulletObject)
  • How many scenes to instantiate (bulletAmount)
  • At what speed the instantiated bullet scenes should move (bulletSpeed)
  • How far away from the origin scene the instantiated bullet scenes should spawn (distanceFromSpawner)

This function is supposed to basically:

  1. Create a "bullet" scene
  2. Rotate itself by a certain amount (the amount is calculated by dividing 360 by the amount of bullets I need to instantiate)
  3. Repeat in a for loop until the specified amount of bullets have been instantiated.

I tried to explain it line by line in comments in the code so I hope that will be helpful.

Here is the script attached to the scene I use as the spawner object:

Here is the script attached to the bullet I'm trying to spawn:

Note: The bullet scenes do not collide with themselves.

The issue:
(Note: The purple orb is the bulletObject, the extremely small gray box with a star texture is the spawnerObject. Also note that none of these textures are mine and I have extracted them from Touhou 8 to serve as temporary textures to work with. Prettier than working with single colored boxes and circles, that's for sure)

If I input 1 to bulletAmount, it will instantiate the bullet where I want it to. That is good, it is supposed to do that:

If I input 5, it will spawn all of them at the same location and the spawnerObject's rotation is set to 0 degrees:

If I input an even number like 8 or 12, it will, again, spawn them at the same location. However, this time they would all spawn in the the opposite side and the rotation of the spawnerObject gets set to 180 degrees. I would expect at least the first of them to spawn 100 units below the spawnerObject but that is not the case (This picture is from when the input was 8):

If I input 7, it gets all messed up and spawns all of them at the same location, but the spawnerObject's rotation is set to -9 degrees:

I have absolutely no clue how and why any of this is happening. I've been stuck on this for the past 5 hours and finally decided to ask for help after googling the issue, browsing the documentation, and not being able to find anything helpful. Please tell me if there is any information missing that might be needed. I am extremely new to both coding and Godot so I might be missing something super obvious (which I kinda hope is the case lol). Thank you in advance 🙂

  • xyz and Jesusemora replied to this.
  • OxygenMolecule To simplify, I would avoid using spawner's rotation. Instead just rotate a direction vector:

    func spawn_radial(packed_scene, spawner, distance_from_origin, count):
    	var rad_vector = Vector2.UP * distance_from_origin
    	for i in count:
    		var node = packed_scene.instantiate()
    		spawner.add_child(node)
    		node.position = rad_vector
    		node.rotation = rad_vector.angle()
    		rad_vector = rad_vector.rotated(2*PI/count)

    xyz
    Thanks for the answer, but it does not seem to work. This time it spawns the bullets at x = 82.134 and y = 100 no matter what input I give it for the amount of bullets. The spawner still gets rotated in the same manner when given the previous inputs of 1, 5, 7, 8 and 12.
    I changed the line where I add it as a child into:
    get_parent().add_child.call_deferred(instantiatedBulletObject)

    I tried:
    get_parent().add_child(instantiatedBulletObject)
    at first but that gave me an error and told me to use call_deferred instead.

    Here is what it looks like when I set the bulletAmount to 12:

    • xyz replied to this.

      OxygenMolecule Don't call spawn_bullet_circular() from spawner's _ready(). Call it afterwards, earliest from the _ready() of the object you're parenting the bullets to. With your system you need to reparent the bullet to some higher up node after you added it as a child to spawner and set its local positions. It'll work but there are more elegant and simpler ways to handle spawning without using rotating spawner nodes, which is a bit clunky.

        OxygenMolecule your problem is very simple.

        you are using position to position the bullet a set distance from parent.
        then you are using global_rotation_degrees to rotate the spawner.
        the spawner CAN'T update between loops to spawn the object in the correct position. my guess is the last position is used only.
        also you are rotating the spawner but the children are still all in the same local position.

        solution: you have to move the bullets into a circle instead.

        instantiatedBulletObject.rotate(amountToRotate * cycleNo)
        instantiatedBulletObject.translate(Vector2(0, distanceFromSpawner))

          xyz
          I'm a bit confused about the "call it afterwards, earliest from the..." part of your comment. I'd appreciate it if you could elaborate on that a bit more. On another note, I tried reparenting the bullet after it has become a child of the spawnerObject node and have it keep it's global transforms but it gave me the exact same results in my initial post. I have changed back line 21 and added lines 33 and 34 in order to do that.

          Later, I also tried storing the initial rotation of the spawnerObject before the for loop. I set the spawnerObject back to this initial rotation right before rotation occurred. It's such an obvious error that I can't believe I missed it. But no, it still did not work and spawned the bullets on top of each other. Although I now get the correct rotation for the final bullet. Example: If I wanted to spawn 3 bullets, now the spawnerObject's rotation gets set to -120, as it should, but all the bullets are once again on top of each other. Even though I reparent them to a higher node in each cycle of the loop and they do get properly reparented to the base node as can be seen on the left:

          You mentioned a more elegant/solution solution, I'd love to know about that as the reason I'm trying to get this to work isn't from personal preference but a lack of knowledge. If you have any suggestions on that front I could try then I'd be happy to do so. All is good so long as I get to choose bulletObject, bulletAmount, bulletSpeed and distanceFromSpawner. Thanks again for your help.

          • xyz replied to this.

            OxygenMolecule Reparenting deferred is no good because it's called after the complete loop was done so all bullet nodes will still end up at the same position. Call reparent() normally. To avoid the error do not call spawn_bullet_circular() from spawner's _ready(). Instead, call it from its parent's _ready().

            To understand why, read up on order of node initialization inside the scene tree.
            https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-private-method-ready

            Jesusemora the spawner CAN'T update between loops to spawn

            It actually can.

              Jesusemora
              You are probably right on it using the last position. Your suggestion seems to help as using .rotate made every single instantiatedBulletObject have a different rotation! I tried spawning 3. One of them is rotated by 0, the other, 35.5, and the last one 71 degrees. However, they are rotated around themselves and NOT around the spawner. Any suggestions about how can I go about setting the spawner as the origin point of the bullets?

              One more thing, 360 divided by 3 is 120. So I would expect to see one of them have 0 (or 360, same thing), another 120, and the last one at 240 degrees. I later tried this instead:
              instantiatedBulletObject.rotate(rad_to_deg(amountToRotate * cycleNo))
              But this gave me 0, 96.8, and -166.5 degrees of rotation instead and now I'm left a little confused.

              • xyz replied to this.

                xyz
                I see now, thank you very much! I quickly tried calling my function with a non-deferred reparent() inside a _process() to see if it would work and yes, it absolutely does. It should work when I manage to call reparent() without deferred in a _ready(). I don't have enough time left today to work on this, but I will get to it tomorrow and read up the link you have sent. Oh, and here is what it looks like with 12 bullets inside of a _process() 😃

                OxygenMolecule To simplify, I would avoid using spawner's rotation. Instead just rotate a direction vector:

                func spawn_radial(packed_scene, spawner, distance_from_origin, count):
                	var rad_vector = Vector2.UP * distance_from_origin
                	for i in count:
                		var node = packed_scene.instantiate()
                		spawner.add_child(node)
                		node.position = rad_vector
                		node.rotation = rad_vector.angle()
                		rad_vector = rad_vector.rotated(2*PI/count)