01: Map Loading

Index

Heads up! This is a work-in-progress, I'm trying to ensure it works from a blank slate, and reviewing all the steps to be sure it's correct.

Setup

This tutorial has been written using Arch Linux, but thanks to the use of pre-built binaries it should be possible to follow the same steps on other distros or macOS. Mac binaries are included in the repos, but I've not tested the process. Windows binaries for tools are included in the various repos as well, so while the commands to copy files etc. may differ, the same process should work on Windows.

Step 1 - Build the basic tutorial file

Create a new directory to store your project and AGT:

mkdir mygame
cd mygame

Clone AGT into this directory - I'm linking to my own repo here rather than DML's for two reasons:

  1. It includes pre-compiled Linux x86 binaries for many of the AGT tools thanks to Diego Parrilla
  2. I fixed a bug in the map loading code that really threw me for a loop while writing this, and it's not get been merged back into Douglas' repo.
git clone https://LaceySnr@bitbucket.org/LaceySnr/agtools.git

Next we want to grab GGN's fbuild version of the AGT build files, I find this is the easiest way to start cross-compiling AGT code for the m68k:

git clone https://bitbucket.org/ggnkua/agtools_fastbuild.git

Some of the files in here expect a certain hierarchy, so to keep things as simple as possible we'll stick to that and put our game in a games directory, in the agtools directory. Then we'll move some of the fast build (.bff) files into agtools and finally copy the contents of one of the tutorial directories to be the basis of our game code:

[matt@Europa ~/Develop/Atari/mygame]
$ ls
agtools  agtools_fastbuild

[matt@Europa ~/Develop/Atari/mygame]
$ mkdir -p agtools/games/mygame

