Gold Medal Games
Gold Medal Games logo
Gameplay Made Great
Written: 11/16/2024

Sometimes you just want to code - ASCIIventure is a playable game demo

Summary

This post describes a quick journey I decided to take last holiday season. Not a journey involving traveling to far away lands, but a personal quest to create a playable game in one week. There is a lot that goes into creating a game, and this is as much about what had to be left out, as it is what made the cut and was included.

Since I wanted people to actually be able to play ASCIIventure, knowing it was never going to be a hit, I settled on it running in a browser. Anyone with a browser and keyboard can play it. (I'm making the assumption that if you are reading this, you also have access to the internet. Technically that is another requirement to play.)

This is a description of the journey described above, not any sort of coding tutorial. But hey, it's about the joy of coding, so some code may sneak its way in. It won't be pretty, the entire game was conceived, designed, written and built in a week.


Introduction

Last Fall I was spinning my wheels. Unity made some annoying licensing decisions, and I was deep into Earth Matters written using Unity. October and November were spent investigating other engines, as well as writing my own. (I smell future blog posts)

I was frustrated. I didn't want to abandon Earth Matters, and I didn't want to port it to some other engine either, but that was looking like the only realistic option. Unity had lost my trust. Fighting engines and frameworks to create a game is not fun. Fun for me is translating game ideas into something playable. Keeping what is fun, and scrapping the rest. Making significant progress in a 3-4 block of being in the zone feels great!

I had use-or-lose vacation at the end of the calendar year. I couldn't remember the last time I had a week to myself. (It might have been 35 years ago, summer vacation before my senior year of high school.) This time, it was cold outside, warm inside, and going to be me and my computer, creating something.


Design

That still leaves things pretty wide open, but how do you build a videogame with rudimentary graphics. On a whim, I decided to use ASCII-art. It also seemed like a platformer (2D) was probably the easiest thing to build in a short period of time. At least based on things I have built before. I sort of knew what I was getting into.

Technical Note: for everything to line up correctly, ASCIIventure had to use a monospace font. Trust me on this if you don't understand why. What the heck, why not just show you:

     TTTTTT                             TTT      TTTTTT                             TTT
     TTTTTTTT                           TTT      TTTTTTTT                           TTT
     TTTTTTTT                           TTT      TTTTTTTT                           TTT
     TTTTTTTTTTTTTT                     TTT      TTTTTTTTTTTTTT                     TTT
      TTTTTTTTTTTTT                     TTT       TTTTTTTTTTTTT                     TTT
       TTTTTTTTTTT                      TTT        TTTTTTTTTTT                      TTT
        TTTTTTTTT                       TTT         TTTTTTTTT                       TTT
    __  TTTTTTTT    TTTTT     ___       TTT     __  TTTTTTTT    TTTTT     ___       TTT
    #%\ TTTTTTTT    \___/    /%#%\      TTT     #%\ TTTTTTTT    \___/    /%#%\      TTT
    #   TTTTTTTT     \_/       #        TTT     #   TTTTTTTT     \_/       #        TTT
    #   TTTTTTTT               #        TTT     #   TTTTTTTT               #        TTT
    #   TTTTTTTT  \|/        p #     <> TTT     #   TTTTTTTT  \|/        p #     <> TTT
    TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT     TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT

These are identical snippets of HTML characters. Can you guess which one is the monospace font, and which is variable-width?

Game Mechanics Design

I knew the player needed to move around the levels. Levels would need to have ground to stand on. I figured there would be other objects on the level as well, whatever they might be. A 2D array seemed like it would do the trick to define the level. Initially I thought it would be cool to use dynamite to blow up terrain. Water could flow out of containment and flood other areas.

In the back of my mind I thought it would be cool if the levels were algorithmically generated, so the game would be different every time you played. The thought of dynamite destroying ground, and the situations that could lead to, like falling out of the level, resulted in my decision to drop explosions. (I still wanted random levels.)

I figured there needed to be some way to move vertically, so I thought throwing a grappling hook and climbing up, and swinging over gaps would be a cool game-mechanic.

At some point, I thought moving targets would be interesting too. So I thought grapple onto bats, and fish (for the water areas) to move up vertically. Down is just falling. No way to die, but falling can be a big setback.

Character Design

I tried drawing (drawing is not the right term, but I'm going to use it for piecing together ASCII characters to make game graphics) a multi-character player. They all sucked. At this early stage I wasn't even sure if I was going to animate anything, and having a multi-line stick-figure slide around the screen seemed crappy. Basically, more detail in this case resulting in a worse look and feel.

 O
/|\
/ \ <-- second best player design (would require animation)          Ultimate winner --> p

Spider: //O\\

Fish:   }<(((“>

Bat:    \,,/ (needed some animation for wing flapping, so also) _,,_

Fish just swim back and forth (left and right) as long as there is water. (How is that for simple "AI"?) Bats could do something similar, but I thought it would be more useful from the start if they followed a path. As I'm writing this, I realize it would have simplified things a fair amount if bats just moved left and right, or up and down, reversing directon whenever they ran into a wall. Oh well, they can follow a somewhat elaborate path now.

I thought bats following a path would make random level design easier. If a location was impossible to grapple up to, just place a bat with a path.

Spiders hang out on their vertical webs, which the player can climb. It took some playtesting, but spiders decsend quickly and then climb slower, allowing the player to lure them down, then jump off and back on to get around them. If the player doesn't get off the web, the spider will push them off the bottom.

NPC AI

There isn't any. There is no randomness whatsoever. Fish swim back and forth within their water confines. Spiders at least exhibit player influenced behavior in that if the player is on their web, they move toward the player. Ultimately trying to push the player off the bottom of their web. Other than that, they hang out at the top of their webs.

Bats follow a fixed path that is defined in the level definition. I really don't know why I thought that would be a good decision. While it is kind of nice being able to look at the level definition file and see the bat's path, it is definitely restrictive. I built a parser to walk the bat's path and create an array of waypoints. To make sure the parser doesn't get confused the trail from waypoint to waypoint cannot be ambiguous.

  Good example:         *         .   B.....*

In the good example, the parser starts at the B and moves straight to the right along the trail of .'s until it gets to the waypoint * It then moves up to the second waypoint. This works because there is only ever 1 next spot to move to. Once the last waypoint is reached, the loop is complete, because the bat will fly from the last waypoint directly to the starting point.

  Bad example: (won't work/unpredictable)     *.       .. <-- The cluster of four ..'s                              ..

In the bad example, the parser starts at the B, and moves to the right along three .'s - at that point it doesn't know if it should move diagonally up to the right, or keep going right. For the parser to work reliably there can never be a choice. Of course do you know what would have been easier, better performing, and more reliable? Just providing an array of waypoints. Guess what the parser ultimately generates? Yep, that array of waypoints.

You can still do some clever path-making within the level, including severely acute angles with a roundabout path of .'s to a waypoint. The bat travels straight from waypoint to waypoint. To create a curve, you need an unambiguous series of closely placed waypoints. Oh well, it's in there, it works, and it does allow for visual level definition. (See Level Design below)

Level Design

Originally, I had the idea of randomly generating the cave. A buddy mentioned an algorithm called Wavefunction Collapse. I took a look at that, and actually did implement it quickly, but I didn’t like the results. I even coded automatically adding the “accents” for the platforms, and randomly placing trees and plants. Since time was tight, I just hand-crafted a level on several sheets of graph paper that I taped together. I probably could have spent the whole week tweaking the level generation stuff. It is definitely promising, but I wanted to move on. The clock was ticking

If you want to see the complete level map as implemented, load https://goldmedalgames.com/ascii/leveldef.js (There are comments in that file that describe things, including how part of the game works.)

The Build

I focused very early on making the player’s character fun to move. Tweaking it to make it feel nice and responsive used up about 50% of my total dev-time. Of course some of the basic infrastructure stuff got completed along the way, like how the ground would work and of course the camera.

Cinematic Camera

When I mention camera, I'm talking about the game engine concept, not the thing on the back of your phone. From some point in virtual space, aimed in a specific direction, determines what can the camera "see." Whatever it can see, is what is displayed on the screen. ASCIIventure's camera can see a 40x14 character rectangle of the level. It would be very difficult to play if you couldn't see yourself, so the camera follows the player.

You might be thinking you could just center the camera on the player, and call it done. While that is doable, it doesn't produce the best results. What ended up working nicely (see image below) is having an ignore-zone in the middle of the camera's view. The result is that if you are moving in the middle of the view, the camera view doesn't scroll around the level.

Since it is the camera's view, I ended up just calling it the view. If you look at the source in index.html, you can see the function MoveView(dx = 0, dy = 0) (Ignore the top part of the function that uses the values of dx and dy. That was for debugging things, jumping to a specific portion of the level.) The rest of that function just tests to see if the player is in the middle of the view, and if so, don't scroll. It also handles the edges of the level. When the game first starts, you appear at the bottom of the view. That is because it is the bottom edge of the level. There is nothing below that to display, so it overrides any scrolling further down. The same applies to all edges of the level in their respective directions.

The white semi-transparent rectangle shows the view, with the clear center rectangle cut out showing the view scrolling ignore zone. As long as the player moves within the inner rectangle, the view won't scroll. As soon as the player moves outside of the ignore-zone, the view scrolls to put the player back into the ignore zone. The player is only allowed to leave the ignore zone when the view is at the edge of the level.

Grappling Hook

This was probably the trickiest part to code. In general you throw the grapnel in an upward direction. It will go straight up if you aren't moving, or diagonally in the direction you are moving or jumping. It flies out a certain distance, and if it can latch onto a ledge, it will, otherwise it retracts back to he player.

Once the hook has landed on either a ledge, bat, or fish, the player remains attached to that object until they jump or climb all the way up. If connected to a fish or bat, the player moves with them. The player can also swing left and right to extend the reach of a possible jump. If the player isn't moving, they will swing to a position directly under the grapnel. That makes it feel quasi-realistic.

I had to make the choice that when on the grappling hook the player is not affect by walls. Overall it just felt better. One thing I have definitely learned, fun always wins out over realism for a game. Just like being able to change directions in the air. Is that realistic? No. Is it more fun? Definitely!

If you want to see the code, take a look at https://goldmedalgames.com/ascii/grapnel.js. The basic gist is the it has to maintain its own state. It can either be hooked on something, hidden because the player is carrying it, outbound when first thrown, or retracting if it got to its maximum length without hooking onto anything.

The other part of the complexity is that it affects player movement. Like if connected to something, then the player can only swing within the range of its current length. The hook is at some angle above the player, and the chain needs to be drawn between the two. Since it is a lot easier to not implement slack and the resulting physics, the chain from player to hook always remains taut. That means it is always a straight line between the player and hook.

I just used Bressham's line algorithm to plot the chain's links.

Animation

It's ASCII graphics, meaning big and chunky, so animations seemed better if they were limited. The most elaborate 2 animations are waterfalls and the little splash when you collect a diamond. I had the most problems with the waterfalls, mainly because of how I stupidly approached it initially.

Initially I thought everything could just be in the map's 2D array. I didn't really entertain the concept of game objects only being drawn to the view whenever they are within the camera's view. As a result, when the player moved within the 2D array it erased everything it moved across. So I added a buffer for the character under the player. When the player moves to the next spot, the vacated spot is replaced from what was within the buffer. While simple conceptually, it actually got more complicated the more object types that were added.

Thankfully at some point I realized the error of my ways, and modified the rendering pipeline. The view (what you see on the screen) is overdrawn with the level data (basically the background "layer"). Then objects are drawn on top of it if they are within the boundaries of the camera-view. However, I didn't go back and fix all the objects. For example, diamonds are a part of the level, rather than a normal object. Shame on me. Why does it matter? If you pick up a diamond, I just erase it off the map. All good right? But what if the diamond is in the water? It leaves a hole in the water when it is collected.

I did some hackery to see if there was water to the left or right of a diamond when it is collected, and if so, backfill the diamond with water. If it were a normal object, I could just remove it when collected and whatever was underneath the diamond would then be visible. Because I didn't do it that way, you can't position diamonds on top of background scenery, or when it is collected it will leave a hole. Dumb, dumb, dumb.

Since there is no sound, collecting a diamond and having it just disappear with your score going up, felt unsatisfying. Hence the little splash animation when you pick one up. You can see comments in the code regarding this, but the animations for diamond collecting is properly just written to the view.

The point of this is that even though this game was intended to be quick and dirty, it gets too dirty when you ignore basic game engine concepts. You quickly realize you are adding special cases for things, that a more generic, general purpose, game engine handles elegantly. For example, if a diamond is placed underwater in a tight chasm, just wide enough for the diamond, when it is collected, it will still leave a hole. Shame on me.

As far as the animations go, it is a different set of characters that get drawn as frames progress. The bat has 2 animation frames for wing flapping. There is a flapTimer that counts frames to set the flap speed. The higher the value, the more frames have to pass before the wings flap.

The diamond collection animation is interesting, because it changes position as it plays. It is basically 3 particles that spread as they rise. It behaves more like a particle emitter system than sprite animation. Since it was quick and dirty, it doesn't involve any particle physics. This is a case where ignoring game-engine concepts made it a lot easier to implement. And that is usually the case, right up until when you want to expand on a concept. There is probably a happy middle-ground for a particle system in this case. Basically an array of particles and some behaviors. However, that is the lead in to the disaster of an implementation of the waterfall animation.

There is a waterfall emitter, and it randomly modifies the color of water particles, that then cascade downward. It modified the level map, and the highlight particles moved down. It is an interesting case because waterfalls can be varying heights, so a variable sized sprite or game object. I don't know of any game engines that support that out of the box, but then again, they don't need to. Water could just be particles that fall, and go away when they hit the bottom.

Further complicating the variable-sized quasi-sprite implementation is around partial rendering. Say you move and only 1 column of water is in view, but not the emitter. It would look weird if it wasn't animating. The end result is that it checks to see if any part of the waterfall is in view, and then animates accordingly. That way when the map is copied into the view it appears to animate, even when the emitter is not in view. It works. Do I want a do-over? Well, only if I choose to take this to the next level and expand on things. Something to do this holiday season perhaps? ¯\_(ツ)_/¯