How to Build a ZX81 Ultima Game
This is my an attempt to emulate an Ultima style game. Tiles is a working prototype of the tiles based graphics engine.
In 2012 I wanted to see if I could make an Ultima style game on the ZX81. I had an ambitious plan centered on the tile based graphics. After a few months of testing, I was able to create a working prototype called Tiles. Although the game isn’t done, the idea behind it shows promise. You can move around the map using the standard ASDW movements. Other commands are placeholders for what could be. This article explores how Tiles came to be.
Starting from the top, what is Ultima?
When my parents bought me a used C64, one of the first games I played was Ultima III. The previous owner had left wonderful cloth map in the box, which reminded me of my D&D days. Diving in, it was so fun to move my little character around a gigantic map. Before long, I was hacking and slashing my way through the world.
The game itself uses a tile based graphics system. On the disk, Ultima builds a map using codes for each tile. The codes correspond to the tile graphic to display. Given the map is larger than the screen, the game engine only displays what the player can see. The game overwrites any default tiles with the monster, player, and battle graphics.
Being a curious kid, I had a disk hex editor that I used on my Ultima save disk. It didn’t take much for me to find the maps and, using that editor, make a few tweaks. I still remember swapping out the Lord British initials is the main castle for my own. Those were fun times.
Can I make a tile game on a ZX81?
This was the question I asked myself when I started this project. Although I’d created games that used maps and tile like graphics, I’d never combined the idea before. Even more ambitious, I’d never covered the screen in those graphics. My first challenge was to figure out how to do that.
Before I could code anything, I needed graphics. I designed some simple 4x4 character based tiles using the ZX81 character set. This gave me an effective 8x8 pixel resolution without color. Although, I did use some other characters for variety. Like Ultima, I wanted some of the tiles to have a sense of movement. I added things like flags waving, water shifting, and your sword rainsing up and down.
To determine how the graphics would work, I built a quick test program. Although slow, it ran well enough to see that I was heading in the right direction. I wanted to build my own Ultima style game.
Building the initial game.
As expected, I titled my first attempt ZX-Ultima, which I wrote in BASIC. My first thought was to use PRINT AT
to display each tile. But that wasn't going to be possible. My other programs only print a couple of characters on the screen, which is pretty fast. Canvasing the screen in a 5x5 pattern of tiles was a bit more involved.
Given I wanted to have dynamic graphics, along with movement, I knew I needed to do something else. Plus, I realized I would need a lot of math. Given that math can be very slow in BASIC, it only added to the challenge. Adding to that, I also had to keep memory usage as low as possible. I started to wonder if the ZX81 could handle this.
Laying out the framework for the engine, I decided to use an array that I would splice the graphics into. This took a bit of work to figure out the math, but ultimately worked—no pun intended. I would slice in each tile into the array and then print it to the screen as normal. As long as you stayed still, the routine was fast enough, although you could see some lag as it printed.
In reality, I used two arrays. Each held a frame of animation to display to the screen. The tile graphics, in this version, could only have two animations. This works quite well, but had the downside of taking forever to create the arrays. Even using FAST
mode, each time you move it will take almost 10 seconds to build the screen.
The other problem was the array held the entire screen display. Printing portions of the array would have been much slower, as I would only be able to print a line at a time. This meant that I would have to put any text in the array as well. Thinking back, I could have gone Ultima II style and put the text at the bottom. But that wasn’t quite the look I was going for.
Building a new graphics engine.
I knew at the start that BASIC was going to be too slow, but I was hopeful. Having proved myself right, I decided to take a different approach. Given I’d been converting my games to MCODER at the time, I decided I’d use it to speed up the game. Yet, converting wasn’t going to be trivial. As I’ve written about before, MCODER requires changing your approach to how you code things.
Looking at my first few attempts, I tried to convert what I’d already built. Yes, those large arrays weren’t going to work. Besides sucking up precious memory, MCODER doesn’t like arrays splicing. Running those old programs again, the display results are quite entertaining. I went through a few iterations of this before deciding to start over.
Taking a step back, I thought about the different ways I could print the graphics to the screen. The challenge was finding a routine that was small and fast. I knew that PRINT
wasn’t going to be any faster in machine code. PRINT doesn’t only print characters, but manages the display map. Given this program wouldn’t load into a 1K ZX81, those checks weren’t much use. I wondered if I could do something different.
Time to poke memory.
As I thought through the design challenge, I wondered if I could POKE
the graphics into the display. Other games I’d played had done this with success. But I wasn’t sure if the ZX81 would be fast enough. To be sure, I decided to try out a couple of different techniques to see what would work best.
Using a program called POKETEST, I tried out four different methods. The fastest, and the one I landed on for my Tiles prototype, was a two step process. The first step, was to take each of the tile graphics and convert them from characters into integers. The program then loaded those integers into a multidimensional array.
The second step was to write a FOR
loop. That loop would go through each tile position and print the required graphic. To do so, it would first read from the internal map, another array, what tile to display. It would then call a routine with the current position of that tile. Using some simple math, it would calculate where to print the tile. Then using another loop, it would POKE
each graphic from the array onto the screen.
It sounds complicated, but the routine itself was only a few lines of code. This also eliminated both of the array’s I used earlier, freeing up a lot of memory. And it was fast. I mean, really fast. Running the same routine in BASIC was a joke due to the floating point math. But in machine code, it was all integer math.
At this point, I hacked the test code into my original program and compiled it. I could now move around the screen in real time with almost no display lag. Yes, it was that fast! I would later refine this technique even further, but I'll save that for a future article.
Putting it all together.
Armed with that knowledge, I hacked together the first version of Tiles. Moving around the map was smooth and had a very Ultima vibe to it. In the BASIC version, I used a static game map, another array, that held what tiles were where. For the prototype, I wanted something a bit more random.
By generating each map, it solved two problems. The first was adding variety to the demo. The second was to keep memory usage down. I could generate the array at start, thus allowing the game to stay below 16K when I removed the BASIC. This is the version that you can play. My emulator should jump to 32K when you attempt to use higher memory, but I found in practice it didn’t always work. On a real ZX81 you have to tell it use that extra RAM. In either case, you can view the BASIC code for the 32K version of Tiles to see how it came together.
To further give it that Ultima vibe, I added in commands and would display them to the right of the screen. This routine was fun to write as I had to create a scrolling function. Before I print each line, I call a little routine that PEEK
’s the character and POKE
’s it to the line above. I then do a simple PRINT
for the new text.
Stagnation and the future.
By the time I completed Tiles in 2013, I was pretty burnt out on it. The prototype itself was hacked together and looking rough. I had a game idea in mind, but I wasn’t sure if I’d have enough memory to build it. The program languished.
In 2016, I picked up the idea again and started to put together Gem Quest. It was my first attempt at cleaning up the prototype and making it into a real game. I thought about sharing those experiences here, but I’ve decided to wait. Digging through those programs, I realized I had done a lot more work that would be fun to share in the future.
I’m still bullish on completing a real game using this engine. I proved the ZX81 has the power to run a tile based game. But there is a lot more to do still. Even so, the demo is a fun program to see what could have been. Figuring this might kick start me finishing it, I decided to clean things up enough to share. Enjoy the prototype. I haven’t given up yet.