Copy Link
Add to Bookmark
Report

Tutorial 2: Coding a tile engine

sang's profile picture
Published in 
GP32
 · 14 Aug 2021

Welcome back

First off, I'm assuming that you've got the devkit up and running as I described in part one. In this tutorial, we will build on the test program completely and make a tile engine. Go to the test directory and make a few modifications.

Note: I suggest after each set of changes you recompile and try the code out, so that you can see what you've done, see the development of the engine, and check if you've inadvertently added bugs :)

Converting to FXE automatically

Go into the Makefile. At the line hello.fxe: hello.gxb , press enter, then press tab, then type in:

 b2fxe -t "Hello Test" -b "icon.bmp" -a "Rico" -r "PD" hello.gxb hello.fxe 

As we did last tutorial. At this point you might want to change the name of the the FXE, the program game, or edit your icon.bmp to suit your project.

Try 'make' as before. You'll find the fxe is produced automatically.

Being lazy and having batch files do all the work

Open up notepad or whatever and type in these lines:

 make 
del *.o *.elf *.gxb
..\emu\geepee32 /EXE=yourprojectname.fxe /RUN

Save it to your test directory as something like build.bat . Now with one click you can compile, delete temporary files, and run the game in GeePee32. How's that for lazy? You'll never have to open the command prompt again! If you get errors during coding, leave the emulator open and the command prompt won't automatically close. There are better ways to see your errors, but this will do for now...

Note: If you're on GeePee32 build 24 (devkit comes with build 40) use /FXE instead of /EXE.


Initial code

Let's take a look at the code that's in gpmain.c . We are going to replace parts of it. Here's the same code as was there before, but made a little neater, and without the Hello World!-esque message:

 void GpMain(void *arg) 
{
Init();
GameEngine();
}

void Init()
{
int i;
GpClockSpeedChange(132000000, 0x24001, 2); /* speed = 133 Mhz */

/* Page-flipper - we need to keep track of which video page we're on. We'll start with page 1. */
nflip = 1;

/* Enable and clear LCD screen:
go through each page (this loop is a little pointless) and register it as a 'page' with the GP32. */

for(i = 0; i < 2 ; i++)
{
GpLcdSurfaceGet(&gpDraw[i], i);
}
/* Here we wipe the first screen clear white - it starts off with random garbage. We don't need to clear screen 0, since it
is a direct copy of screen 1. */

GpRectFill(NULL, &gpDraw[nflip], 0, 0, gpDraw[nflip].buf_w, gpDraw[nflip].buf_h, 0xff);

/* The GP32 needs to know to show screen 0 on the LCD, while we show screen 1 */
GpSurfaceSet(&gpDraw[0]);

/* The random values all come from one number, this case 36547. The random numbers will be
the same every time this way, but we'll improve that later */

srand(36547);
}

void GameEngine()
{
while(1)
{
/* Now we tell the LCD to swtich to the next page, and tell our program to draw on the next page
e.g. at the start of the code, LCD is set to 0, drawing (nflip) is set to 1
Now LCD is set to 1, nflip is set to 0 ... clever eh? */

GpSurfaceFlip(&gpDraw[nflip++]);
nflip &= 0x01;
}
}

I've only moved some code, but you can see the clear difference between initialisation routines and the game itself. I've added comments so you can see what each routine does. We're about to replace the random seed, however - you'll find out in a bit, why.

Page flipping is the technique used here. We have two 'copies' of the screen, and we flip between them. While the LCD is showing one screen, we draw onto the other. nflip holds the number of the page we need to draw on - either 0 or 1, we start with 1 for the sheer hell of it.

You also need to change gpmain.h so that GCC knows about our new functions. After the line void GpMain(void * arg) add the definitions of GameEngine and Init. Just add these lines after it:

 void Init(void); 
void GameEngine(void);


Tile engines

Almost any game you care to program will involve a tile engine, unless it's a fighter or a puzzler. Platformers, shooters, RPGs, all use them. That is, unless they're 3D. Which is slightly out of the scope of this beginner's tutorial :) Let's make one. Add this code to the end of your program, and call it inside the main game loop (inside the while(1) loop). Note the tilesize is 32, you can change it easily as I have added a #define .

 #define TILESIZE 32 
void DrawTiles (int x, int y)
{

int xtile, ytile, xpos, ypos, i, j;

/* Getting starting tile to draw at */
xtile = x / TILESIZE;
ytile = y / TILESIZE;

/* Get the pixel offset to draw at
By the way, 'xpos = x & 31' is the same as 'xpos = x % 32' but faster */


xpos = x & (TILESIZE - 1);
ypos = y & (TILESIZE - 1);

/* Now draw the tiles */

for(i = 0; i < (320/TILESIZE + 1); i++)
{
for(j = 0; j < (240/TILESIZE + 2); j++)
{
/* Get tile number to draw, draw it */
GpRectFill(NULL, &gpDraw[nflip], i * TILESIZE - xpos, j * TILESIZE - ypos, TILESIZE, TILESIZE, rand() % 256);
}
}
}

