QTE Dev Log #4 - Starting Over
I’m making a recreation of the Excite QTE minigame from Shenmue series of games. In the last blog post, I went over how I overcame perfectionism to develop the game’s menus and scoring system. Since then, I scrapped everything!
Say It Isn’t So!
Perhaps it’s a bit misleading to say I scrapped everything. After all, I’m still committed to finishing this game. Starting from a blank git repository, I’ve built the game from the ground up, copying over assets and minimal code as needed. I now consider the old version to be a very large prototype.
“But Katie, didn’t you say in the last blog post that aiming for perfection was slowing development down to a crawl?”
Yes! Not in the way I initially thought though. Aiming for 70% helped to lower the barrier to entry so I could get started on work. I could have continued work on the prototype version, but I eventually realized when building and bug fixing the game that it would be faster to start from scratch.
The Problems
In the prototype, I had separated out the code between the core logic and the display of information to the user (originally, I called these the Brains and the Looks). However, I had not separated the Godot Engine itself from the core logic. What I mean by this is that to execute any code, I would always need to run the game through Godot first. This made testing essentially impossible, since every component was intermingled with API calls and heavy use of Godot’s signals.
This also had the side effect of restricting what kind of data I could send, since I wasn’t relying on pure Rust anymore. To use the language’s strengths, I needed to separate the game’s logic into modular parts. I also made sure that anything that could be engine-agnostic was engine-agnostic. Now, I could write automated tests to make sure everything was working as expected. Here’s a simplified version of the Lives struct to illustrate my point:
pub struct Lives {
lives: u32,
score_needed: u32,
}
impl Lives {
/// Gets whether the player has run out of lives.
pub const fn game_over(&self) -> bool {
self.lives == 0
}
/// Removes a life from the player.
pub const fn remove_life(&mut self) {
if self.lives > 0 {
self.lives -= 1;
}
}
/// Returns true if the number of lives have just been incremented, false otherwise.
pub const fn try_increment_lives(&mut self, current_score: u32) -> bool {
if current_score >= self.score_needed {
self.score_needed += POINTS_PER_LIFE;
if self.lives < MAX_LIVES {
self.lives += 1;
return true;
}
}
false
}
}
The Lives struct is doing one thing and doing it well. It’s determining based on the current score whether to increment a life, and exposing a method to remove a life from the player too. This makes it easy to automate tests such as:
game_over()is true once all the player’s lives are removed- A life is awarded if enough points are earned
- Lives don’t exceed the maximum specified
After the last blog post, I wanted to re-work the difficulty select screen, which I used as an excuse to improve the menus as a whole. This was good for a while, but out of the blue, a bug (which I still can not explain) showed up. The credits screen refused to hide itself if I went into the difficulty screen first:

The difficulty screen didn’t have this problem if I went to the credits first. It was literally just the credits that were giving me such a hard time, which was just super frustrating.
There were also some other minor things with the prototype that were bothering me, so I made the difficult decision of starting (mostly) from scratch. Good news is that I’m on the latest version of Godot and Godot’s Rust bindings, so it’s also more easy than ever to develop the game.
What’s Changed
First of all, the gameplay has a slightly different UI with new icons to indicate the number of lives a player has left. I think the information is more organized in this state than it was previously, though I feel like the differences between versions are minor.

I also have implemented a screen that shows the results of the previous game to the player after they run out of lives. It has aggregated information from during the game - as well as a means to track the player’s high score. This was super simple to set up after I had organized the game’s data into chunks. I could have serialized the save data using Godot, but since I’m already programming everything in Rust, it made more sense to use the serde and serde_json crates to avoid over-complicating the code.

The Future
I plan on making one more blog post before I release the game, but we’ll see. The last 10% of development takes the other 90% of the time after all, so it’s very possible I’ll be posting to this blog a couple of times before you see a release date set. See you in the next post!