Setting up the base code for my game is pretty easy. After making my other ones, I put together a basic template I could use to make starting a new game as simple as could be. However, even with that basic template put together I had to do a few things: Make it so my cyborg could move around the map, which included running left or right or jumping, akin to a platformer like Cosmic Horizons. Putting those things into the game was pretty straightforward, but as soon as I had those things in, I realized I had the same two bugs (from Cosmic Horizons) that I had never solved because I had no clue at the time and I wanted to make sure to get my thesis finished in time. I’ll talk about each of those problems now.
Problem 1: If I tried to hold down a key, then jump, pressing the jump key would overwrite me holding down the key and the character would stop moving horizontally. Functionally this meant if I was running along a platform and wanted to jump to another platform, I would have to run to the edge, jump, let go of the horizontal key and then re-press it. Obviously this is a completely ridiculous skill requirement for the game because it feels like the game isn’t doing what you’re telling it to do and leads to extreme frustration. Although I had a pygame function that was supposed to allow me to hold keys, it wasn’t able to understand that I was still holding the key down if I pressed any other key. To solve this, I had to explore the event function in pygame and really dig into what my key presses meant and how the game was recording them.
I learned that each type of event has a certain type (KEYUP is 1, KEYDOWN is 2, MOUSEBUTTONUP is 3, etc.). I also learned that the game recognized I was holding the key down, but the KEYDOWN event wasn’t being added to the event queue if I pressed another button. Essentially (I’m pretty sure) when I had a button pressed down it would automatically add this event to the event queue over and over as it went through that loop, but when I pressed then released another key, the KEYUP event would run through the event loop and since it was a different event type than KEYDOWN, me holding a button would be lost. With this realization, I knew I simply had to find a way to add a KEYDOWN event to the event queue as long as I was holding down a button. To do this I added in a tiny bit of code: if the next event in the event queue is not a KEYDOWN event, and I just had a KEYDOWN event, add a KEYDOWN event to the queue. Now, this means if I was pressing a button that button press should be added to the event queue again. However, what if I release the button between the time it add the event and the next loop? This is fixed because I look at all keys being held as I enter the event loop, so even though I have an event type KEYDOWN, I won’t actually run that button press unless it’s still being held. Perfect (and it’s worked perfectly since I’ve implemented it. Wonderfully fantastic quality of life change for the game). Should also keep the game feeling smooth and quick.
Problem 2: I didn’t even know this was a problem in Cosmic Horizons until I saw people try the game out on different computers – the game was running differently based on how quickly the computers were capable of running, so slower computers and faster computers completely changed the feel of the game. Even when I set my FPS (gating the maximum speed), slower computers still ran the game too slow. I had no clue how to fix this, so I went to the internet and started doing a bunch of research. Apparently (unsurprisingly in retrospect), this is a pretty normal, common problem for games that people figured out how to solve forever ago. Basically, you want the game to run at some set FPS, right? In pygame, this is naturally gated by the FPS function that provides an upper limit to FPS (for my game this is 100 because I like 10s). To solve the other side, though, you need to understand what’s happening that’s causing the game to run slower.
The game has to run through three separate phases as it runs -> the event loop, the update loop and the drawing loop. The event loop handles any user input. The update loop changes anything that should be changed, like the cyborg changing position if an event had occurred. The drawing loop puts it all on the screen for the player to see. And, of course, all of these things take time. The faster a processor, the faster is can do all this stuff. Slower computers, however, just can’t keep up. And even if you want the game running at 100 FPS, some are only going to be able to hit 60 or 30 FPS. To account for this, we need to look at the time it takes for the computer to completely one full loop through all these phases, which we’ll call our time delta (really, really glad I had a physics background for this stuff). Let’s turn to an example to make this a little easier to understand.
If I want my cyborg to run across the screen at 10 pixels per loop at 100 FPS, this means the screen should be updating 100 times every second, or one frame every 10 milliseconds. At ten pixels per loop, and one loop every 10 milliseconds, my cyborg has a speed of 10 pixels/10 milliseconds, or 1 pixel/ms. But what if I can only run the game at 50 FPS? Well that means my time through one loop is going to be doubled – it will take 20 milliseconds for it to complete a loop, compared to the 10 ms at 100 FPS. But that doesn’t mean my speed needs to change – I can still be running at 10 pixels per 10 milliseconds. All this means is that the distance my cyborg needs to be traveling while running at 50 FPS is twice as much, so it should be moving 20 pixels per frame, instead of 10. However, this speed isn’t any different since 20 pixels/20 milliseconds is the same speed: 1 pixel/ms. So what I, as the programmer, need to do it establish 1) the time it takes to run through one loop and 2) the standard speed at which I want the cyborg to run. And then my equation ends up looking like this: cyborgXPos += (distance I want to travel per standard frame) * (ratio of actual time per expected time). I can then shift the expected time over and get cyborgXPos += (distance to travel over expected time) * actual time, where the first part (distance to travel over expected time) is nothing more than the cyborg’s horizontal speed. This means we get the simple equation cyborgXPos += cyborgXSpeed * time between frames.
I can then apply this across the board to all movement – I know what I want my base time to be (10 milliseconds if at 100 FPS), and I know the distance I want to travel in each frame at 100 FPS. And then I just need to calculate the actual time spent iterating through the loop and multiply that with my speed. And that’s how I solved my computer speed issues.