It’s been a while since I talked ’bout the li’l game-development project, which, believe it or not, I am still working on. For example, I just spent the days trying to fix the mess I caused by stupidly refactoring my inventory code.
In fact, I just fiddled the whole day learning how to parse JSON files through RapidJSON. This allowed me to implement 2 features:
Saving, which is automatic, every time you return to the level select screen & collect a diamond. So, basically, anytime any saveable change happens.
For those curious, the game currently saves total gems, level beaten, gem & time scores, & diamonds. I also plan to implement health upgrades, ’mong others (which is why, as you’ll see, Autumn’s max health is down to 2 now).
I don’t know if I mentioned the diamonds yet. They’re just big items that act like DK coins in the Donkey Kong Country series. Like in that game, collecting them is permanent once you get it, whether you die or not. It e’en, as I just mentioned, saves immediately afterward, so if the game happened to crash a li’l afterward, you’d still keep it.
I also changed to title screen to give a menu, allowing for a new save (o’erwrites save file ’pon next save, though not necessarily on using it) & loading. I’m still thinking ’bout how I’m going to implement multiple save files. Ideally, I’d have a menu allowing for as many files saved & opened as one’s file system could hold; but that’d probably require a menu that could access the user’s documents, & I have no idea how I would do that, ’specially in an OS-independent way.
I started saving with JSON files, which were hilariously telegraphed & easy to edit so that one has perfect scores all round. But for reasons I can’t ’splain, I spent hours fiddling with writing binaries & shockingly found a way to do it & read it back correctly. I e’en found a way to compress booleans so that there are 8 bools in a single byte… for some reason. I don’t know—’twas interesting. ’Course, it still wouldn’t be too hard to edit the save file to get oneself a perfect save file (hint: change all the victory/diamond values to FF, all the gem scores & the total funds to 7F & all the time scores to 00).
I did think ’bout fiddling with encryption; but honestly, it doesn’t matter, anyway.
Not particularly important from the player’s perspective, but now the program can directly read the level files spit out by Tiled in runtime, which means I no longer have to copy & paste arrays from JSON files every time I make a change to a level.
I spruced things up a bit in preparation, which delayed things a bit. In addition to the minor changed, like the diamond, the Bad Apples now allowing you to bounce off them like in a true platformer, & the tacky low-res photo background in the sewer section, I added a fence background. Believe it or not, that required me to rewrite the background code so that it could handle non-repeating backgrounds so the fence wouldn’t repeat vertically.
& then there’s the constellation. 1st, note that it slowly scrolls ’long during the whole level, so that the moon is on the right side @ the beginning o’ the level & on the left by the end, rather than looping constantly as if there were many moons in the sky. It’s touches like these that ensure this game won’t be done ’fore I’m 80.
But worse: the constellation background is actually randomly generated when the level’s loaded. This required me to refactor the “background” class so that it could allow other types, such as this constellation type. Logically, this wasn’t hard, since background had a remarkably simple interface: other classes only interacted with it through the common update & render methods, so making a polymorphic system round them was simple. Now the classes are “MapLayer,” “MapLayerImage” for what was “Background,” & “MapLayerConstellation” for the custom constellation. If anything, the only headache was changing the instances in the Map classes vector to pointers to allow for polymorphism. I gave up on trying to get unique_ptrs to work & just stuck with shared_ptrs. ¿Who cares?
Believe it or not, the hard part o’ the constellation background itself was not the random-generator, which was easy, but figuring out how to get an array o’ tiles splayed ’cross the screen so that they scroll the right way when the camera moves. After a day or so, I figured out some combination o’ code from the block-generation code & the background code worked.
I don’t feel too bad ’bout doing all that for just this background in the 1st level, since the actual hard part will work for any tile-based background I may want to do. (Think o’ it like the block-based layer 2 in Super Mario World.)
But there’s a much cooler part o’ the video (which, surprisingly, took much less time): what’s being recorded in that video isn’t actually me playing the game. I did play that exact way once, & recorded the inputs every frame &, @ the end, printed those arrays o’ arrays in a file.
The way input is read for the player has been refactored for this. Rather than directly reading the Input class, the player sprite has an InputComponent, which decides how input’s read. For InputComponentPlayer, it’s based off Input’s button presses, like normal; but InputComponentSequence holds the big array I mentioned & feeds it out to the sprite for movement cues, rather than actual button presses.
This, actually, I think is similar to the way the recording & playback o’ inputs for Mario’s movement on the title screen o’ Super Mario World may’ve worked, for those familiar with Lunar Magic & Super Mario World hacking. ’Cept mine’s much mo’ stable, since the sequence is directly tied to the player sprite, which means that whenever the player’s paused, the sequence readout’s paused, too.
There were amusing bugs when I was developing these, mostly caused by an o’ersight I still need to fix: the SpriteSystem creates a new player sprite during every map transition. This is ’cause different maps may have different player sprites, & the sprite system doesn’t bother to check whether the sprite type doesn’t change ’tween maps. This obviously resets all its data, including the sequence readout & recordings. I kludged this simply by making these static.
The 2 glitches were caused by the recording & readout, respectively. @ 1st only the movements for the last map were saved, which I realized without e’en seeing the glitch happen, simply by the smallness o’ the output file. However, the funnier glitch was the readout 1, wherein I saw Autumn get stuck in a loop in the middle map, constantly jumping up & back down the rightmost sewer hole & running left & then back right, seemingly absentmindedly. @ 1st, I was befuddled by how the sequence seemed to go on fore’er. I don’t know why it took me so long to realize that the sequence was simply being recreated ’cause the sprite was, since ’twas caused by the same thing that caused the previous glitch that I fixed so recently.
I do need to see if I can recreate this glitch (should only require making the InputComponent no longer static) so I can record a video o’ it.
There are still some quirks that I can’t truly fix. 1st, since its recording & readout are static, you can only use them once per program run. Trying ’nother level will cause them to continue from after the last level’s end. Also, pausing has no effect on the sequence, which would actually be useful. What I mean is, if you pause while recording, wait any ’mount o’ time, & then unpause, the sequence won’t record the pause @ all: on playback, it’ll act as if the pause ne’er happened @ all & go immediately from before-pause to after-pause. This is ’cause pausing actually isn’t tied to the player sprite @ all, but the LevelState (which makes sense, since it’s not something the sprite’s doing, but a meta command). Lastly, exiting the message that pops up when the message block in “Blueberry Burroughs” is hit requires a human to close it, e’en on sequence-playback. ’Gain, closing messages isn’t controlled by the player sprite, but the created MessageState. In fact, while the message is up, the player sprite isn’t e’en being updated, since it’s back in the paused LevelState. Hence, how the game pauses. ’Gain, though, this doesn’t affect the sequence, since it’s paused during the message as well.
Anyway, this input-recording business isn’t just for fun. As I mentioned earlier, I wanted to have “Rooftop Rumble” actually have a character hopping ’long the rooftops with you in a race toward the end, also being able to hurt you if she touched you. I settled for a ghost that simply floated forward, impervious to the level; but now, I should have no trouble getting an actual moving racer character as I originally envisioned.
I actually expected to have these videos & this article up sooner, if not for a bunch o’ hitches that got in the way—most o’ which took hours to figure out, but were fixed by simple changes (& were, correspondingly, caused by simple o’ersights).
The 1st was figuring out how to record decent video in the 1st place. I spent days recording o’er 2 dozen videos with different video software @ different resolutions. To allow me to configure screen sizes & modes (full-screen vs. windowed) & saving (’cause writing to files causes lag) without having the memory-wasting IDE open I e’en made up some arguments I can put into the console & added code to the game to read these through the main function’s argv array & adjust based on them on startup.
@ 1st, I could get 2 types o’ videos: small recordings that moved smoothly, but I couldn’t figure out how to not be resized blurrily in YouTube, & videos that were sharp, but had flickering all o’er the place. After fiddling with both problems, I found out some vsync flag I put in the renderer @ start-up caused the flickering. Removing it made recording videos work well.
After that & the pointless random constellation, I should’ve recorded the videos; but for some reason, I had the genius idea to refactor the inventory code to split its integrals from the specific window dressing for the level-select & level states (the different UIs o’ those different screens), possibly in preparation for the o’erworld state. I do think the inventory’s cleaner & easier to work with; but damn was this a pain to do.
Worse, it introduced a game-crashing error that seemed erratic & all o’er the place. After a while, I was able to pin-point it to spawning most sprites in the 8th Y-position o’ “Wasabi Woods.” I’m not kidding: it just-so-happened to affect that level as I wanted to record it & in such seemingly specific places. While most sprites caused the program to crash immediately while entering the level, including the spiky-fruit that was s’posed to be there, the rope & cracking ice cubes worked fine. The hydrant would only cause a crash ’pon waking up—according to my debugger, caused by a method call in its timer. Most o’ the time my debugger wouldn’t give me any info beyond ?s.
This unpredictable behavior could be caused by a simple error I made, which seemed to have nothing to do with this level or these precise values, but had to do with the code I changed for the refactoring, as I thought it should: in simplifying the inventory code, I pushed out its LevelSelect-specific code into LevelSelect, since it didn’t have much, anyway. But I did still update stuff specific to inventory. In moving stuff round, I put the inventory-updating call @ the end o’ the LevelSelect update method. Unfortunately, this violated a rule I made for myself that I apparently keep forgetting: it went after the code that handled changing game states, which meant that the game was deleting LevelSelectState & initializing LevelState, & then somehow returning to the update method in LevelSelectState & updating an inventory that shouldn’t exist. Logically, the game should’ve crashed whenever I entered a level, but for some reason it only broke ’pon entering 1 specific level with most kinds o’ sprites in the 8th block y-position on the left half o’ the screen.
There was also some crashing & slowdown in the cart & stealth levels, but I fixed that earlier: turned out I was testing block interaction for (& trying to render, though that didn’t cause any slowdown, surprisingly) sprites that weren’t on-screen; & since those levels had a lot o’ sprites, ’twas memory-intensive. Now that’s fixed & the levels work normally.
I’m sick o’ all these programming distractions & will be focusing mo’ on designing actual levels & art for a while:
I think “Soupy Sewers” is done ’nough that I can show it off, so expect a video for that soon, once I clean it up a bit. The level still has quirks—mainly the end, which has a truly lame section. Also, a wacky glitch with an enemy. Maybe that’ll be funny to show off.
I also hope to finally get round to drawing a cart sprite so I can show off “Hot Shop,” which is pretty much done, ’cept for the art, which is still quite rough.
I also have 2 sky levels that are still in alpha stages: 1 has you as an owl who can flutter all round, & will involve navigating a bramble maze; the other is a coin heaven where you have to collect all the gems to win. In that level you ride a moving cloud platform, & the level loops infinitely.
I’ve also been thinking ’bout what to do ’bout a map screen. I have decided, a’least, that it’s going to be a free-moving o’erworld (think like Super Mario 3D World, as opposed to the locked paths o’ Super Mario World or the Donkey Kong Country games). I don’t remember if I mentioned it yet, but 1 idea I had was to have an o’erworld like a Zelda game, but with 2D platformer levels ’stead o’ dungeons; but part o’ me thinks that’ll only add too much complexity for a novice game, & that perhaps I should set that aside for a later project, when I have mo’ experience. Plus, it may get in the way o’ the “Spiral” idea I know I mentioned already.