Chapter 3: Shoot Bullets and Defeat Enemies
Goal of Chapter 3
In Chapter 2, we achieved the goal of:
- Creating a simple stage where the player character can walk and jump.
In this chapter, we’ll take it further and explain how to make it so that:
- The player can defeat enemies by shooting bullets.
Here’s an example of what the finished result for Chapter 3 will look like:
Planning How to Make “Player Shoots to Defeat Enemies”
Let’s think about the steps required.
-
The player will need a new Shot state.
-
To fire bullets, we’ll also need to create a new Bullet object.
-
Since we don’t yet have an enemy, we’ll also need to create an Enemy object.
So, in Chapter 3, our goals will be:
-
Create the Bullet object
-
Add a Shot state to the Player
-
Create the Enemy object
Finally, to make it possible to defeat enemies, we’ll need to implement the process of dealing damage (an attack system).
ACTION GAME MAKER provides a dedicated system for handling attacks and damage, so before we dive into production, let’s take some time to learn about how this system works.
“Attack” and “Damage” in ACTION GAME MAKER
Attack Hits
Do you remember the HitArea2D / HitCollision and AttackArea2D / AttackCollision nodes inside the Player object?
In ACTION GAME MAKER, whenever an AttackCollision from an AttackArea2D collides with a HitCollision from a HitArea2D, the system automatically processes damage.
Additionally, with Godot’s built-in features CollisionLayer and CollisionMask, you can fine-tune which objects “collide” and which do not.
CollisionLayer and CollisionMask
As explained earlier with wall collisions, Godot controls collisions using the CollisionLayer and CollisionMask system.
Think of them as layers that add depth to collision detection.
You configure them in a panel like this:
For example:
- If an AttackCollision has a Mask set to layers 1 and 2, it cannot hit a HitCollision that only exists on layer 3.
This setup also helps avoid unwanted cases, such as a character colliding with their own attack hitbox.
Damage Processing
When an attack successfully hits, damage is processed automatically:
-
The attack power of the attacking object (set in BaseSettings) is subtracted from the HP of the defending object.
-
Afterward, you can enable Invincibility (also set in BaseSettings) to make the damaged object immune to further hits for a certain amount of time.
With this system, ACTION GAME MAKER handles the core of attack and damage logic automatically, and you only need to configure the relevant nodes and settings.
Creating the Bullet Object
Now that we’ve covered the basics of how attacks work, let’s actually create a bullet.
The bullet will also be a Game Object, so the basic workflow is the same as with the player:
The one difference is that the bullet is not controlled directly by the player’s input device.
Instead, bullets are fired and given movement through the “Shoot Bullet” action, so we don’t need to create custom movement logic for the bullet itself.
Creating a Game Object for the Bullet
-
Open a new scene tab, and for the root node, select Game Object.
-
In the Create New Object window, set:
-
Object Name: bullet
-
Template: bullets
-
Type: bullet_base
-
The bullet object template will be loaded.
Setting the Bullet Image
Let’s assign an image to the bullet.
We could also create an animation to make the bullet look cooler, but for this tutorial, we’ll keep it simple and just use a static image.
-
Right-click the image below and save it to your computer.