You'll also have to add its definition to gpmain.h . After adding a call to DrawTiles(100,100) (for example) to the game loop, compile, run and enjoy :)

A quick description of the routine: we find out what tile to draw first by finding out which block of 32 (or whatever) the x and y co-ords are on. Then we find out the pixel-offset. Then we go through the tiles, drawing rows then columns, using 'GpRectFill' to draw rectangles. Note the rand() % 256 line - this finds a colour between 0 and 255 to draw with.

Controls

Now we're gonna let you move around this messed up random level (don't worry, it'll be static, and the graphical, over the course of this tutorial). Controls are relatively easier. Before the call to draw tiles, let's add some code. In the init, let's make a few unprofessional variables. Usually I'd struct these, but this is a simple project. Declare ex, ey, exmax, eymax, px, py. Change the DrawTiles to draw at ex, ey. All the variables are ints.

We have a camera system . The player moves around, the camera follows, game draws around the camera. The camera, in this project, is at (ex, ey). First, in your init, set px and py to something like 400 each and eymax/exmax to about 1000. The eymax/exmax has no point yet, but it will save us time later.

First we need to set the camera: We set the camera so the player is in the centre. We also limit the camera, so that it never goes into the edges of the map. Why? Because that leads to DrawTiles drawing tiles that aren't on the map and don't actually exist :) This way, when the player reaches the edge, he moves off the centre of the screen, but the camera stays put.

 ex = px - 160; 
ey = py - 120;

if (ex < 0) ex = 0;
if (ey < 0) ey = 0;
if (ex > exmax) ex = exmax;
if (ey > eymax) ey = eymax;

Put this code before the call to DrawTiles. Now for the actual input:

 void HandleInput() 
{
ExKey = GpKeyGet();
if (ExKey & GPC_VK_LEFT) px--;
if (ExKey & GPC_VK_RIGHT) px++;
if (ExKey & GPC_VK_UP) py--;
if (ExKey & GPC_VK_DOWN) py++;
}

The GP32 returns a single variable, through GpKeyGet, that contains all the keys on or off (depending on whether they've been pressed). By doing a bitwise AND on the variable, we can see if certain keys have been pressed. Here we very simply increase or decrease player co-ords as keys are pressed. As usual, call this function and reference it in the header. I would call it before the engine co-ord setting etc. At this point, you can move around; when px or py reach 0, the camera will stop (the invisible 'player' still moves, however).

An actual map

The final part of this tutorial is to create a static map. You might notice the tiles keep flickering, because they're being set to different values each time. Now we're going to lay the foundations for a graphical tile engine and get tile values from an array.

Just to clarify, our map will contain 'char's (numbers with values 0 to 255). In our example we will use the chars to find what colour to draw; in a graphical engine each graphic/tile has a number which is located in the map. Before you begin decide on a map size. 100 * 100 tiles is a good example for our game, but you can have whatever you like. It is also possible to do variable-size maps but that is a little too complex for this tutorial... (alright it's not too hard, but this is a very basic project).

Firstly you'll see why exmax and eymax is important - set it to your tilesize * your map's width, in tiles. Then subtract screen resolutions. For a 32x32 tilesize, 100x200 map, exmax is 2880 and eymax is 6160. I would add two defines - MAPH and MAPW, set to 100 and 200 respectively.

Now add this line to the top of your code:

 unsigned char map[MAPW][MAPH]; 

(change to suit your map size).

We also have to set the map up. Add this routine after the srand() call:

 for(i = 0; i < MAPW; i++) 
for(j = 0; j < MAPH; j++)
map[i][j] = (unsigned char)(rand() % 255);

The final thing to do is to replace the rand() % 255 in the DrawTiles routine to map[xtile + i][ytile + j] ... and the tile colour is now pulled from the map. Compile and we have a very primitive tile engine!


Homework

Here's some stuff you can try:

  • Run this thing on a live GP32. The speed increase is phenomenal.
  • Add a player. All you have to do is draw a few rectangles. I suggest
    GpRectFill(NULL, &gpDraw[nflip], px - ex - 5, py - ey - 5, 10, 10, 255);
    GpRectFill(NULL, &gpDraw[nflip], px - ex - 4, py - ey - 4, 8, 8, 0);

    (the player co-ords are absolute; 'px - ex' makes them relative to top-left corner of screen)
  • Add some trig functions using the -lm switch to make the player spin around in circles and stuff.
  • Put the 'hello world' text back on it :)

Anyways, I went through my own tutorial as well, and produced some code (with the player box as well) available here. Only download this if you didn't understand / can't be bothered to carry out my tutorial, and want to see what the results are :)

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT