0 votes

I'm new to Godot and have based some code on a video that shows an arrow being fired:

https://www.youtube.com/watch?v=oLc_GPuD4Fs

The part of the video where they create the arrow:

https://youtu.be/oLc_GPuD4Fs?t=122

They update the arrow with translate():

https://youtu.be/oLc_GPuD4Fs?t=104

However, I want it to have proper phsyics, so I use apply_impulse():

extends Node2D

export (PackedScene) var arrow_template

var shoot_speed = 10

func create_arrow():
    var arrow = arrow_template.instance()
    arrow.add_to_group("arrows")
    var arrow_spawner_position_node = get_node("Position2D")
    arrow_spawner_position_node.add_child(arrow)

    # Get the position of the arrow when it's a child of our Position2D
    # node, store it, remove the arrow from it, and reapply it when it's
    # a child of the scene.
#    var global_pos = self.global_position
#    arrow_spawner_position_node.remove_child(arrow)
#    owner.get_parent().add_child(arrow)
#    arrow_spawner_position_node.global_position = global_pos

    var distance = owner.get_global_mouse_position() - self.global_position
    arrow.apply_impulse(Vector2(), distance * shoot_speed)

This mostly works, except that moving the mouse (which causes look_at() to be called) affects the arrow:

https://gfycat.com/DearSpectacularChickadee

I figure this is probably because the arrows are being created as children of the spawner, which itself is a child node of the player. So I tried fixing it - see the commented-out code above, where I've also explained what I was trying to do. My attempt failed though...

Here's what my node/scene trees look like:

https://imgur.com/a/quUshap

So: what is the correct way to spawn an arrow that takes the initial position and rotation of the shooter without being affected by it after it's been shot?

asked Apr 7, 2019 in Engine by oskfo (37 points)

1 Answer

+1 vote
Best answer

You're adding the arrow as a child of arrow_spawner_position_node, which I presume is a child of player? If so, that's your problem. Any node that's below the player is going to follow the player when it moves. Indeed, you want that for the spawner position, just not for the arrow itself. Make the arrow a child of the world/level/etc node, and it will be independent of the player.

answered Apr 7, 2019 by kidscancode (13,384 points)
selected Apr 8, 2019 by oskfo

Hey, thanks for the response. I tried adding the arrow to the scene in the commented-out code, but the position and rotation of the arrow are then set to zero. I used the following code to fix that, but just wondering if there's a better way to do it:

var arrow_position_node = get_node("Position2D")
var scene_node = get_node("../..")
scene_node.add_child(arrow)
arrow.global_position = arrow_position_node.global_position
arrow.global_rotation = arrow_position_node.global_rotation

This could be simplified a bit by replacing the last two lines with

arrow.global_transform = arrow_position_node.global_transform

This is a fine way of doing it, with the caveat that get_node("../..") is a bad practice as it's going to break if you ever move your nodes around, and it means you can't test your player independently because if you run the player scene alone, it doesn't have a grandparent node.

Thanks!

What would you recommend as the replacement for get_node("../..")?

I like to use signals for this. The player (or enemy, etc) emits a signal when shooting. The main scene receives this signal and instances the projectile.

As a general rule, get_node() for going down the tree (because nodes can manage their children), but signals (or sometimes groups) for going up the tree (because nodes shouldn't need to know what's above them).

That sounds like good advice.

What if the item that fires the projectile was responsible for creating it? E.g. each weapon would have a use() function that the player script called? Does that sound like a maintainable solution to you? Though then I guess there would be the same problem of how each weapon node gets a hold of the scene node...

You can pass the newly created instance with the signal, and the receiver will add it as a child.

Nice, thank you for all your help!

OK, this is kinda scary... the exact same problem I was having and the advice you gave me was already documented here:

https://docs.godotengine.org/en/3.1/getting_started/step_by_step/signals.html#shooting-example

Not sure how scary that is. I wrote that section of the docs. :P

Hahaha! It all makes sense now!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.