-
Drag and drop the downloaded bullet.png into the FileSystem in the editor.
-
In the Scene panel, select Sprite2D.
- In the Inspector, drag and drop bullet.png onto the Texture property (currently
<empty>
).
Adjusting MoveAndJumpSettings for the Bullet
We want the bullet to move faster than the player.
The player’s speed is set to 300, so let’s make the bullet speed 600.
-
In the Scene panel, select the MoveAndJumpSettings node.
-
In the Inspector, set both Horizontal Speed and Vertical Speed to 600.
Now we have a basic bullet object with its own sprite and speed settings.
Adjusting the Bullet’s Attack Hitbox
Setting Collision Layers and Masks in AttackArea2D
By default, this object is placed on Layer 1, with its Mask set to 1 and 2.
This means it can hit objects on Layers 1 and 2.
However, the Player is also on Layer 1 with Mask 1,2 — so if we leave it like this, bullets will collide with the player as soon as they’re fired.
To prevent this, we’ll move the bullet’s collisions to Layer 3 and Mask 3 only.
-
In the Scene panel, select AttackArea2D.
-
In the Inspector, expand the Collision section.
-
A panel will appear for selecting Collision Layers/Masks.
Now, bullets will no longer collide with the player.
Adjusting the Size of the AttackCollision
At the moment, the attack hitbox is much larger than the bullet sprite.
Let’s resize it so it better matches the bullet.
-
In the Scene panel, select AttackCollision.
-
In the editor viewport, drag the orange handles around the shape to resize it.
With this, your bullet’s collision is properly set up — it won’t hit the player, and its attack hitbox now fits the bullet’s size.
Setting Up the Bullet Script
The wall collision (CollisionShape2D) is already the right size, so no changes are needed there.
With the basic setup done, let’s create a script for the bullet.
The bullet should disappear when:
To achieve this, we only need two states: Move and Disappear.
Let’s set them up.
Attaching the Script
-
Select the Bullet (GameObject) node.
-
In the Scene panel, click the
+ (Attach Script) button.
-
Leave the default options and click Create, making a new script called bullet.vs.
Setting the “Move” State
We’ll reuse the default State001 as the Move state.
Since bullet movement is handled by the “Shooter” (the object that fires it), this state doesn’t need additional actions.
-
Select State001.
-
In the Inspector, change the Title to Move.
Creating the “Disappear” State
-
Right-click to the right of the Move state and select Add State.
-
Select the new State001 and rename the Title to Disappear.
-
In the Inspector, click + Add Executable Action.
-
Choose RemoveSelf and click Add.
- Now, whenever the bullet enters this state, it will be removed from the scene.
Linking Move → Disappear
The bullet should disappear if:
We can combine multiple conditions in a single link using AND (and) / OR (or) logic.
In this case, we’ll connect them with OR:
Enemy Collision OR Tile Collision OR Outside Camera
Steps:
-
Right-click the Move state and select Add Link.
-
Connect the link to the Disappear state.
-
Select the new link, then in the Inspector, click + Add Condition.
-
Choose ContactWithHitArea.
-
Check Select All Sides and click Add.
-
Again, click + Add Condition.
-
This time, choose ContactWithTile.
-
Select All Directions, and for the logical condition, select OR before clicking Add.
-
Add a third condition with + Add Other Condition.
-
Scroll down and choose Offscreen.
-
Set the Target Type to This Node, and set the logic to OR, then click Add.
-
Confirm the conditions look correct.
- If the AND/OR settings are wrong, click the icons to toggle them.
Note: Should “ContactWithHitArea” use AND?
The AND/OR setting refers to the relationship with the previous condition.
Since this is the first condition, it doesn’t matter whether it’s AND or OR.
But if you move it below another condition, AND would apply.
To be safe, leave it as OR.
That’s it for the bullet setup! Don’t forget to save the scene.
Modifying the Player Object to Fire Bullets
To make the player able to shoot bullets, we’ll need to go through four steps:
A. Create a shooting animation
B. Add and configure the BulletsSettings node
C. Add and configure a Connector node (the firing point)
D. Create a “Shoot Bullet” state in Visual Script
Let’s start with the first step: adding the shooting animation.
A. Creating the Shooting Animations
First, let’s think about the situations where the player will fire bullets. Since we’re going for a Mega Man–style setup, it seems like we’ll need three shooting states: standing and firing, shooting while moving, and shooting in mid-air.
However, the “Move” animation we already created is designed so that the character runs while holding their hand forward, which works perfectly for a shooting motion. That means we only need to create two new animations: Ground Shot and Air Shot.
We’ll use the same method as before: the required shooting poses already exist in the loaded sprite sheet. The workflow is: Add Animation → Insert Keyframe → Load into Animation Set.
Creating the “Ground Shot” and “Air Shot” Animations
-
Set the Scene tab to Player and the Editor view to 2D.
-
Select the AnimationPlayer node.
-
In the Animation panel at the bottom, click Animation and create a new animation.
-
Name the new animation Ground Shot.
-
Select Sprite2D, and in the Inspector, expand the Animation property.
-
Set the Frame to 1 (the frame where the player is standing and holding their hand forward). Then press the
+ (Add Keyframe) button.
-
In the popup, uncheck Create RESET Track, then click Create.
-
Repeat steps 2–3 to create another new animation, this time called Air Shot.
-
With Sprite2D selected, set the Frame to 2 (the frame where the player is mid-air, holding their hand forward). Press the
+ (Add Keyframe) button to insert it.
Registering the Animations in the Animation Set
-
Select the Player (GameObject) node.
-
In the Inspector, click the Bulk Load Animations button.
-
The Ground Shot and Air Shot animations will now be added to the animation set.
C. Adding and Configuring the Connector Node
A Connector node is used to specify a “location” when working with Visual Scripts—such as the origin point for firing bullets or where objects should spawn. In this case, the connector will serve as the firing point, so we’ll place it at the tip of the hand shown in the shooting animation.
-
Select the Player (GameObject) node.
-
Click the + (Add Child Node) button in the upper left to open the Create New Node dialog.
-
Select Connector and click Create.
-
Select the AnimationPlayer node and switch the animation to Ground Shot.
-
Select the Connector node again.
-
Switch the editor tool to Move Mode.
-
Drag the ✛ (connector marker) to the tip of the character’s hand. This completes the connector setup.
D. Adding the Shoot State
Next, let’s add the states needed to fire bullets. We’ll need both Ground Shot and Moving Shot states, as well as an Air Shot state.
However, the air shot needs some special handling. If we simply transition from Air Shot back to Jump, the jump action would trigger again, causing the player to rise higher with every shot. To avoid this, we’ll split the Jump state into two states: Jump and Air.
-
The Jump state will immediately transition into Air.
-
While in Air, if the player touches the ground (tile below), they’ll transition back to Idle.
-
The Air Shot will then return to Air, preventing repeated jump actions.
Let’s build this step by step.
Creating the Ground Shot and Moving Shot States
-
Switch to Script Tab
-
Create a new state near the left side of the Idle state.
-
Rename the new State001 to Ground Shot.
-
In the Select Animation, assign the Ground Shot animation set.
-
Click + Add Execution Action.
-
Select FireBullet as the action.
-
In Bullet Data, specify the bullet object you created earlier.
-
Click the Assign button next to Connector.
-
In the Select Node window, choose the Connector you created earlier.
-
Confirm that Bullet Data = “bullet” and Connector = “Connector,” then click Add.
-
For Moving Shot, copy the Ground Shot state:
-
Right-click Ground Shot and select Copy.
-
Right-click near the right side of Move and select Paste State.
-
Rename the new state to Moving Shot.
-
Enable the checkbox Continue Previous Animation. Since Moving Shot uses the same animation as Move, no animation change is needed here.
Linking Idle ↔ Ground Shot and Move ↔ Moving Shot
We want pressing the Attack button in Idle or Move to transition into Ground Shot or Moving Shot. After 0.3 seconds, they should return to Idle or Move.
-
Right-click Idle and add a link to Ground Shot.
-
In the properties, check Trigger On Input.
-
Expand the Input Operations List (Array[InputCondition]).
-
Click + Add Input to add a new entry.
-
Click and choose New Input Condition.
-
Expand Input Condition and set Input Key = Attack.
-
Right-click the link and select Copy.
-
Right-click Move and select Paste Link.
-
Connect it to Moving Shot.
-
Right-click Ground Shot, add a link back to Idle.
-
In the inspector, click + Add Condition.
-
Choose Elapsed Time as the condition.
-
Change the duration from 1 second to 0.3 seconds.
-
Right-click this link, select Copy.
-
Right-click Moving Shot, Paste Link, and connect it back to Move.
At this point, the ground shooting states are complete.
Testing Ground Shooting
-
Click the Run Project (F5) button in the top-right.
-
If configured correctly, pressing X in Idle or Move will fire a bullet.
-
If it doesn’t work, check the following:
-
Animation doesn’t play → Check state animation settings and AnimationPlayer.
-
Animation plays but no bullet → Check the FireBullet action settings.
-
Bullet appears but doesn’t move or flies incorrectly → Check BulletsSettings.
Close the test once it’s working.
Splitting Jump into Jump and Air States
-
Create a new state to the left of Jump.
-
Rename State001 to Air.
-
Set its Animation to Jump (for air visuals).
-
Right-click Jump, add a link to Air.
-
In the inspector, set Conditions for Switching = Unconditional (this means Jump instantly switches to Air).
-
Cut the existing Jump → Idle link: select it, right-click, and choose Cut.
-
Right-click Air and Paste Link, then connect it to Idle.
-
This way, landing transitions now go through Air, not Jump.
-
Test play to confirm the jump → air → landing flow works correctly.
Creating Air Shot and Linking It
Since Air Shot works the same way as Ground Shot but uses a different animation, we’ll reuse the existing state.
-
Right-click Ground Shot and select Copy.
-
Paste it to the left of Air.
-
Rename Ground Shot (1) to Air Shot.
-
In the inspector, change the Animation to Air Shot.
-
Copy the Idle → Ground Shot link.
-
Right-click Air, paste the link, and connect it to Air Shot.
-
Copy the Ground Shot → Idle link.
-
Right-click Air Shot, paste the link, and connect it to Air.
Final Test
Run the project again:
-
Press X in Idle or Move → Player fires bullets.
-
Jump with Z, then press X → Player fires bullets mid-air.
If both work correctly, your shooting system is complete!
Creating the Enemy Object
Next, let’s create the enemy object. We’ll start with the simplest type of enemy—one that only moves left and right. The process is the same as when we created the player character: create a scene, assign an image and animation, configure the nodes, and then build the script.
Creating the enemy GameObject
-
Switch the editor view from Script back to 2D.
-
Create a new scene tab, and for the root node, select GameObject.
-
In the Create New Object window, set the following:
-
Right-click Unsaved and save the scene as enemy.tscn.
Setting Up the Enemy’s Image and Animation
For the enemy, let’s use the image provided below. Unlike the bullet, which was a simple symmetrical circle, this enemy is asymmetrical and needs to flip depending on its movement direction. Since the auto-flip feature is handled through the Animation Set, we’ll set the texture with Sprite2D, create an animation with AnimationPlayer, and then configure the Animation Set on the enemy (GameObject) node, just like we did for the player.
Configuring the Sprite2D
-
Right-click the image above and save it to your PC.

