Stormy Skyscrapers
It’s a good thing I didn’t promise anything specific, ’cause I ended up completing a completely unmentioned level. ’Cept, if you paid attention, you might have seen clues o’ the idea ’hind this level lurking in the background since my 5th post. I’m surprised I hadn’t shown off mo’: for the longest time I’ve had an unfinished city map with a grayscale palette, a few Hydrant enemies, & a long spike pit with horizontal & vertical moving platforms, with the plan o’ the level being mainly vertical.
None o’ that map made it into the current 1, which I started working on sometime last month, inspired by my playing Super Mario Bros. Deluxe, as I oft do during September, for some reason, & finding ’nother gimmick to steal. In this case, I basically completely ripped off the… ¿teeter-totter platforms? I don’t know what they’re s’posed to be called, but they’re called “weight platforms” in the game’s code. The 1 notable difference is the improved rope graphics, that was a pain to get to work right.
This level is heavily inspired by World 4-3 in Super Mario Bros., which I think is my favorite level in that game. However, the thin mushroom platforms are replaced by skyscrapers. I wanted this level to have a retro platformer feel, which is why it has a grayscale palette: much as mushrooms are sort o’ the natural, normal setting for Mario, urban landscapes are the natural, normal environment for Boskeopolis, — which is a city, after all — which is why it’s the 1st setting o’ each cycle.
The paper plane enemies, which grab you & drag you back to the start if you don’t struggle out o’ them quickly ’nough, are a riff on the randomly generated Bullet Bills. I thought making them drag you backward a bit was a nice way to balance having an extra projectile to dodge while not making it too hard to avoid being killed — while also rewarding you for being a fast button masher for struggling out o’ their grip. Then ’gain, some might find this mo’ annoying, ’specially when there’s an infinite healing heart block in the middle o’ the level. This is s’posed to be a 2nd-cycle level, so it shouldn’t be too difficult. It should be just a notch ’bove the 1st level.
I can’t remember exactly, but programming in the weight platforms probably took the most time out o’ everything, & became a barrier when I stumbled ’pon a bewildering glitch that caused 1 o’ the platforms to not budge, no matter how much I stepped on it ( but did work for the other 1 ), no matter how much I rolled back my code or recompiled it. Finally, I narrowed down the culprit in the silliest way: technically, ’twas caused by changing the map, which was why no amount o’ rolling back code fixed this bug — ’twas a bug that had been round since the start, but hidden till I added a block right ’tween the platforms. To make the platforms work, e’en when 1 is off-screen, I made the actual sprite go all round the limits o’ where the platforms can go till they fall. Thus, the block ’tween them was interacting with them & trying to block them from going downward. The fix was to simply turn off block interaction for that sprite, since they clearly didn’t need it.
I was updating this sprite up to near the end o’ developing this level. As I was demoing collecting gems in the level, trying to determine what I should set the gem score to be, I realized I’d ne’er implemented gaining points when breaking the platforms.
A’least the weight platforms sprite doesn’t seem to have much ugly kludge code, & I actually bothered to comment it properly.
The next boundary I ran into was, surprisingly, the gorilla sprites spawned by the big barrel sprite, an obvious reverse paean to Donkey Kong — the same problem I ran into on for “Ice Box Rock”, but far trickier to fix, since the platforms they walked o’er were sloped. @ 1st I just kept the level with the blocks_work_offscreen flag checked in the hopes that it wouldn’t be too slow, like “Rooftop Rumble”, which also has the option. I e’en tried optimizing the map by making all o’ the building blocks that weren’t the roofs background tiles, since you can’t interact with them, anyway. But, predictably for a much bigger level, there was noticeable slowdown. For, not only did this level have mo’ blocks than “Rooftop Rumble”, it had far mo’ sprites — sprites that all needed to interact with blocks, which was the point o’ making the blocks work offscreen in the 1st place. We’re talking ’bout up to 20 sprites, as opposed to “Rooftop Rumble”, which only has 2 sprites.
Surprisingly, I was able to optimize the handling o’ blocks when offscreen, so that the level seems to work in perfect speed now. ’Twas clear to me that the main problem was that every sprite was testing interaction with every block — 20 sprites times hundreds o’ blocks. The trick was to limit the blocks checked per sprite to only blocks near ’nough to actually be probably candidates. @ 1st I sat round thinking o’ complex patterns o’ dividing maps into screens, only to realize that that wouldn’t help when sprites enter the dividing line ’tween screens, till I tried a much simpler solution.
The way the block list works normally is that non-null blocks are added to the end o’ the list as they’re sequentially scanned from the map array, from top to bottom, left to right. Since empty tiles ( 0 in map data ) are ignored to save memory, & since no maps don’t have large gaps o’ empty tiles, the sequence o’ blocks in the block list have no relevance to their position, as shown below:
The position for each block is held in each block, & thus each sprite needs to test each block to see if they’re colliding.
Visual comparison o’ block data rendered based on position & data as its held in array:
The solution was to change it so that all tiles, including empty tiles, are added to the block list. This creates a consistent pattern ’tween the map & block list, like so:
Doing this, we can calculate where in the map a block will be by its location. We can be sure, for instance, that a block whose index into the block list has a remainder gainst the map width o’ 0 will have 0 as its x position value. Indeed, this is obvious when we remember that the blocks get their position in the 1st place from their position within the map’s block list.
Thus, rather than testing every block for each sprite, we only need to test blocks within a range o’ a block or so ( I picked 3 blocks @ a whim ) ’bove to below & left to right. Thus, the loop changes from
for ( Block& block : blocks ) { block.interact( sprite, ... ); }
to
const int first_x = floor( sprite.xPixels() / PIXELS_PER_BLOCK ) - 3; const int first_y = floor( sprite.yPixels() / PIXELS_PER_BLOCK ) - 3; const int last_x = ceil ( sprite.rightPixels() / PIXELS_PER_BLOCK ) + 3; const int last_y = ceil ( sprite.bottomPixels() / PIXELS_PER_BLOCK ) + 3; for ( int y = first_y; y < last_y; ++y ) { for ( int x = first_x; x < last_x; ++x ) { const int n = y * map.widthPixels() + x; Block& block = blocks_[ n ]; block.interact( sprite, ... ); } }
When I was 1st testing this out, I actually started by changing the rendering code to this, based on the hero’s position, which, when I figured it out, gave this cool effect where only the blocks right round the hero sprite appeared.
In hindsight, this is an obvious optimization: trading space for time, which is usually beneficial on modern computers with mo’ memory than it has processing power to use optimally. I think I thought adding mo’ blocks to memory was slower ’cause levels with the blocks_work_offscreen flag checked on were slower. But that was ’cause it had to process mo’ blocks, not just ’cause it had to keep them in memory.
This makes me think it might be mo’ efficient to use this kind o’ code with regular levels where only the blocks on screen & just round are live, ’specially since it would be less o’ a memory burden, being a few extra blocks ( probably barely mo’ than a kilobyte o’ extra memory ).
After that, the only snag was a slight bug with that paper airplane sprite: I thought it’d be cheap if you could get hurt by enemies if the paper airplane happened to move you through them, so I made the paper airplane sprite turn off the hero sprite’s ability to interact with sprites while in its grasp. The problem is, that meant the paper airplane sprite itself couldn’t interact with the hero sprite. The solution rather janky, but works: give the paper airplane sprite a pointer to the hero sprite it can keep @ all times &, when it sets the hero sprite to be grabbed, use the hero sprite’s pointer while updating to handle moving the hero & letting go.
In making the girder blocks, I did have to make a flatter slope than I had before. This gave me the ’scuse to finally stop screwing round & clean up the slope blocks, which I’ve mentioned before, were hackily copied & pasted from class to class. So these classes wouldn’t have to waste, like, a couple whole precious bytes out o’ the billions available, I made a single templated class with direction, steepness, & position within its slope ( since flatter slopes require mo’ blocks to form a complete slope ) as template arguments. The great thing ’bout this was that it not only cleaned the code, it made making the new flatter slope blocks ridiculously easy: I just had to quickly calculate some new #s to push into the template, & then it just worked perfectly. Turns out programming’s easier when you don’t do a hacky job o’ it.
Since the graphics are mostly reused, there wasn’t much to do there, & the girder & platform blocks were so simple, they took hardly any time to draw. In fact, I actually drew some fancy bridge blocks that I decided not to use till the very end o’ the level, preferring the plain building graphics ’stead. Using these familiar city graphics to replace the mushroom towers felt like a better parody than using some unfamiliar graphics. Plus, it allowed for layering towers o’er each other, like after the 1st pair o’ weight platforms.
Something I tried to emulate from the original Super Mario Bros. was making it so that while the weight platforms could help in certain ways, including getting gems & the hidden diamond, you wouldn’t have to wait for them to slowly move in order to just beat the level: if you play it right, you can hop straight from platform to platform in this level ( & in World 4-3 o’ Super Mario Bros. ) with minimal delay. Actually, I don’t know what happened, but somehow this level became easier to do quickly, as I easily got 18 seconds on my 1st try in this video, which took many tries before, which was why I set the time limit to 20 seconds. I should maybe change it to 18. However, I want some leeway, as this level can be kind o’ cheap with those randomly generated paper plane enemies.
In the process o’ making some o’ these code changes, I also made some other simple code refactoring. For instance, the same insight that led to turning the slope blocks into a templated class, probably from some C++ books I was reading, led me to realize that the Counter class, used in many places, could be a template.
I also finally got round to adding an autoformatter for text, such as the text that appears in message blocks. Before, to keep them formatted, I had to manually calculate where to put line breaks, as it would naturally just go to the next line when it ran to the end, e’en if ’twas in the middle o’ a word. Now, it runs the text through a function that checks if the letter @ the start o’ each line after the 1st is a space or line break, & if so, it eliminates it, if it’s a different character ( which means it’s a word that spans ’tween lines ), it goes backward till it finds a space & replaces that space with a line break so that the word spanning 2 lines starts @ the next line.
Here is a before & after shot that demonstrates this:
So for this message, the algorithm stops on the 1st letter o’ the 2nd line, sees it’s not a space or newline & goes backward till it reaches the next space, right before the “W” in “will”. It then replaces that space with a newline, causing the next letters, “will” to be printed on the 2nd line, as shown.
It’s probably not the most efficient algorithm, but that’s fine, since it only runs once @ the beginning o’ a level.
I hope to finally get round to other code refactorings, but I have to be conscious o’ my time, ’specially as I grow older & become e’er mo’ senile.
¿& who knows what the future holds?
This code is still sloppy as a hippo & probably won’t compile on your computer.