[matt@Europa ~/Develop/Atari/mygame]
$ cp -r agtools_fastbuild/tutorials/tutor1/* ./agtools/games/mygame/

[matt@Europa ~/Develop/Atari/mygame]
$ cp agtools_fastbuild/*.bff ./agtools

[matt@Europa ~/Develop/Atari/mygame]
$ cd agtools/games/mygame/

[matt@Europa ~/Develop/Atari/mygame/agtools/games/mygame]
$ ls
FBuild      FBuild.exe    fbuild.linux.fdb
fbuild.bff  FBuild-linux  m68k-atarimegabrown-elf-c++filt.exe

Before we can build, we need to extract the correct compiler for the machine we're working on as per GGN's readme:

  1. Depending on your OS, do one of the following:

    Windows: Un7zip browncc-12.2.7z into agtools\bin\win

    Linux: tar -Jxvf linuxbrown-12.2.tar.xz into agtools/bin/Linux/browncc-12.2/

    Mac x64: tar -Jxvf macbrown-12.2.tar.xz into agtools/bin/Darwin/browncc-12.2/

    Mac M1: tar -Jxvf macm1brown-12.2.0.tar.xz into agtools/bin/Darwin/browncc-12.2/

I'm running Linux, so from the game directory I run the command like so:

[matt@Europa ~/Develop/Atari/mygame/agtools/games/mygame]
$ mkdir ../../bin/Linux/browncc-12.2 && tar -Jxvf ../../../agtools_fastbuild/linuxbrown-12.2.tar.xz -C ../../bin/Linux/browncc-12.2

Finally, before we build let's update the build file to name our game something other that tutor1. All that needs to be done is to edit the PROJECT_NAME variable in fbuild.bff - remember it can be a maximum of 8 characters long:

.PROJECT_NAME="mygame"

Last but not least, we need some code to actually try and compile - grab that from the agtools tutorial directory and rename the source to match the project name you've specified in fbuild.bff:

[matt@Europa ~/Develop/Atari/mygame/agtools/games/mygame]
$ cp ../../tutorials/tutor1/tutor1.cpp ./mygame.cpp

At this point, your toolchain should be configured, and we can finally run a build by running the appropriate version of FASTBuild - GGN has included a binary for Mac (FBuild), Windows (FBuild.exe) and Linux (FBuild-linux). Run the appropriate one for your platform, and with some luck you'll see a slew of output, ending in something like this:

10>Copy: /home/matt/Develop/Atari/mygame/agtools/games/mygame/build/mygame.prg -> /home/matt/Develop/Atari/mygame/agtools/games/mygame/disk1/auto/mygame.prg
FBuild: OK: all                                                
Time: 2.037s

Your first Atari binary with AGT! If you want to run this with Hatari, ensure the emulator is configured as an STE as it's default setup, and then you can start it from the command line like so:

hatari ./disk1

The program will automatically start and you should see the text "[A]tari [G]ame [T]ools / dml 2017" followed by "The shifter display interface", along with a message to press the space bar. Upon doing so you should see some glitchy patterns scrolling sideways for a few seconds and then the program will exit.

This original tutorial file is a demonstration of how to directly manipulate display buffers, but now we're up and running we'll set about getting a tiled background on the screen, followed by a sprite.

Step 2 - Loading a map

A large part of AGT is the tools it comes with for generating sprites maps and palettes automatically from graphics files, the idea being it makes it easy to reproduce old arcade games and the like given a few screenshots. For my own purposes I create my own tile maps and sprite sheets etc. and run those through the various tools to get the right kind of output. Rather than delve into that process now, I've generated some files that are ready to go - a simple rectangular room and a blobby little sprite (called blobbo) with four frames of animation (ok three, I doubled up on one!). Download this zip archive of the two map files and extract it in your game directory. Then copy the two files (map.cct and map.ccm) from it into the disk1 directory generated by AGT.

Now let's load up the map: it's time to start editing code, so open your source file (mygame.cpp if you followed along) in your favourite editor. First of all we to include a few more parts of AGT. Search for the comment //background stuff and uncomment the four includes that follow it.

Next we need to leverage some C++ templates provided by AGT and define the kind of world configuration we want to use, and by that I specifically mean the AGT display type. AGT supports various display modes and optimisations, and even supports 32 colour graphics through "dual field" displays where it alternates between two palettes and tilesets every other frame, but we're going to keep things simple for now. We'll use 16 colours and aim for 50Hz updates. Just before the AGT_EntryPoint function you'll see a #define for SCREEN_LINES_STORAGE, some lines defining the two screen buffers used for the current garbled screen, and an array of two pointers to those buffers. Remove all of those and then paste in the follwoing type definitions. The comments here come from other AGT samples and I've left them in place because they give some idea of what's going on.

typedef playfield_template
<
	// tile size, as a power of 2, so 2^4 = 16px
	4,
	// max scroll limit
	102,
	// Vertical scroll  mode 
	VScrollMode_LOOPBACK,
	// Horizontal scroll mode
	HScrollMode_SCANWALK,
	// Background restore mode
	RestoreMode_PAGERESTORE
> playfield_t;

// define an arena (world) composed of 2 such playfields (double-buffering on STE)
typedef arena_template
<
	// Number of frame buffers
	2,
	// Single frame mode (used for dual fields, ignore)
	true,
	// Dualfield mode
	false,
	// Playfield type definition
	playfield_t
> arena_t;	

With that done find the line that initialises the shifter:

shift.init();

And then delete the two for loops below it, leaving you with this:

shift.init();


// =====================================================================================================================
//	mainloop

... and while we're busy deleting things, remove all of this code from the loop underneath:

// ---------------------------------------------------------------------------------------------------------------------
//	cycle colour 0 for a short while, then restore it to black
//	this represents some dummy mainloop 'work', which might become game logic later on

for (s32 flash = 0; flash < 1000; flash++)
{
	reg16(FFFF8240) += 0x237; 
}
reg16(FFFF8240) = 0x000;

// ---------------------------------------------------------------------------------------------------------------------
//	when we're done with 'work', activate the current backbuffer as the new frontbuffer

// get pointer to the current backbuffer
u16 *backbuffer_ptr = screenbuffer_ptrs[backbuffer_num];

// configure shifter with our buffer, sizes and scroll offsets
shift.setdisplay(0, (u32)backbuffer_ptr, SCREEN_XSIZE, VIEWPORT_XSIZE, xscroll, yscroll, /*displayport=*/0);

