After a week of programming cursed C++ and OpenGL I have submitted my entry HARDPOINT to the itch.io BulletHell game jam as of last night. You can download it here and give it a shot yourself or just watch the video to see it in action below.
Some random technical takeaways from this jam in no particular order:
- I really liked working with vector graphics for this entry. I would make 2D models in blender and then wrote a custom python script which would convert them from .obj into baked C++ source. This made it really easy to keep the graphics crisp, keep iteration times down and not require any external assets.
- Finite State Machines are really great for handling game events that you want to be timed and animated. For example, when the player is destroyed the game state changes to handle that scenario. It pops up scroll text to the screen, waits for a period of time before re-spawning the player with a shield. FSM states can be chained too to produce more complex events like self destructing the boss, scrolling the background faster with a message, and then generating a new boss. I’m going to use FSMs a lot more in future game projects.
- When dealing with rotations in 2D it is important to keep them all in the same representation if you can. I ended up with four different ways of representing rotations:
- An angle as a floating point value (turned into basis using sin/cos)
- A normal direction as a vector (where an orthogonal basis can be formed with its cross product)
- Two vectors forming an orthogonal basis (lets you invert an axis to flip the handedness of the matrix)
- A 4×4 matrix as used by OpenGL for transformations.
- It was possible to convert between some representations and not others, and some representations might be more efficient for certain purposes. Trying to unify this would have lead to a lot less special case code and weird spaghetti code to work with the data in the way I wanted to. In the end the two-vector basis seemed like a pretty solid representation to pick. Every part of the boss for instance is represented by one plus and offset from the boss origin.
- For a game jam don’t get too hung up on performance early on if its not a visible issue. This would be be bad advice generally there is only so much time you have available in a gamejam. One of the most inefficient parts of the code I wrote was the collision detection for player bullets and boss parts. I test every single bullet against every single boss part, which is an O(N*M) operation and scales terribly. I would rather have solved this by keeping an AABB tree for all of the parts inside “boss space”, transform each bullet into that space and test against the AABB tree. However… the performance seems really okay as it is and let me get on with other tasks, such as making the game more fun. If I saw any slowdown, then I might have tried to solve this issue.
- I used SFXR for some of the sound effects which is really simple but generates so much aliasing I had to use a low-pass filter in audacity to tame it. Also beware that there is a bug in SFXR which will generate an incorrect .wav DATA chunk size when producing files at a sample rate of 22050. I wrestled with myself about fixing this issue, but lucky I made the right choice to just render at 44100 and move onto a more important task.