Now let's write our input code to fire a laser. You can handle user input anywhere in your scene tree. But it's a good idea to think about the structure of your game and what would make sense. We could handle the user input in the LaserWeapon node. But think of the scenario where the player ship has multiple weapons on it. It might make more sense to handle the input on our Player scene. So that is what we will do.

Open up the Player scene.

And then open up the Player script, "Player.gd".

When writing input code, it's helpful to understand how the Godot engine handles input.

So how does the Godot engine handle input?

Well first, the user gives input to their machine, whether that be a button press, mouse movement, screen touch, or joystick movement. The machine's OS (operating system) propogates that input to our Godot game. Starting at the root Viewport, Godot will pass the input event to first the "_input" function, then to any Control node "_input", and then finally if the input event wasn't consumed by an GUI elements, it will be passed to the "_unhandled_input" function.

Here is the official documentation on the subject.

If you look in our file "Player.gd" you will notice that used none of these options. We actually referenced an "Input" class from within the "_physics_process" function.

So what's the difference? Which one is right for us to handle shooting lasers?

The long and short of it is that using the "Input" class in the "_physics_process" or "_process" function actually queries the OS every physics step or frame respectively. Whereas using the "_input" and "_unhandled_input" functions let's the OS tell Godot that user input was given. From a performance standpoint, it behooves us to minimizes the calls we make to the OS. Ideally we would want to us the "_input" and "_unhandled_input" functions over the "Input" class in the processing functions whenever it makes sense to.

Since shooting a laser has nothing to do with the user interacting with a GUI, we will use the "unhandled_input" function and add it to our "Player.gd" script.

func _unhandled_key_input(event: InputEventKey) -> void: if (event.is_action_pressed("shoot")): $LaserWeapon.shoot()

Whenever the user gives us the "shoot" action as input, we will call the "shoot" method on our LaserWeapon instance. Notice the "$" symbol in "$LaserWeapon". This is a shorthand syntax to reference a child node instead of using the "get_node" function.

We referenced an input action named "shoot". But this hasn't actually been defined in our project yet. So let's do that.

Open up the Project Settings. And select the "Input Map" tab.

The input map is where we can define which controller inputs and keypresses map to which actions in our game. For the movement code we wrote earlier, we used to some input actions that Godot had already defined by default such as "ui_left" and "ui_right".

Let's define a new action. We will name the action "shoot".

Scroll down to the bottom. Our new "shoot" action will be there. Click the "+" symbol to add an input event. We will bind the "shoot" action to a "Key".

When you select an option, Godot will start listening for inputs. You can choose whichever key you want. You can even bind multiple keys to a single action. For this tutorial, I am going to bind the "shoot" action to the spacebar. When you have the correct key registered, click "OK" to confirm.

Let's try running our project. When we press spacebar lasers are spawned, but they don't do anything. They don't move or interact.

For this game, we want the lasers to fly upward. So let's implement that. Open up the Laser scene and attach a script to the root Area2D node.

extends Area2D var direction := Vector2(0, -1) var projectile_speed := 1000 func _process(delta: float) -> void: self.position += direction * projectile_speed * delta

Because our Laser scene is of type Area2D and does not interact with the Godot physics engine, we will put the movement code in the "_process" function instead of "_physics_process". And we will modify the laser's position directly.

A quick note about the 'self' variable. In GDScript, 'self' refers to whichever node the self variable is being used in. It's a way for a node to refer to iself. You will often see the 'self' variable referenced implicitly. In our code here, just writing 'position' would work just fine. But sometimes writing out the self variable explicitly can help make your code easier to read and understand.

The 'position' variable is of type Vector2. We want the laser to travel up the Y axis, so we set the Y value to a negative value with our Vector2 "direction" variable. We will multiply by the "delta" value to maintain smooth movement even with choppy framerates.

Now we run our project, the lasers travel upwards as expected.

Here is something to think about. Every time we shoot our laser, we are instancing a new Laser scene. This comes with a small yet non-zero memory cost. Our game is consuming more and more RAM without ever releasing it again. In our game once the laser leaves the viewport, it no longer affects the gameplay. So we should free it from memory.

You can see this in action if you open up your the task manager in Windows or your OS's equivalent. If you keep shooting lasers, our game will slowly consume more RAM. This is what's known as a memory leak.

To help solve this, Godot has a built-in node for us, the VisibilityNotifier2D node. Add the VisibiltyNotifier2D to our Laser scene. We are going to use a signal that the VisibilityNotifier2D provides.

Signals are the preferred way for nodes in Godot to communicate with each other. They preserve Godot's modular nature.

When you use functions like "get_parent" or "get_node" to call a node's methods directly, they make a hard assumption about the node structure of your game. That structure or heirarchy could change throughout the course of development. Any nodes that communicate that way will have their connections to each other break when the game is refactored.

In contrast, when a node emits a signal, it does not care who recieves it. It is the responsibility of the listener node to connect itself to the signal to hear when it is emitted. And there is much less reliance on node heirarchy, at least compared to traversing the node tree and calling functions directly.

To connect signals through the Godot editor, we first select the node that will emit the signal. In this case select our new VisibilityNotifier2D node in our Laser scene. We can see what signals our node can emit in the "Node" tab under the "Signals" heading.

Double-click the "viewport_exited" signal. Connect it to the Laser node.

This will create a function for us in the Laser node's script. This is the function that will execute when the signal is emitted.

In our game, when a laser leaves the viewport it will no longer be used, so we want to free up the memory it was taking up. Godot does have a function called "free", which will immediately handle this, but I almost always recommend using the "queue_free" function instead. This will allow Godot to finish processing the current frame, and then free the object.

This is good practice to better avoid "null pointer exception" errors. These are errors where an object in your program has been freed, but later on, your code tries to reference that object again.

With the new signal function added, our Laser.gd file should now look something like this.

extends Area2D var direction := Vector2(0, -1) var projectile_speed := 1000 func _process(delta: float) -> void: self.position += direction * projectile_speed * delta func _on_VisibilityNotifier2D_viewport_exited(viewport: Viewport) -> void: queue_free()

Now our lasers don't endlessly consume memory!