Line by Line

WIP Report: Space Shooter Game

Posted August 26, 2019

Tags: programming, video games, WIP report




I feel like a weight has been lifted from my shoulders. Two weights, actually, but I already wrote about the other one the other day so let's just focus on this one for now.

I haven't posted much about this, but I've been working on a computer game for a while now. In fact, I've been wanting to make video games since I was around ten years old. I remember having a bunch of different ideas written down in notebooks. Some were original, others were what would now be called fan games. One time, I even drew up a plan for a Sonic game that had the player switching on the fly between Sonic, Tails, and Knuckles, with Silver Sonic as the main antagonist.

At some point in my early teens, I picked up a copy of Windows Game Programming for Dummies, about writing games in C++ with DirectX. I intermittently puttered along trying to follow along, but I didn't really understand it (and truth be told, the book's “Here's how this is done, just use the code on the CD-Rom” approach doesn't match my learning style).

About ten years ago, after some modest successes, I decided to get serious and try to learn and understand how to write C++/DirectX games. A few reasonable tech demos later, I decided the best thing to do was to try making a full game. Since I had collision detection more or less figured out (thanks to Ron Levine's swept SAT algorithm for AABBs) but not collision resolution, I decided to make a scrolling shooter. This let me get away with just destroying one or both of any two objects that collide instead of figuring out how they push or bounce off each other.

I worked my way up to a functioning prototype, using filled rectangles as placeholder graphics. There are solid obstacles that one-hit-kill you if you crash into them, and enemies that lower your health if they shoot you or collide with you.

I originally only had a single “level” that was hard-coded into the game's initialization function. I split that out into a separate level initialization function, then added a second (still hard-coded) level with a different layout of obstacles.

Around this time I discovered the SDL library and the Lazy Foo' SDL tutorials. Switching to SDL made so many things so much easier: Windowing, graphics, File I/O, input (including controller support), sound (with a plugin), text display (also with plugin), and (at least in theory) cross-platform compatibility. I can't stress how much simpler this all is than working with Windows and DirectX directly, especially for someone with limited C++ knowledge. Sure, it took a while to get the whole thing switched over, but it was time well spent.

With that done, my next step was to add a state machine to track what the game is doing. Initially, the game only had one state, the main gameplay loop. When you finished a level, it would immediately load the next one. So I added a new state for a placeholder level results screen. It just displays a separate screen and waits for a key press before starting the next level.

And that's when I encountered The Bug.

Okay, I'm being dramatic. But it drove me crazy trying to find it. Everything seemed to work fine on level 1, but on level 2, the player would occasionally just die for no apparent reason, even at full health. Figuring that it was a false collision, I set obstacles to be destroyed along with the player after a crash. This revealed that the player was, indeed, colliding with obstacles that were off to the left of where the player actually was.

Always the left, it seemed. You might already be guessing what I missed.

It turned out that the static obstacles had a horizontal velocity set. I couldn't see it in testing because the main gameplay loop never bothered to update the positions, since obstacles aren't supposed to move anyway. I spent so long checking my implementation of the collision algorithm for bugs, and finding none, that it took me forever to think of just printing out the collided-with obstacle's x and y positions and velocities.

Printing those to console revealed that the obstacles always seemed to have a horizontal velocity of 64. I'm not sure why it was always 64. It turned out that in the level initialization function, I was setting up a bunch of obstacles but never zeroing out their velocities. Once I added that, the bug disappeared.

With that figured out, I figured the next step was to go back to the state machine and add another state, this time a title screen. Like the level results screen, this would wait for player input before starting the level, but it always loads the first level.

Great, but this revealed another problem. Not a bug, this time; just some messy code. I'm using a switch statement to check which state the game is in and run the appropriate logic. I also have to run a loop to get all the input events from SDL and handle them appropriately. I can't put the main switch inside the event loop, because then it would only run when there was an event. So I put the event loop inside the switch, instead: Each case had its own event loop that handled only the events relevant to that game state.

I originally didn't figure this was a problem, because most of the events would not be treated the same from one state to another. But even with only three states, I noticed mode duplicated code than I was comfortable with.

There was one other option: I could loop through the events before the switch, respond to the ones that were the same across the board, and then store the rest in an array or something that the individual states' cases can access. This is probably the cleanest way of handling it. The reason I didn't do that before is that arrays in C++ have a fixed size. I mostly do Web development, so I'm more familiar with PHP and JavaScript, which let you add an element onto the end of an array whenever you please. In C++, though, you have to guess in advance. If I make the array too small, I run out of room and some events get dropped. Players of video games tend to dislike dropped inputs, I'm told.

What I didn't know about, at the time I made that decision, was the vector. Confusingly, C++ vectors are neither crocodiles nor directions paired with magnitudes, but rather “sequence containers representing arrays that can change in size,” according to cplusplus.com. In other words, it lets me start with 0 items and add a new one to the end whenever I please, which is exactly what I needed. So I learned something about C++. Neat!

So now the event loop is set up as it should be, with unhandled events stored in a vector for the individual states to use if needed.

And that brings us to the present. Here's what all that looks like:


(Direct Link: MP4, WebM)

So what's next? Most likely, I'll add a pause menu game state. After that, I'm not sure which of the many tasks I'll pick, but it's likely to be either splitting the graphics into a separate function from the main game loop or loading levels from files rather than hard-coding them. At some point I'll be looking into whether I can convert some of my arrays to vectors or some other container type.

And, y'know, I also have a comic to work on.

I'll let you know when I have anything interesting to report.