Just Survived My First Game

I have just finished my Spring Lisp Game Jam 2020 and submitted the first game I have ever made. The game is a partial remake of the classic Battle City, which is probably also the first game I have ever played. While building the game is so much fun, trying to get started is not. In this blog post, I will share with you some lessons I have learned and some resources I have found useful and hopefully help you get started with your first game too.

Stage 4

Game Engine and Language

First thing first, game engine. My game engine of choice is TIC-80 (check out its homepage and API reference), and I have a very simple reason for this: it’s small, it’s free, it can run anywhere and you even can upload your game to its “arcade” so that everyone can play your game online. More importantly, TIC 80 supports Fennel (a Lisp dialect) and Lua, which is the language I will use for my game. That said, with a smaller size you get fewer features. By default, for example, you have only sixteen colors to use. Generally, if you want a more powerful game engine with more pixels and colors to use, Love 2D is a great option but since I don’t need those extra pixels anyway let’s just keep it simple in our first game.

One nice thing about TIC 80 is that you can find so many useful tutorials from its wiki page on GitHub and these tutorials have covered almost every genre of game I can think of. You should definitely check out their step-by-step tutorial before you proceed.

Make Preparation and Avoid Burnout

For first-time game makers and program developers alike, the most important thing to do is to avoid burnout. And one of the reasons why people feel burnt out is that they do not work with a purpose. So I recommend you make a plan of your game or at least the first stage of your game (the game logic is usually the same throughout the game anyway).

Having a plan helps you make up your mind. Honestly, both drawing and coding can be quite exhausting, and if you only have a rough idea of what you want, you may change your mind from time to time and you will have to keep rewriting your code to follow your transient thoughts.

Also, you should have all the sprites and tiles ready before you even start to code. It’s your design and drawing that can tell you how far you can go and keep your expectations reasonable. You may have so many fancy ideas but can you draw those frames and animations? I am not kidding. We may easily overlook the efforts it may require before one can design a good game or draw decent frames, only to get frustrated later. To make things worse, stopping here and there to draw new sprites will only make the process a lot more tiring.

In general, I think imitating a classic game is a good start since they are already well designed and beautifully illustrated. While there are many people who can simply enjoy the process of game making, I am pretty sure imitating a classic game will make you less likely to feel burnt out.

Sprites (FG, foreground)
Tiles (BG, background)

Write Clean Code

Now that you have all the sprites ready and have drawn a beautiful background using the tiles in the map editor, you may want to write some code to control your characters. I don’t have much to say about coding, but I did learn a few things.

1. Try to use classes.

There are of course many benefits in using the Object-Oriented Programming style (OOP in Lua, check out this page and this video), but the very reason why I recommend it is that it helps you to keep your code better organized. Games, after all, imitate life and so can your code. Why not just make it a bit easier if you can.

2. Name your variables properly

It’s really a very personal thing, and I don’t want to judge people but I find that using vague words and abbreviations everywhere makes code harder to read and to debug. (Like control_sequence versus keypress_sequence, maybe?)

Character Animation: Coordinate of Sprites

Now that we already have everything ready, it’s time to print our characters on the screen. To do this, we use the spr function. All the animations will be done by us asking the spr function to print animation frames in a specific order and at a specific position, and you can check out how it works here or take a look at my code.

frames and animations

There is one thing that can be very confusing for first-time developers. What’s the coordinate (a dot) of a sprite (a plane)? It’s definitely not very obvious and may surprise you somewhere later if not already. To find out, you can print a one-pixel rectangle at the same position as you print your sprite but actually, the coordinate of a sprite just means/specifies the coordinate of the top-left corner of the printed sprite, like

rotate=0 and rotate=3

Character Interaction: Collision Detection

After we have printed our characters, we can try to make our character interactive so that they can walk on the ground, collect coins or attack other characters, and that’s when collision detection comes into play. In TIC 80, there are two ways to implement collision detection and each has its own purpose. The first way is to use the built-in mget function. While mget is already a pretty simple function, there are still two things I want to point out: first, unlike the spr function, all the map-related functions use a tile-based coordinate system that starts from the top-left corner of the world map, instead of the screen.

Map #1

See the coordinate 004:007? It tells us the position of this white box is 4 tiles (that is 32 pixels) from the left of the world map and 7 tiles (56 pixels) from the top of the world map (instead of the screen) and that makes the actual coordinate of the white box (32, 56). The coordinate of the same position on the second map would be (while the actual coordinate of the white box remains 32, 56)

Same position on the second map

The second thing I want to point out here is that mget only works with tiles, that is, things you draw in the map editor or with the mset function (it doesn’t matter whether you draw these objects on the foreground tile sheet or the background tile sheet though). And that means we can only use mget to detect static assets such as the ground. To do this we need to know the position of the object in the next frame, since what we want to do is to avoid collision altogether instead of knowing it when it happens. If we can know the position an object is going to occupy, the rest is as easy as calling the mget function. You can take a look at my code here, but it’s actually quite simple. Since we can go only one direction at a time, we can simply pass the coordinates of the two corners of the object to mget, but you can also use one corner or three, which depends on how big your sprites and tiles are.

Edge to detect collision

And for dynamic objects (characters, bullets, or fireballs) we will need to compare the coordinates of two objects to determine whether they (will) overlap. You can check out this blog post to see how it works, but again the basic idea is simple enough. There are many situations where two square objects do not overlap, but they all fall into four categories:

  1. Even A’s bottom edge is above T’s top edge, that is, ya + h < yt (note that, while A is above T, A’s y should be smaller than T’s. Personally I find it very counter-intuitive…)
  2. Even A’s top edge is under T’s bottom edge, that is, ya > yt + h
  3. Even A’s left edge is to the left of T’s right edge, that is, xa + w < xt
  4. Even A’s right edge is to the right of T’s left edge, that is, xa > xt + w
