Before I dive into all of the technical details, I thought it would be a good idea to give you an overview of the technical design of the game.

Background

But first, let me briefly address the experience I gained in my career as a software developer. I won’t turn this into a resume, but I think it’s worth mentioning that I have been working as a software developer for over 25 years now and that I’ve learned a lot about software design and development. I’ve been working on .NET-based B2B solutions mostly but there’s so much that I have learned that is applicable to game development as well.

Development environment

As I mentioned in the Introduction post, I managed to set up a C-based cross-compilation development environment for the Atari ST, based on Thorsten Otto’s m68k-atari-mint cross-tools. Combined with the equally great RMAC assembler, this allows me to write the game in C and assembly.

This means that I can write code on my PC and then build the game and test it in an emulator, which is a much more comfortable development environment than working on the ST itself. It also allows for a much faster development cycle since I don’t have to transfer the build files to the ST every time I want to test something.

I’m using version 15.2.0 of GCC (the C compiler) which allows me to use relatively modern C features.

I must admit that I’m not aware of all of those newer features, partly because I hadn’t been programming in C for many years, so I probably take most of them for granted. But I do know that compared to the older C/C89/C90 standards (as was common on the ST), I can use things like single-line comments and freely placeable variable declarations, which is really convenient.

Dependencies and tools

Let’s start with a list of tools:

  • Aseprite
    • This is the tool used to create most of the game’s graphics, such as sprites and tiles.
  • Arkos Tracker 3
    • This is the tool used to create the game’s music and SFX.
  • Godot Engine
    • While this is a complete game engine for modern computers, Godot is only used as an editor for the game’s levels. Using a custom script, level data is exported in a format that can be used by the game.
  • UPX
    • This is a general-purpose executable packer that I use to compress the executable game binary.

I decided to use as few libraries as possible to keep the game small and retain full control over the code. This means that I have written my own code for things like sprite drawing, tile drawing, input handling, etc.

I’m not under the illusion that my code is better than some of the available libraries I could have used but I wanted to take the challenge of writing as much as possible myself, to maximize my understanding of the matter.

There are some libraries that I do use, however, mainly because I could not reasonably write them, or it didn’t make sense to do so:

  • Libcmini
    • To get some basic C library functions, such as string handling and file I/O.
  • Sync scrolling library
    • This library by Troed allows the display address to be positioned with 2-byte granularity.
  • Wakestate detector
    • Also by Troed; it is used to properly initialize the sync scroller for the hardware.
  • LZ4 depacker
    • This library by Leonard is used to decompress the game’s assets, such as graphics and level data.
  • Arkos Music player
    • This is the 68000 port of the official player by GGN.

Note that the list of dependencies and tools is neither complete nor final. More dependencies may be added in the future, or removed if a better solution is found.

Build chain

The build chain is pretty straightforward. I have a Makefile that defines the build process, which includes:

  • Running the asset pipeline to process the game’s assets (see below)
  • Compiling the C code
  • Assembling the assembly code
  • Linking the C and assembly build outputs together
  • Compressing the linked binary
  • Creating a disk image that can be run in an emulator

I’ll elaborate on the build chain in a future post.

Asset pipeline

The asset pipeline is a custom tool that I wrote to process the game’s assets, such as graphics and music. Some of the things that the asset pipeline does include:

  • Converting assets into the appropriate format, e.g.,
    • Convert .PNG files to 4-bitplane graphics data (optionally masked).
    • Convert level data into a format that is optimized for use in the game.
  • Compress and combine all asset files into a single file.
    • Files are placed in a specific order to minimize the drive head seek times when loading.
  • Generate C header files that help address the assets from the game code.

I can sum up many reasons for combining the asset files into a single file, but honestly, I just did it because I thought it was cool.

I’ll elaborate on the asset pipeline in a future post.

Codebase

Design principles

I tried using a modular design for the codebase, with separate modules for things like graphics, input, audio, etc. in an attempt to keep the code organized and maintainable.