// 'commit' these shifter changes on the *next* vblank to occur (i.e. the shifter interrupt)
// (without this, the display changes will only be pending)
shift.advance(/*displayport=*/0);

// scroll the screen towards the left, but loop back after 16 pixels
xscroll = (xscroll + 1) & 15;

The loop should then look like this:

for (s16 frames = 0; frames < 256; frames++)
{

	// ---------------------------------------------------------------------------------------------------------------------
	//	wait for the current VBL period to elapse

	shift.wait_vbl();

	// ---------------------------------------------------------------------------------------------------------------------
	// toggle buffers so next time we write to the new backbuffer

	backbuffer_num = backbuffer_num ^ 1;

	// ---------------------------------------------------------------------------------------------------------------------
	//	repeat forever...

}

Now we've removed the reference to screenbuffer_ptrs the code should still compile successfully at this stage, though it won't do a whole lot, and you'll just get black screen for a few seconds instead of the glitchy graphics seen previously.

We want to load our map and tiles before taking over the machine state as it will make any debugging easier: once AGT takes over the messages printed to the screen won't be visible (there are other ways to get these, to be discussed later). In order to give us time to read any such messages we'll move the lines that print the "press space to start" message and wait for us to hit a key to continue, so find those, and put them above the call to machinestate.claim():

// ---------------------------------------------------------------------------------------------------------------------
//	shifter (display) initialisation
//	pull any machine-specific metrics out of the display service & save state before any drawing systems are used

printf("configure shifter...\n");
shift.configure();
shift.save();

// tile & map loading will go here!

printf("press space to start...\n");
// wait for key using BIOS (we haven't installed our AGT input services - TOS still in charge)
Crawcin();


// ---------------------------------------------------------------------------------------------------------------------
//	now claim control over all machine resources - display, timers, input etc.

printf("claim hardware...\n");
machinestate.claim();

Note the comment I added above marking the point where we'll load our tileset and map, and then get things ready to display them. The first parts consist of loading the two types of data from their respective files:

tileset tiles;
tiles.load_cct("map.cct");

worldmap map;
map.load_ccm("map.ccm");	

Then we create the 'arena' that represents the game field in AGT:

arena_t world(shift, VIEWPORT_XSIZE, VIEWPORT_YSIZE);
world.setmap(&map);

and then provide it with pointers to our tiles so it actually has some graphics to draw for the map data. We're using a double buffered display so we need to set the data for each buffer. Following that we call world.init() so that AGT knows what part of the map we'll be displaying.

// we're using double buffering, and each playfield supports two layers, but we're
// using the same tiles for each right now
world.select_buffer(0);
world.settiles(&tiles, &tiles);

world.select_buffer(1);
world.settiles(&tiles, &tiles);

world.init(0, 0);

Now the tiles are set we need to set the shifter's palette to use the colours from our tiles, again, we're setting the same palette for each of the two buffers in use, and we're only setting one playfield (layer) for each of the buffers, so both second params are 0. This code needs to be run after we initialise the shifter, so find the call to shift.init() and put this code after that.

for (s16 c = 0; c < 16; ++c)
{
	shift.setcolour(0, 0, c, tiles.getcol(c)); // logical (next)
	shift.setcolour(1, 0, c, tiles.getcol(c)); // physical (current)
}

Finally we need to modify the main loop of the code to render our world; this involves selecting the right buffer, updating the position (static for now), and activating it. The calls here are self explanatory, and the code goes right after shift.wait_vbl(), which as you've probably guessed, waits for the vertical blank.

world.select_buffer(backbuffer_num);
world.setpos(0, 0);
world.activate_buffer();

Step 3 - View the Map!

At least it's time to build - so run the appropriate version of FASTbuild again, and fire up Hatari. Watch for any errors around loading the map files etc., if they don't get loaded you won't see anything. If they do fail to load, check they're in the disk1 directory and check the filenames are right in the code.

Index