Four situations where two objects do not overlap

If our situation should fall into none of these four categories, then we can say with confidence that these two objects have overlapped. But again, since what we want to do is to avoid collision altogether we need to predict collision one frame in advance instead of simply knowing it when it happens. Therefore, we need to use the coordinate of A in the next frame and the coordinate of T in the current frame to calculate whether they will overlap.

One frame in advance

Once you know how it works, implementing it should be easy. I just store the coordinate of each object in a table, update it every frame, and loop through each object to run a detection method (my code). Even your player, unlike mine, is allowed to move diagonally, you can always split such a movement into two directions and use the same method.

Order of Layers and Dynamic Content

So now we have interactive characters and these characters can now do pretty much whatever we want them to do: casting a fireball, shooting a bullet, triggering a landmine, you name it. But let’s not just stop here, let’s go elsewhere, like, map #2. The classic Battle City has more than one hundred stages and while each stage is quite different from the other there is a certain part of the background that stays unchanged throughout the game.

Screenshot from YouTube

So how we can reuse these parts and save some space so that we can create more stages? You can check out this tutorial or my code (how I define it and how I use it). One thing to note though, if you use mset to print a map, you will still need a transparent map for it to print, like

mset()
map()

But printing the map is only part of the story. In Battle City, different types of tiles can have quite different features. You can easily destroy brick walls but never an iron wall; bullets can fly over patches of water but tanks cannot do the same; bushes can hide tanks under them while ice fields make it difficult to control the tank. So these are basically all simple collision detection problems, but how do you “destroy” a brick wall actually? And how do you hide tanks under bushes? Simple. To “destroy” a brick wall, we just record the brick hit by a bullet and use mset to replace it with an empty tile, like

mset(x, y, 0) -- l have left that slot (#0) empty 
Tile #1

To hide tanks under bushes, you simply need to print bushes after you have already printed tanks. But how? Aren’t they part of the background? No, they don’t have to be part of the background simply because they are static. Actually, it’s what my first map actually looks like

The map I have drawn in advance

And bushes are only printed later using the spr function (my code for this part) and I also maintain a table (my code for this part) of the coordinates of these bushes for this purpose.

But if you are building something like a platformer, you should also check out how Bruno Oliveira implement the side-scrolling effect in his 8 Bit Panda.

Character Control: Control Sequence

Now that we have implemented basically everything we can do in a game, what’s next? One thing that bothered me most was: How do you control your character? Or let me rephrase my question, how do you arrange your btn functions? This can be pretty easy if we are talking about a platformer or if we allow our character to move in eight directions. But in Battle City, you are allowed to move in only four directions. What should happen if I hold down the up arrow key before I release the down arrow key, what should happen if I press the up arrow key when I am still holding down the left arrow key, and what should happen if I hold down two keys and release one of them? I have never played Battle City using a keyboard, but I did love how smooth I can control the character in Crazy Arcade. It works like this: Even if you have held down a key (the up arrow key, for example), pressing another key (the left arrow key, for example) will immediately change the direction of the character while after you release the left arrow key the character will just continue to go up since you are still holding down the up arrow key. But how should I implement this behavior? Normally if you allow your character to move freely, your code should be like

if btn(0) then ... end
if btn(1) then ... end
if btn(2) then ... end
if btn(3) then ... end

It’s simple but I can’t allow my tank to shift everywhere, so to allow my character to move in only one of the four directions, at first my code was like

if btn(0) then ... 
elseif btn(1) then ... 
elseif btn(2) then ... 
elseif btn(3) then ... end

But it will lead to asymmetry. If you press Up, then no other key will work but if you press Down, the character can still move up if you press Up. It’s not what I want, so I try to eliminate that asymmetry. I tried many methods but none of them worked. I did not really have a solution until I posted my question on Reddit, and here is my implementation. Basically, I created a table called control_sequence. This table works like a stack: Every time btnp returns true, a method will push the keypress to the top of the stack and the movement of the character will be updated according to the element on top of that stack, and if the stack is empty (cleaned up if btn returns false) the character will just stop moving. It works pretty well I must say.

Debugging

In the end, there is debugging. While I don’t know much about debugging, I did have a mental checklist to run through for common problems:

  1. Sprites (when you rotate or flip a sprite or calculate coordinates)
    1. Asymmetry in drawing
    2. Size of different assets
    3. Width and height
  2. Coordinate
    1. The coordinate of a sprite is the coordinate of its top-left corner
    2. The coordinates start from the top-left corner of the screen
    3. Map functions use tile-based coordinates
  3. Collision detection
    1. Find out which corner should have detected collision
    2. Find out the condition where the collision should be detected
  4. TIC
    1. The TIC function is called every second (store variables elsewhere)

Closing thoughts

So basically we have just walked through the entire process of building a game. Again, the most important thing to do is to avoid burnout. Be confident, stay calm and don’t rush. You don’t really need to be really good at programming to build a game (I had never used Lua before either). Just be mindful of the gotchas I just mentioned since while they are all petty things that can be solved quite easily, but if you need to do dozens of experiments to find out what happened, they are time-consuming at best, and you will most likely just quit.

Finally, though I did not finish the game in Lisp as I had intended to, I am still greatly indebted to the Lisp Game Jam for giving me such a great opportunity to complete a game that I can really enjoy. Perhaps you are not in any game jam as I did but you should definitely force yourself to finish the game in a reasonable time (10 days, perhaps). Ever heard of Parkinson’s Law? Yeah, your work will expand so as to fill the time available for its completion. With an infinite amount of time, you may build the best game in the world, but it’s way more likely you will just get burnt out and end up building nothing.