But even more importantly, I take great care to use proper names for concepts, functions, variables, etc. I think that this is one of the most important things to do when writing code, as it makes it much easier to understand and maintain the code in the long run.

I’m also trying to stay consistent with coding style and naming conventions, to make the code more readable and easier to navigate.

Object Oriented Programming

Since I’m using C and not C++, there is no built-in support for classes or polymorphism. But for some parts of the code, I wanted some features of Object-Oriented Programming, such as encapsulation and polymorphism. To achieve this, I implemented a simple component system, similar to the one presented in articles like Object-Oriented Programming (OOP) in C. I’ll write more about this in a future post.

C calling convention

One particular convention I’m paying attention to is the so-called calling convention, which defines how functions receive parameters and return values. I’m using the fastcall calling convention, which means that the first few arguments are passed in registers, and the rest are passed on the stack. Because the ST has a notoriously slow memory bus, this is a significant performance boost, at least when doing a lot of calls.

The same calling convention also applies to situations where the compiler generates code that calls linked assembly functions, so it’s important to take into account when writing assembly code that is called from C.

Another aspect of the convention is some of the registers are reserved for the caller and some for the callee. This means that both sides have to take care of saving previous register values before using them. A few registers, however, are designated as clobbered, which means that they can be used by both the caller and the callee without saving their previous values.

Many times, when ‘strange’ things were happening during code execution, I had to conclude that it was because I forgot to save a register.

Memory management

At some point during execution, memory needs to be allocated to hold things like sprites, tilemaps, level data, etc. Those allocations need to be freed at some point as well, to avoid running out of memory. Since the TOS operating system has some known problems regarding memory allocations, I decided to play safe and implement a simple, naive memory manager that suits the needs of the game. Basically, I simply allocate all memory at the start of the game with a single malloc() call, and then manage memory allocations myself.

Object pools

Since performing allocations (and deallocations for that matter) are relatively costly operations, I didn’t want to do them during gameplay. In fact, it is common practice in game development to avoid dynamic memory allocations during gameplay, even in modern games.

This means I had to find a way to pre-allocate memory for all the objects that I need during gameplay. I decided to implement object pools, where arrays of more or less specific types of objects are pre-allocated at the start of the game.

One advantage of this approach is that I can prevent having to search through lists of objects during gameplay, which can be costly. Instead, I can simply make sure there’s an object pool available that is pre-filtered to suit the search criteria. Of course, this means that I have to be careful not to have too many pools because that would become costly as well.

Metrics

When optimizing code, it’s important to have metrics to measure the performance of the code and to identify bottlenecks. I have implemented a simple metrics system that records CPU cycles. This is possible because of a recent addition to the Hatari emulator, which allows using Natfeats to get the current CPU counter value.

Note that this is not possible on a real ST, since there is no CPU counter on the ST.

The program then writes CSV formatted data to the debugger console, which allows me to easily process and analyze the performance of different parts of the game.

Optimization

Talking about optimization, this is a really important aspect of designing games, especially for a platform like the Atari ST, which has very limited resources compared to modern computers. It’s a constant balancing act between writing code that is easy to understand and maintain, and writing code that is optimized for performance or memory usage.

Some of the optimization techniques that I use include:

  • Write performance-critical code in assembly.
  • Pre-shift sprites in order to avoid shifting them during gameplay, since shift operations are costly on the 68000.
  • Pre-calculate data tables to reduce the need for expensive CPU instructions such as mulu and muls for multiplications.

Debugging

Well, this is an area I know I will have to put more effort into. There are a lot of possibilities for improving my workflow here. One of them is checking out Hildenborg’s excellent work on debugging support but I haven’t found the time yet to set it up.

For now, I rely on the Hatari emulator’s built-in CLI-based debugger, which is pretty good although the learning curve is relatively steep. I know about the existence of Hatari Remote Debugger GUI, but I haven’t tried it yet either.

Wrapping up

This is just a brief, high-level overview of the technical design of the game. In future posts, I will dive into more details about specific technical aspects of the game, such as how I implemented scrolling, how I draw graphics, etc. Stay tuned!