Section 8: Finishing Touches
In this section, we'll add finishing touches to your game by adding a reward system, adding skills, such as bombs and lightning, and then finally applying effects with shaders and particles.
Section Intro - Finishing Touches
There are no notes for this lecture.
Preparing the Reward
In this lecture, we took the time to prepare the UI to display the reward and treasure chest to the player. We had the opportunity to explore how to work with spritesheets too. There are no notes for this lecture.
Creating a Reward Resource
In this lecture, we created a resource for storing a reward that will be given to a player when they open a treasure chest. Using a resource is great for when you need to share the same piece of information to multiple nodes across scenes. We created a class called RewardResource:
In this class, we're storing four pieces of information which is the texture that will be displayed in the UI, a description of the reward, the stat to update, and the value to update the stat by.
In addition to creating the resource, we also toggled the icon above the treasure chest. To do this, we registered methods to the BodyEntered
and BodyExited
signals.
From these methods, we're just toggling the visibility, which is why we're using lambda functions.
Applying Rewards
In this lecture, we applied the reward to the player when they opened the chest. Most of what we did are things we've done before. The newest thing we learned was about the Monitoring
property on an Area3D
node. This property tells an area node that it should monitor for other area nodes or bodies.
By default, this property is enabled, but you will want to disable it so that the player can't redeem the same reward multiple times. We did so in our class.
Preparing the Bomb
In this lecture, we prepared the bomb by adding two animations for expanding the bomb and the explosion itself. There are no notes for this lecture.
Exploding the Bomb
In this lecture, we learned how to make the bomb explode by instantiating it in our game. First, we had to switch from the Expand
animation to the Explosion
animation. To do this, we subscribed to the AnimationFinished
signal from the node responsible for playing the animation like so:
Before playing the Explosion
animation, we're checking if the current animation that just finished playing is the Expand
animation with the help of the animName
parameter, which contains the current animation name. If so, we're switching to the Explosion
animation. Otherwise, we're deleting the bomb since after a bomb explodes, it should disappear from the scene.
In the PlayerDashState
class, we're instantiating the bomb right when the state is entered. First, we need a reference to the scene.
The PackedScene
is the class you'll want to use when trying to store a scene in a variable. Next, we instantiated the bomb with the following code:
To instantiate a scene, we're using the Instantiate
method on the scene. In addition, we can pass in a generic to specify the root node's type.
Afterward, we're adding the scene to the root node of our scene since it's not automatically added after instantiation. We can use the AddChild
method to perform this task, which accepts the scene to add.
Lastly, we're positioning the bomb in the same position as the player when they begin their dash.
Using Interfaces
In this lecture, we fixed an issue with only being ab le to damage enemies with attacks and abilities by using interfaces. Interfaces are a feature in C# that are similar to abstract classes. They allow us to describe the methods found in a class. Unlike abstract classes, methods in interfaces are not allowed to have implementations, thus forcing child classes to provide the implementation.
To define interfaces, you use the Interface
keyword followed by the name of the hitbox.
In this example, we're defining an interface called IHitbox
. It's common practice to start interface names with a letter I
to help other developers identify it as an interface. Secondly, methods in an interface, don't contain an implementation, so it's perfectly fine to end a method definition with a ;
instead of curly brackets.
To apply an interface, you must add the interface with the inherited classes like so:
Even though it's not allowed to inherit from multiple classes, it's acceptable to add multiple interfaces. All you have to do is comma separate the classes and interfaces like the example above.
Thunder Combo Damage
In this lecture, we took the time to add another ability for performing a lightning attack when a player performs a successful combo attack. In the PlayerAttackState
, we're checking if all combo attacks were performed by comparing the current attack and the maximum combo attack.
Afterward, we just instantiate the lightning scene and then move it over to the enemy's current position.
Creating a Shader
In this lecture, we learned how to create a shader. Shaders are programs for manipulating the graphics in our game. Most game engines use shader languages. Godot is no exception. It provides a language that is similar to GLSL.
For our first shader, we decided to create a shader to change the color of a sprite. In our shader file, we first set the rendor mode to spatial
since we're trying to manipulate a 3D node.
Next, we enabled the unshaded
mode to prevent lighting from affecting our shader and the cull_disabled
mode to apply the shader to both sides of a sprite.
Afterward, we added uniform
variables to allow them to be modified outside of the shader. First, we have a variable for keeping track of if the shader should be applied to the image. Secondly, we're storing a color. Lastly, we're storing the last texture stored in the sprite.
Afterward, we defined a fragment()
function, which gives us access to each pixel in the sprite. In this function, we're storing the current color of the pixel with the help of the UV
variable, which stores the coordinate of a specific pixel in the sprite.
Once we have the color, we're setting the ALPA
to the current alpha of the image, then we proceed to check if the shader should be active. If it is, we'll override the color with the flash_color
variable. Otherwise, we're using the original color of the image.
Dynamically Applying a Shader
In this lecture, we're dynamically applying the shader to our sprites. First, we need a reference to the shader. To store the reference, we created a field with the ShaderMaterial
type.
Afterward, to store the shader, we're accessing the MaterialOverlay
property, which contains our shader. Since materials can store different types of resources, we're casting the property to ShaderMaterial
so that it's compatible with our variable.
Next, we subscribed to the TextureChanged
signal to tell us when the texture changes on the sprite from the animation player node.
From the method handler, we updated the shader's tex
variable with the SetShaderParameter
method.
We used the same method for setting the active
parameter too.
Stunning Enemies
In this lecture, we updated our interface to allow our attacks or abilities to stun the enemy. In the interface, we added the CanStun
method like so:
We applied this method in both the AttackHitbox
and AbilityHitbox
classes like so:
Adding Fire Particles
In this lecture, we learned how to use Godot's particle system to create a fire. There are no notes for this lecture.
Dash Cooldowns
In this lecture, we applied a dash cooldown to prevent players from constantly dashing. To perform this task, we added a delegate with the Func
type. Unlike the Action
type, we're allowed to return values from our functions. The return type must be specified as a generic.
By default, we're always going to assume the state can be transitioned into by calling this method. If a state wants to override this method, they can. In our dash state, we used the IsStopped
method to prevent the player from transitioning into this state while the timer is running.
Lastly, in our state machine, we're calling this method to check if we can transition into the new state.
Last updated