The Mezunian

Die Positivität ist das Opium des Volkes, aber der Spott ist das Opium der Verrückten

Boskeopolis Land: Let’s Code a Crappy 2D Platformer Like Millions o’ Other People on the Internet & Lose Interest & Give Up Only a Few Months In, Part XXXXV: Gravity, Hypocrisy, & the Perils o’ Being in 3D

Gravity, Hypocrisy, & the Perils o’ Being in 3D

I’m surprised this made it past the rejection bin. This started as an idle, silly idea that I planned to procrastinate to the sequel, ’specially due to the rigidity o’ this game engine, thanks to me to being a terrible programmer who was an e’en terribler programer when I started mo’ than 2 years ago. However, I figured out an easy way to do it by just adding some flags to hide all the blocks, make all the sprites invisible, & then just create a background that draws everything seen in this level. This is thanks to the MapLayer class being ridiculously flexible: it’s basically just an update & render virtual method.

This level & associated classes all have “Doom” in their name, but they should truly be called “Wolfenstein3D”, as this level uses the much simpler raycasting method that that game uses, rather than Doom’s mo’ complicated ( & mo’ powerful ) BSP trees. Raycasting works better for this game’s engine, as it works well with grid-based maps, which this game engine uses, whereas Doom’s system is based on lines which can be @ any angle ( & thus can have walls that aren’t all @ 90° angles, as Wolfenstein 3D & this game have ). That’s fine for me, since this is just 1 level & it’s s’sposed to have a retro look. I can tell you that I’d worry ’bout how primitive the 16-pixel block textures ( smaller than Wolfenstein’s, actually, but the size o’ blocks in this Mario-inspired engine ) look stetched out before I worry ’bout perfectly square walls. Since this works well with grid-based maps, I can just use a regular grid-based map & keep their usual behavior. This pseudo-3d gimmick is nothing mo’ than a visual gimmick o’er a normal isometric 2D level — basically just “Maybe I’m a Maze”, but with simpler, slower enemies that you can kill off. The walls are just regular solid blocks, the gems & hearts are the same gem & heart blocks used in all the other levels, & thus I didn’t have to add any new behavior there. Only the hero, enemy, & bullet sprites needed much new behavioral programming, & that was mostly to handle moving in various angles.