-
Drag and drop the image into the FileSystem to import it.
-
Select the Sprite2D node.
-
In the Inspector, drag and drop enemy.png into the Texture property. Since this image does not need to be divided into frames, the sprite setup is complete.
Adjusting the Collision Shape with CollisionShape2D
The current collision shape is much smaller than the sprite, so let’s resize it.
-
In the Scene window, select the CollisionShape2D node.
-
Drag the orange handles at the corners of the collision box to resize it so that it properly matches the sprite.
Configuring AnimationPlayer and Creating the Animation Set
To set up the Animation Set, we need an animation. Unlike the player, this enemy doesn’t require frame switching, so simply creating an animation is enough.
-
Select the AnimationPlayer node.
-
In the animation window at the bottom, click Animation and then select New.
-
Name the new animation Move and create it. Since no tracks are needed, this animation is complete. Now let’s set up the Animation Set.
-
Select the enemy (GameObject) node.
-
In the Inspector, click AnimationPlayer.
-
Confirm that the correct AnimationPlayer is assigned, then click OK.
-
Click the Bulk Load Animations button in the Inspector.
-
Expand the Animation Set and confirm that Move has been properly registered.
Adjusting the Enemy’s BaseSettings
To make sure the enemy can actually be defeated by attacks, we need to configure its health and invincibility settings.
Setting Health
-
Select the BaseSettings node.
-
In the Inspector, change both HP and Max HP from 1
to 3
.
- This means the enemy’s HP will reach 0 after taking 3 points of damage.
Setting Invincibility
Invincibility prevents an object from losing HP for a short period after taking damage. Without this, the enemy could take multiple hits at the exact same moment.
Since the player’s firing interval is set to 0.3 seconds, an invincibility time of around 0.2 seconds should work well.
-
If set to 0.4 seconds, the enemy might ignore the second bullet in a quick double shot.
-
If set to exactly 0.3 seconds, there’s a chance the second bullet won’t register in close-range situations.
Additionally, the invincibility system includes a feature that makes the object blink during the invincible state. With a 0.2-second invincibility time, setting the blink interval to 0.1 seconds will make the blinking visible.
-
In the Inspector, change the Invinciblity Duration property from 1.0
to 0.2
.
-
Right below it, change the Blink Duration property from 1.0
to 0.1
.
Adjusting the Enemy’s Hitbox
Next, let’s configure the enemy’s hitbox. Since we set the bullet object’s collision layer to 3, the enemy also needs to be on layer 3 so that it can properly interact with bullets.
Configuring Collision Layers and Masks in HitArea2D
-
Select the HitArea2D node.
-
In the Inspector, expand the Collision section.
-
Change both the Layer and Mask to 3.
- This ensures the enemy’s hitbox can properly interact with the bullet’s attack collision.
Adjusting the Size of HitCollision
Currently, the HitCollision is too large, so let’s resize it.
-
In the Scene window, select the HitCollision node.
-
Drag the orange corner handles of the collision box to resize it so that it is slightly larger than the enemy’s visible sprite.
Adjusting the Enemy’s Attack Collision
Next, let’s configure the enemy’s attack hitbox. Unlike bullets, the attack collision only needs to interact with the player. Since the player’s collision layers are left at their default values, we don’t need to change any layer or mask settings here. All we need to do is resize the collision shape.
-
In the Scene window, select the AttackCollision node.
-
Drag the orange corner handles of the collision box to resize it so that it matches the size of the enemy’s hitbox.
Setting Up the Enemy Script
The enemy will only move left and right, so all we need is movement and a way to disappear when its HP reaches 0.
In theory, this could be done with three states—Move Left, Move Right, and Vanish—but here we can take advantage of the Template Movement action, which handles simple patrol movement automatically.
So, we’ll create just two states: Template Move and Vanish, and link them so that when HP reaches 0, the enemy disappears.
Attaching a Visual Script
-
Select the enemy (GameObject) node.
-
Click the Attach Script button.
-
Leave the default name as enemy.vs
and press Create.
Creating the “Template Move” and “Vanish” States
-
Rename State001 to Template Move.
-
In the Inspector, set the Animation Category to Move.
-
Click the + Add Executable Action button.
-
Choose MovementTemplate.
-
Set Move Time to 3
and enable Can’t Fall Off Ledges.
-
Press Add.
-
Right-click in the empty space to the right of the Template Move state and choose Add State.
-
Rename the new State001 to Vanish.
-
Click the + Add Executable Action button.
-
Select RemoveSelf and add it.
If it looks like the figure below, your state setup is complete.
Linking Template Move to Vanish
-
Right-click the Template Move state and select Add Link, then connect it to Vanish.
-
In the Inspector, click the + Add Condition button.
-
Select HPIsZero.
-
Leave the default settings (TargetType: This Node) and add it.
If your link matches the example, the setup is complete.
Placing the Enemy in the Stage1 Scene
Now that the enemy is ready, let’s place it in stage1 alongside the player.
-
Switch the editor view from Script to 2D.
-
Switch the scene tab to stage1.
-
In the Scene window, select the Player node.
-
With the Player selected, drag enemy.tscn from the FileSystem into the stage, placing it to the right of the Player.
-
Test play the game.
If everything is set up correctly, the enemy will patrol left and right every 3 seconds, and will vanish after being hit by 3 bullets.
Troubleshooting Checklist
-
Enemy doesn’t move left and right:
Check the Template Move state settings.
-
Enemy doesn’t flip when turning around:
Check the enemy node’s Animation Set and confirm the Template Move state has the correct animation assigned.
-
Enemy disappears after 1 hit:
Check the BaseSettings for the enemy’s HP, Max HP, and Invincibility settings.
-
Bullets don’t hit the enemy:
Check the HitArea CollisionLayer/Mask of the enemy and the AttackArea CollisionLayer/Mask of the bullet.
-
Bullets don’t vanish on impact:
Check the bullet’s Move → Disappear link and confirm the Vanish state has the RemoveSelf action set.
Adding Damage Handling to the Player
At this point, we’ve achieved the goal of this part: “the player can defeat enemies by shooting them.”
However, there’s still a problem: the enemies already have an attack hitbox, but the player currently shows no reaction when taking damage.
On top of that, since the player also has an attack hitbox, they can currently defeat enemies just by colliding with them.
Let’s adjust the player’s settings so that:
-
The player reacts when taking damage.
-
A “damage taken” animation is played.
-
Invincibility time is applied after being hit.
-
The hitbox and attack box are tuned properly.
-
Finally, we’ll add the corresponding behavior in the Visual Script.
Adjusting Invincibility Time in BaseSettings
Next, let’s configure the invincibility time.
Since this is for the player, it’s best to give them a slightly longer invincibility duration. We’ll set it to 1 second with a blink interval of 0.2 seconds.
Additionally, we should disable the hitbox during invincibility.
Even though the player won’t lose HP during this time, the system would still register collisions with attacks unless we disable the hitbox.
-
Select the BaseSettings node.
-
In the Inspector, change Blink Duration from 1.0
to 0.2
.
-
Expand the Disabled Hit Collision List (Array…) located just below the Blink Interval property, then click + Add Element.
-
Click and set it to New DisableCollisionData.
-
Once it changes to DisableCollisionData, click it to expand.
-
Press the Assign button.
-
A node selection window will appear. Choose HitCollision and press OK.
Now, whenever the player takes damage, their hitbox will be disabled for 1 second.