Now it's time to focus on the projects I've been making in Godot.
I started this project as a break from working in UE5, with much of the inspiration coming from a game called Titan Souls and to experiment with making a vertical slice. For this project, I rebuilt my custom action system, created with C++ in UE5 using GDScript, and used it to build the player and enemy actions/behaviors. I also worked on adding art, sounds, and effects to the important aspects of the game.
The biggest struggle of this project was learning Godot and getting comfortable making complex systems using GDScript. One of the first challenges from these struggles was rebuilding the action system. Instead of trying to copy the system, I began by identifying its core purpose and came up with a couple of things.
First, it was created to have a consistent interface for actions' start, update, and ending. The second part, and the one that became the most complex was keeping it completely separate from the owner. Having actions be separate from the owner was key for avoiding dependencies between actions and having actions that only work for one type of character (player vs. enemy characters), two things that could cause headaches in the future.
The first point was simple: construct a base class with start, update, and end functions. Anything all actions need to do can be put in the base class and then each action can be implemented in their individual scripts. Overall this was a simple solution with one small downside, sometimes the location where the base functions are called in the child can impact how the ability system behaves. For example, in some abilities if "super.end()" is placed too early in the function it can cause problems with how the whole system behaves.
A much bigger struggle came from the second point. In UE5 I solved this problem by using an action manager as an in-between for the actions and the owner, but quickly found this approach was causing unnecessary and unmanageable complexity in Godot. I decided to cut the manager I had been building and start from scratch, working on identifying what purpose the manager served and how I could fill that void.
One role of the manager was to determine if an action trying to start was blocked by a different action already being performed. The second role was to handle everything about actions so the owner wouldn't need to know about them. To solve both issues, I moved some functionality into the action base class and the rest into the character base class. Now the action base class has a function to determine if it is blocked from starting by another ongoing action. The character base class is now responsible for triggering actions and keeping a list of all actions that can be performed. This isn't a perfect solution but worked well for this project.
Overall, I was very happy with the result of this project and it may be one I come back to in the future.
My next project in Godot was a 2D platformer where I wanted to keep things simple. The focus was to create a player controller that felt good, the "recoil" mechanic, and make levels that tested all of the actions the player could make.
Player Controller
Starting with the player controller, I made a simple system where the player could fall, move left and right, and jump. During this time, I also made basic floors and a death system for when the player missed a jump. Adding onto this initial setup, I added coyote time and jump memory (also called jump buffering) to improve how responsive the player controller felt by not requiring the player to have perfect timing when jumping.
Coyote time is the small window where the player just walked off the edge of a platform. Sometimes when a player waits until the last possible moment to jump it doesn't trigger because they are in this coyote time window and it feels like the game missed the jump input. To fix this problem I allow the jump input to initiate a jump if pressed during this coyote time window.
Jump memory is another useful addition to the player controller and is looking at the other side of a player's interaction with floors. In this case, a player may hit the jump input just before landing on the floor. Without jump memory, this input would not trigger a jump, making it feel like it was lost. To resolve this, jump memory will "remember" the jump input for a short time and if the player collides with the floor during this window it will immediately trigger a jump.
Level Generation
For this project, I also decided to add PCG for making levels. Initially, I tried to make levels from scratch, but I ran into problems making it interesting. Many of the obstacles that this first system created felt the same, and there was no flow or meaningful repetition to the challenges. Because of this, I pivoted to making a generator that used "sequences" and put them together.
One aspect of this generator I wanted to focus on was the difficulty curve, easing players into the game and then giving them an up-and-down in difficulty as they continued to play. To get this working, I first had to separate all of the sequences into one of three difficulties: easy, medium, and hard. Then, using a list of numbers that correspond to difficulties, it could generate a level that varied in difficulty exactly how I wanted it to.
That is where I ended work on this project to pursue other ideas.
Comentários