For those curious, the gist o’ raycasting is that you calculate a ray for each vertical stripe o’ the screen measuring the distance ’tween the nearest solid block on the grid & the player ( to be mo’ accurate, a point on an invisible line perpendicular to the player & a li’ in front o’ the player to prevent a strange fish-eye perspective ) & using that distance to calculate how tall that line o’ wall should be, with larger distances giving shorter lines & shorter distances giving longer lines, simulating walls shrinking in the distance. ’Course, there’s many other complications, like applying a texture to these lines ( in my case, I just calculate which texture X it should have & just stretch the texture block o’er the height o’ the line, which is mo’ efficient for SDL & GPU than manually calculating each pixel ), creating perspective textures on the floor & ceiling ( it’s a blur to me how I did this, though I do remember that the #s for the ceiling & floor are the same, just using a different offset for different textures ), & adding in “objects”, like the crab enemies, the bullets, the gems, & hearts & cutting out parts that are hidden ’hind walls ( this was actually the hardest part ). Since I have no idea what I’m doing for e’en basic programming, it’s obvious that I relied on learning how to do this nonsense from other sources, with this tutorial as a major influence, as well as Fabien Sanglard’s excellent in-depth study o’ Wolfenstein 3D’s source code, The Black Book of Wolfenstein 3D, which is what inspired this idea in the 1st-place ( though Wolfenstein 3D used so much assembly & so many arcane optimizations that most o’ its code wouldn’t work well for my project ). I did, however, twist the code I copied a lot so that it’s now virtually impossible to recognize, sometimes for petty anal-retentive reasons ( I don’t like free variables that change round a lot ) & some for optimization reasons, due to the difference ’tween low-memory computers that these guides were aimed for & modern computers with their strange GPUs & SDL with its immensely limited GPU control compared to OpenGL.

I ran into many subtle bugs ’long the way, ’cause this 3D-like business, e’en if just a graphical illusion, is far beyond what I’m used to. For instance, I don’t think my high school or my joke o’ a college I went to taught trigonometry, so I was just going off vague memories. ( I still don’t know what sine & cosine do, but I know I remember I used it for calculating angles on the shmup level I still haven’t finished yet, so it makes sense here ). & then I would just rely on trying things out & seeing what happens. The last bug I ran into was when I changed the shooting so that the bullet appeared a block or so in front o’ the player when shooting, so the bullet starts @ a size you’d expect to come from the slingshot & not @ the size o’ the screen ( which would make it look like Autumn is shooting rocks bigger than she is ), only for it to start @ the sides o’ the player when pointing in certain angles ( which makes e’en less sense, visually ). When creating this, I set the bullet to be an offset from the player’s center, which seemed most balanced; turned out, simply changing it to the x & y position ( which is the top left ) made it work exactly as expected. This still makes less sense to me.

For a long time before that, both bullets & the player moved in weird angles. This was less obvious for the player, since you can’t see them; for the longest time, I just thought ’twas just my imprecise angling while playing. I finally realized the cause was that the acceleration & velocity system I use for regular 2D movement doesn’t work with this strange angled movement. For those who don’t already know, almost all sprites move by setting acceleration, which is added to velocity every frame, which gets capped @ a set top speed, & that velocity is added to position. This works great for, say, 2D platformer movement ( & is, in fact, how movement in Mario games works — though they oft have weird acceleration oscillations for reasons I don’t understand ). However, for angled movement, this, with the velocity cap, creates a subtle problem: if your angle is so that you move mo’ on 1 axis than the other, then that axis’s speed will be greater than the other axis. However, due to the speed cap ( which is necessary to keep you from going just zipping through everything ), after the bigger axis reaches the speed cap before the other axis, the other axis keeps gaining speed till it reaches the same cap, which gradually transforms all non-straight angles ( all angles that don’t have the lesser axis as exactly 0 ) to 45°. This, logically, causes the very effect I could see from the beginning: veering parabolas. The level as shown just has constant speed for the player & bullet sprites, which fixes this ( though, an example o’ this game engine’s stupid built-in rigidity, ’cause I have it built into the core sprite class that collision detection relies on the built-in velocity properties to work, I do still have to set velocity, or else do a bunch o’ work creating a new collision detection just for this level ).

The 1 exception to this fix are the crab enemies: they still fall under the ol’ movement glitch, as you can see by their weird sideways movement in the video. I did this on purpose as I prefer this movement for them — it fits their crablike nature perfectly. The only bug with them is why I have crab enemies in a dungeon. The answer: I can’t think o’ an enemy design I like better than them & they have a simple animation that isn’t a headache to depict.

The challenge offered by the crab enemies is interesting to me: since this is a 1st-cycle level, I made this level very easy. The crabs aren’t very fast & you have to basically try to get hurt if you see 1 coming up to you & can’t shoot it down before it touches you. That is if you’re not racing round with strafing ( like in most games with 3D movement, strafing is quicker than just moving forward, which is why I move like a Goldeneye 007 speedrun in the time challenge part o’ the video ); if you are, they can sneak up on you when you can’t see them clearly.

Since this level is so easy, I didn’t feel any qualm with forcing the player to explore the whole maze & collect every gem in the level to get the gem challenge. But man can it take long, & makes me wonder if I should’ve picked a less monotonous song for this level ( “Boskeopolis Underground” had this same problem ). I was not happy when recording this video & actually dying to a crab somehow when halfway through attempting this, making me do it all o’er ’gain.

The time challenge, meanwhile, is easy if you know the strafing trick, ‘cause I didn’t want to force players to figure out such an obtuse trick to complete the game.

Source code

Posted in Boskeopolis Land, Programming