2
\$\begingroup\$

Please note that my Godot version is 3.3 so that could be a reason.

I tried to create a script to shoot bullets.

First I tried using the input function

func _input(event):
    # Mouse in viewport coordinates.
    if event is InputEventMouseMotion:
        #print("Mouse Motion at: ", event.position)
        look_at(event.position)
    if event is InputEventMouseKey:
        direction = event.position
        print("Mouse Click/Unclick at: ", get_global_mouse_position())
        var shoot = bullet.instance()
        get_parent().add_child(shoot)
        shoot.global_position = global_position
        yield(get_tree().create_timer(0.08), "timeout")

Then I tried using event.isEcho() and a state (also used Input.is_just_pressed())

enum {
IDLE
SHOOTING
}

Here is my current code: Sprite code (player/gun)

extends Sprite

onready var bullet = preload("res://bullet.tscn")
onready var direction = null

var pos = null

# Called when the node enters the scene tree for the first time.
func _ready():
    pass

func _process(delta):
    if Input.is_action_just_pressed("click"):
        direction = get_global_mouse_position()
        print("Mouse Click/Unclick at: ", get_global_mouse_position())
        var shoot = bullet.instance()
        get_parent().add_child(shoot)
        shoot.global_position = global_position
        yield(get_tree().create_timer(0.08), "timeout")

func _input(event):
    # Mouse in viewport coordinates.
    if event is InputEventMouseMotion:
        #print("Mouse Motion at: ", event.position)
        look_at(event.position)

Bullet: (not so relevant as my question is why process and input runs twice)

extends KinematicBody2D

var direction = Vector2.ZERO
var MAX_SPEED = 20
var ACCELERATION = 5
var velocity = Vector2.ZERO

onready var tween = $Tween

func _ready():
    direction = mouse.direction
    # (81, 23)
    tween.interpolate_property(self, 'position', position, direction, 0.8, Tween.TRANS_LINEAR, Tween.EASE_OUT)
    tween.start()
    yield(get_tree().create_timer(0.8), "timeout")
    queue_free()

I changed the code to:

func _process(delta):
    if Input.is_action_pressed("click"):
        set_process(false)
        direction = get_global_mouse_position()
        print("Mouse Click/Unclick at: ", get_global_mouse_position())
        var shoot = bullet.instance()
        get_parent().add_child(shoot)
        shoot.global_position = global_position
        yield(get_tree().create_timer(0.08), "timeout")
        set_process(true)

Still doesn't work.

The _input is run exactly at the same time so that's probably why.

Log:

Mouse Click/Unclick at: (453, 177)
Mouse Click/Unclick at: (453, 177)

Result: One bullet comes from (0, 0) going to some random place (I think), and another goes to the right position.

enter image description here

Sprite 2 is the gun. sprite is the player character.

enter image description here

After one click

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

About your first attempt

Your first attempt is making little sense to me. What is InputEventMouseKey? I know InputEventMouseButton.

Anyway, I don't think event.isEcho() works for mouse. As echo is - at least historically - a keyboard thing.

And don't use Input.is_just_pressed() in _input. I have a detailed explanation of that elsewhere.


About your second attempt

In your second attempt you do this:

func _process(delta):
    if Input.is_action_just_pressed("click"):

And this should work. The code you put inside the if statement should only run once per "click". Meaning that the game has to get a release and a new press for the code inside the if statement to run again.

The code inside the if statement should not be running twice, unless there were two "click". If it is running more times than you expect, I would suspect your - I'm guessing - mouse is faulty.


What does yield do?

Why are you using yield on a timer at the end?

yield(get_tree().create_timer(0.08), "timeout")

This is what the line of code actually does:

  1. Create an SceneTreeTimer that will emit a "timeout" signal after 0.08 seconds.
  2. Schedule the continuation of the method (from that point of the code onwards) to execute after it gets the "timeout" signal from the SceneTreeTimer.
  3. Return. It returns a GDScriptFunctionState.

To reiterate, yield takes an object (in the above example get_tree().create_timer(0.08)) and the name of a signal (in the above example "timeout"). And it will schedule the execution to continue after that object emits that signal. In other words yield awaits the signal form the object (and I say await and not wait to emphasize that yield is not blocking). I hope this gives you an appreciation for what yield does.

So, when the SceneTreeTimer emits the "timeout" signal, the execution of the method will resume from that point in the code. And since that point in the code is at the end of the method, it accomplishes nothing.


Look at this:

tween.interpolate_property(self, 'position', position, direction, 0.8, Tween.TRANS_LINEAR, Tween.EASE_OUT)
tween.start()
yield(get_tree().create_timer(0.8), "timeout")

Are you trying to await until the tween completed? Write what you mean:

tween.interpolate_property(self, 'position', position, direction, 0.8, Tween.TRANS_LINEAR, Tween.EASE_OUT)
tween.start()
yield(tween, "tween_completed")

And just in case, I'll also say that yield is not preventing further calls to _process to run. Just like using return does not prevent further calls to the same method to run, and I remind you that yield actually returns. Thus, other calls to _process can still happen, and you should be getting one per graphics frame, unless we do something about that.


Disabling _process

If your goal is to stop getting calls to _process, then we can do something about that. In particular we can disable _process. To do that, you can call set_process(false). And remember you need to call set_process(true) to enable it again.

Can we do that with the timer? Sure. It would be something like this:

set_process(false)
yield(get_tree().create_timer(0.08), "timeout")
set_process(true)

So, set_process(false) will disable calls to _process, until the call set_process(true). And the call set_process(true) will only happen after the timer completed. I hope that makes sense.

\$\endgroup\$
13
  • \$\begingroup\$ Look in my question ( I added ) \$\endgroup\$ Commented Mar 16, 2022 at 1:31
  • \$\begingroup\$ @youlikecodingstackoverflow You changed from is_action_just_pressed to is_action_pressed. The execution will enter if Input.is_action_pressed("click"): every frame that "click" is pressed (so if you keep it pressed, it should keep entering). Use is_action_just_pressed instead. And having both _process and _input should not be a problem. Now that you mention the bullet direction: Notice interpolate_property takes initial and final values. How are you telling the bullet the destination? Idk what mouse.direction is. \$\endgroup\$
    – Theraot
    Commented Mar 16, 2022 at 2:01
  • \$\begingroup\$ So I changed the bullet code to use get_global_mouse_position but somehow 1 bullet goes to the right mouse position, but not go from the player to the position and 1 bullet goes from the player but moves along with it (meaning its attached or something) and it doesn't go to the right position \$\endgroup\$ Commented Mar 16, 2022 at 2:08
  • \$\begingroup\$ @youlikecodingstackoverflow You are adding them to the parent get_parent().add_child(shoot), are you moving the parent? Anyway, since you are using global position, then use global_position instead of position to move the bullet. Another thing you can try is calling set_as_toplevel(true) on the bullet so it is always in global space. \$\endgroup\$
    – Theraot
    Commented Mar 16, 2022 at 2:12
  • \$\begingroup\$ So I am moving the parent. How can I get the node above the parent? I tried get_parent.get_parent, get_root node. Another thing, when I try to get_parent() (which is the moving kinematic body) it gives me null position even though in remote that's where the position is \$\endgroup\$ Commented Mar 16, 2022 at 2:14
0
\$\begingroup\$

For anyone viewing this question, my solution was:

Making the bullet get the mouse position

Making a singleton and making bullet get the pos variable in the player singleton, I updated pos in _process().

Using Input.is_action_just_released() instead of the event check because the event is run if the mouse button is clicked AND released (which caused the Input to be ran twice)

\$\endgroup\$

You must log in to answer this question.

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