The Game Crushed Back
The past month of working on Bit crusher has been quite the intense reality check. I ended off my last post being like, "I actually am super comfortable with Godot now and I feel like I can really start cooking." Total lie; the last month has just been like speed bump after speed bump after speed bump after speed bump.
It has been nightmarish, but in a really fun way. Like "God, that was awful…let's do it again."
The past month, the priorities were intended to be number one: make the base game "good enough." Basically just iterating upon the core gameplay loop of my game. Number 2 was to work on implementing the complexity reduction system that I mentioned in the last dev log, and then number three, I kind of wanted to just hone in a bit on the style of Bit Crusher. I'm not done iterating upon the style, but I just kind of wanted to get a better idea of the direction in which I wanted to take it.
Those were my main three goals initially, and then I kind of had to force a fourth goal in there: fixing my horrible code and design choices. Everything was a complete mess, and I think it's a bit less of a mess now, but that's kind of up for debate.
Quick Clean Up
Before I could get started on the goals I had set up for this part of my game development cycle, I wanted to fix some random issues that my game was having. Hence why I had "making my game good enough" as the first priority. Before moving on to each phase of my game development cycle, I am hoping I can do this before each step, so that I am at least 80% positive that the work I'm leaving behind is solid.
First of all, my game was way too fast. It didn't really leave a lot of room for skillful action. Because everything was moving so fast, it was basically impossible to be good at. So I had to tone down the speed for a lot of the player movement and enemy movement.
There was also this weird behavior that enemies and the player were exhibiting, where they moved a lot faster when going in a negative direction as opposed to a positive one. At the time, I didn't want to deal with this issue (the speed thing was kind of done first in the hopes that it would make this behavior less noticeable), but eventually I had to. The floating point numbers within the speed vectors being added were causing unexpected behavior after the x and y positions were converted to integers, but at the time, I wasn't exactly sure of what was happening. So my solution was to make the speed values integers before adding them: boom solved.
In addition to that, the collision detection was really bad. Part of that was due to the high speed of things, so that had already been partially mitigated. But outside of that, I didn't really know what the issue was, so I just made the collision boxes bigger. Easy fix.
I also wanted visual indicators of in-game events, like for when the player got hit. What I had for that was color changes for the player and the enemy when that happened, as well as when the player's defensive item was activated. I plan on implementing cool down systems for specific defensive and offensive weapons, and I wanted to start now with making those states visible within the game itself, as opposed to the UI.
Before I could move on, I started noticing this weird error where my player was taking damage twice but it wouldn't update twice? I was just getting notified of the player being hit twice. It took me the better part of an entire hour to find out I had another instance of the player scene hidden beneath the root scene. It was in my Project Settings as a global object for reasons far beyond my understanding.
Mathematical Complexity Reduction
So the first real goal that I wanted to tackle after this quick housekeeping session was implementing the mathematical complexity reduction with bit shifting that I had always been thinking about.
Compatibility is a pretty big deal for me. I would like for anyone on any machine to be able to play this. If someone had a Nokia phone, I was hoping I could get them to be part of the club.
The concept behind what I was attempting to do was this: close your eyes. What is 3 plus 4? Easy. 9 plus 6, easy too. 17 plus 37, a bit harder but doable. 296 plus 137, definitely harder. See how the math gets more difficult with each digit added? Computers work the same way, but the digit increases in binary, not in the base-10 arithmetic that we use. If I could force the computer to work at a lower digit count, I was under the impression that I could make huge strides in optimization.
To make sure I wasn't being unreasonable, I went to Reddit for some tips. I posted about the system I wanted to implement and got flamed, especially for doing collision math this way. But I was still convinced that doing collision and movement math with positions shifted down by a certain factor could work.
The idea was to perform collision and movement calculations with positions shifted down, then shift the values back up when rendering them on the screen. Shifting down would let me work with smaller numbers and avoid floats, making arithmetic simpler and potentially improving performance by reducing computational complexity.
I was a little worried, though, because the player might feel cheated if what they see on screen doesn't match the collision detection happening in the physics engine.
This system also wasn't very Godot-friendly. People on Reddit pointed me toward Vector2i, which I started using, but a lot of Vector2 methods I depended on for enemy and player movement were unusable without using Vector2. Godot also doesn't easily recognize custom variables in enemy scenes unless they inherit from a shared class, which was important since I was storing the "raw position" of the game entities as a separate variable within each entity. Accessing that additional variable was doable, but a bit unsafe.
As I kept working to implement this system, I started to question whether it was even worth it. For a 2D game, is there really that much heavy computation happening in the first place?
I also realized that while I wanted to reduce collision complexity with this system, I wasn't even applying it to Area2D.
At that point, I gave up. I'm not using this system. It's not worth it, and a full implementation across the entire game would be a nightmare.
Instead, I'll make it a class and keep it visual. I'll still use Vector2i, and I'll maintain a separate Vector2i for position, converting it later when needed. Now I have a class for it. I might organize my classes into a dedicated folder instead of mixing them with enemy-specific and weapon-specific code. Or maybe not.
Style
So with the complexity reduction system kind of canned, I wanted to move on to something that I was at least kind of positive I wouldn't be canning, which was the style I wanted to pursue.
I want it to be very very simple visually, where it's just simple shapes fighting each other, like Agar.io. A lot of old arcade games resorted to a style like that due to hardware limitations. There's also diep.io where you level up your turret and move around, becoming different kinds of turrets. Similar simple shapes only vibe.
I have so much admiration for the style of modern 2D pixel art games, stuff like Enter the Gungeon, Deltarune, and Geometry Dash. I know that one doesn't exactly count, but still. Those games have so much swag. But I also wanted to commit to the classic game feel, where the pixelation is actually consistent. The reason pixelation was consistent back in the day was because they were working with lower resolution screens, but there are modern games that commit to that as well. One of the best examples is Animal Well. So that is the direction I plan on taking it: simple shapes and geometry behind an old ass monitor.
So initially, I tested a bunch of random stuff, specifically things I had already created in the previous proof of concept that I've mentioned a few times. One of the first things I implemented was a trail system where enemies and the player leave a trail as they move.
The trail was pretty easy. I've done that before. It's just a list of the entity's previous positions and then drawing shapes at those positions.
I also had another simple trick for making the game look "old-school," which were lines over the screen. I had the lines over the screen for every eight pixels, as that is something Animal Well has as well (but admittedly way cooler). I also added another set of lines in the same exact place, however they quickly move down the screen and rubber band back to their original position after moving a short distance, to once again further hone in on the old school monitor vibe.
Something I didn't have in the old proof of concept was a shifting background that I wanted to implement as well. I also added lines over the screen, with each line being one pixel wide and spaced at a factor of eight to somewhat replicate the look of older displays where you'd see those scanline-like artifacts.
The background was more of a challenge because I had never worked with the GLSL shader system in Godot. It was completely new to me. At first, I was applying everything to the vertex, which was not correct. Once I figured out what I was actually supposed to apply it to, it worked as intended.
In addition, I took the low precision system I had and applied it to the trail and the shifting background. And boom. That's a good start to my style.
…alright something feels wrong.
The Walk Back
It was around this time in my development cycle where I finally couldn't ignore the bad feeling that had been creeping up on me about a lot of the choices I was making. It just felt like I was taking a very…weird route.
Not only was I building up a lot of technical debt, with having no folders for specific code files and assets, and just no documentation in general, I was also forcing Godot into very strange behavior that it doesn't really support, and jumping through weird hoops for results.
Outside of the usual technical debt, I was also making just bad game design choices. The visual style that I had at the time that I called "good enough" heavily muddled the visual information that was supposed to be delivered to the player to inform them on what was actually going on on the screen. There was just so much random shit that it got in the way of knowing where the enemy was and how close I was to hitting them.
In addition, there wasn't enough reaction from the game entities in response to specific events such as a player getting hit or an enemy getting hit. This was especially still the case for my collision boxes, which were heavily hindered by my having a very low physics tick count at the time (in an attempt to truly imitate old school arcade games).
This is where I inserted the aforementioned fourth goal of fixing my stupid mistakes and cleaning up my code. I knew I would pay for my technical debt later. I was already paying for it with two dev streams where it was 90% just me staring at my computer and saying to myself, "what on earth is happening?" So to try and prevent that in the future, I went back and fixed a bunch of stuff.
When converting the position to make it so that entities wrap around the screen, I made sure to round the position instead of doing a direct int conversion, which floors it toward zero. So if the y position was 10.1, it would go down to 10, and if it was 10.9, it would also go to 10. I had to explicitly round the numbers instead of allowing the implicit int conversion to truncate the decimal point to make sure that the speed was consistent.
I also modulated a bunch of shared behavior between game entities into a class so that I wouldn't have to spend so much time redoing code I've written a million times before. Having shared classes makes it a lot easier to create prefabs of enemies and items.
I then toned down the visual flair a bit and made sure that it didn't get in the way of conveying information to the player about what's happening in the game. Part of the way I did that was expanding the size of the trail and making it so that the part of the trail closer to the player is black. This allows the trail to still create an interesting effect while also centralizing the player's attention on the darkest part of the screen, which contains the player and the enemies. Now, my visual flair was adding swag AND improving the game's readability.
I also had to make everything delta-based. The only reason I had to do that was because I had to raise the physics tick rate to improve collision detection, which made everything much faster since I wasn't using delta before. That was a nightmare to figure out.
Once I was done with a lot of this cleanup, I went back and made a testable build to figure out if what I had was fun. I made a restart button, implemented player health and a health bar, and added a bunch of things that felt early to implement, but that I needed immediately so I could properly test the game.
I figured out that what I have so far is actually pretty fun. It's still early and nowhere near release. I'm not trying to pull a Pirate Software and say that this is 99% done. If anything, it's less than 10%. But I think I'm working on a good foundation.
After testing a bit, I went back over my original design milestones to ensure their completion.
Conclusion
Okay, well, maybe I'm not perfect as a game dev yet, but regardless, I'm having so much fun with this process. I am even more excited to keep moving forward now that I know a bit more on how to set a good foundation and I also now have a bit more of a foundation built so I can start building the really cool stuff.
Next, I want to further modularize my game so that I set an even better foundation.
Then, to build on that foundation, I want to add more defensive and offensive items so that it's not just this spinning cube and the directional shield. I also want to add rooms with different mechanics, number of enemies, and other stuff.
Once I have those two things figured out, I want to build on the style further. Minor visual changes. I don't want to add much visually yet, but I do want to add audio such as music and sound effects. Not a lot, just a little to refine the proof of concept.