In this part, you will create the ice punch spell and bullet.
We will not be going through all the steps together. Instead, you will have to look at the basic fire spell and bullet and use them as a reference to create the ice spell.
We’ll start by giving you a list of requirements for how the spell should work, and we invite you to use those to try and code the spell yourself.
Requirements are the starting point for coding any game mechanic. It’s a precise list of everything that code should do.
Below, you will find more tips and steps to help you complete the task. We’ll gradually share more details up to the complete solution. Feel free to peek ahead anytime if you get stuck.
Your task is to add the ice punch spell to the game.
The spell should work like this:
3
damage.Note that we gave names to this project’s collision layers and masks. You can see the names in the Inspector by clicking … to the right of collision layers or masks.
You will see that mobs are on layer 2
and walls on layer
5
.
Here is what the finished spell scene should look like.
Your bullet scene should look like this.
The blue rectangle is the bullet’s hitbox. It will cause it to collide with walls and damage enemies, while the thin blue square that encloses the sprite is due to the presence of the particle node.
You can find all the sprites and particles to use for a given
mechanic in the corresponding folder. For example, you will find the ice
fists in the spells/ice_punch/
directory.
We recommend trying to create the spell and bullet on your own by
looking at the provided SpellBasicFire.tscn
and
Fireball.tscn
scenes.
Below, you will find some information to start creating a new spell and bullet. If you continue down the guide, you will find a growing amount of information, up to the complete code explanations. However, it will still not be a linear, step-by-step solution.
If you ever need the complete solution, you may open the
godot-final-game-completed/
project, where you will find
the finished code with detailed comments.
While coding your spell, you will want to test it with the player
character. Open the Robot.tscn
scene, select the
SpellHolder node, and assign your spell scene to its Spell
Scene property in the Inspector.
To get started, we recommend that you create the spell scene before the bullet because you can always test a spell with any existing bullet in the project.
You can create the ice spell and make it fire the provided fireball first. That will allow you to ensure that the spell works as intended. Then, you can create the ice bullet scene and assign it to the ice spell to complete the task.
To create a new spell, you need to inherit the Spell scene.
You can right-click on the Spell.tscn
file and select
New Inherited Scene.
You will then need to give your node a new script that inherits the Spell class.
Finally, you will have to assign a bullet to the spell in the Inspector.
For the bullet, it’s a bit different because we did not provide a base scene to inherit.
You need to create the bullet scene from scratch with an
node as the root. You then need to assign it a new script that inherits the Bullet class.We then invite you to read the code in the Bullet.gd
and
the Spell.gd
scripts to figure out which functions you need
to override in your inherited scripts.
Please give it a try on your own and come back if and when you get completely stuck. Note that there is no shame and coming back to get more information. What’s most important is that you first try your own because it will train your code reading and problem-solving skills.
The provided basic fires spell fires automatically on a cooldown. As
long as the player keeps to shoot action pressed. to achieve that, we
use the _physics_process()
function, although the
_process()
function would also work.
How can we make the bullet shoot only on the frame the player presses down the shoot action?
You have two options:
Input.is_action_just_pressed()
like you did for the jump in
the side-scroller series._unhandled_input()
function and call
event.is_action_pressed()
.Either approach is completely fine.
Here’s one solution to make the spell fire only upon pressing the shoot action.
We still want a cooldown: we don’t want the player to be able to frantically click to shoot many high-damage ice fists.
Otherwise, the spell would be a little too imbalanced.
# You can either use the unhandled input function or a process function with
# Input.is_action_just_pressed() to achieve this. Either is fine.
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("shoot") and _cooldown_timer.is_stopped():
_cooldown_timer.start()
shoot()
For the ice fist bullet, you want to create a scene with an ice.png
sprite, an AudioStreamPlayer2D
, and an instance of the
IceSplashParticles2D.tscn
scene.
Code-wise, you need to emit the particles using the
Particles2D.emitting
property. You need to do that when the
bullet collides with something.
You could override the Bullet._hit_body()
function, but
it is intended to deal damage to entities with a function named
take_damage()
.
Instead, we want to override the _destroy()
function as
it gets called whenever the bullet hits something.
Our version emits the particles but also hides the ice fist and disables the bullet so that it stops moving and damaging enemies.
func _destroy() -> void:
_disable()
_particles.emitting = true
_sprite.hide()
_audio.play()
Lastly, we need to call queue_free()
on the bullet so it
does not linger invisibly in the game.
Otherwise, if the game session gets long, the invisible bullets will accumulate and eventually start to hurt performance.
We want to give the particles enough time to finish emitting before
calling queue_free()
.
The trick we found was to wait for the audio to finish playing. We
connected the AudioStreamPlayer2D
’s finished signal to the
queue_free()
function.
func _ready() -> void:
_audio.connect("finished", self, "queue_free")
A good alternative would be to use a timer with its
timeout
signal and start the timer in the
_destroy()
function.
This covers all the problems involved in making this bullet work and spell work.
In this series, you will find many challenges for additional practice.
We invite you to do all the ones that you find cool. You can do them now or at the end of the series, where you will find them all listed again.
The more challenges you take, the more practice you get, and the more you improve your skills, the more unique your final project will be.
Also, feel free to go further and come up with entirely new mechanics or variations!
Do you want to make a bullet move in a spiral? Go for it! Don’t hesitate to look for code snippets online or to ask us.
Challenge: Can you make an ice fist bullet that
starts very slowly and accelerates (hint: you need to change its
speed
)?
Challenge: Can you make a fire spell that shoots bullets rapidly (hint: it’s a variation of the provided SpellBasicFire)?
Challenge: Can you make an ice spell that shoots
three ice fists in a cone instead of just one (hint: you need to
override the shoot()
function and use a for
loop)?
In the next guide, we will work on a pickup for the player to loot and equip the ice spell.