Eeems' Platformer Program Workflow Tutorial

Index

  • Preface
  • Structure
  • Initializing Sprites and Variables
  • Store 'map' or screen to back buffer
  • Repeat getkey(15) and (other checks
  • Animations/Sprites
  • Updating the Screen
  • Collision Check
  • Gravity and Jumping
  • End/Return and Routines
  • Conclusion

    Preface

    Program flow can sometimes be one of the toughest challenges when programming. In Axe it can still be a problem, especially with the buffers. This is a short guide on how to setup your program so that it is easy to handle and relativly quick.
    *Note - this is for AXE programs only, but you can take some of what you learn and apply it to xLib/Celtic III programs.

    Structure

    The basic structure I find is sometimes the hardest to come up with, but a good one for platformer games is as follows:

    Initialize sprites and variables
    store 'map' or screen to back buffer
    repeat getkey(15) and (other checks
    all drawing things here, like animations
    draw sprites that are constant
    dispGraph
    recallpic
    X axis collision check
    X axis movement code
    Y axis collision check
    Jump check
    gravity/jump action
    End
    return
    routines

    I will go more in depth later on each section.

    Initialize sprites and variables

    Sprites and variables are a no brainer. Everybody will have to initialize them at some time, so why not do it right at the start? The easiest way to do it is to store all the character sprites to one location, and all the enemy sprites to another, and then all the other sprites to yet, another.

    [sprite data->Pic1
    [sprite data->Pic2
    [sprite data->Pic3

    This is the usual setup for sprites, and it works quite well. Pic1 will store all the standard sprite data for the character and can be called by:

    A*8+Pic1

    Where A is the sprite you want to call starting at 0.
    You can also just change the sprite calling to exact numbers, so:

    Pic1         sprite 0
    Pic1+8       sprite 1
    Pic1+16      sprite 2
    Pic1+24      sprite 3
        ...etc...
    

    You will then want to initialize the other variables. So there are "real" variables and then pointers. "Real" variables will be easy you just store the value to it.

    0->X
    0->Y
    10->Z

    Storing to pointers is kind of hard compared to that. You can store it in a few different ways. You can just do the standard:

    10->{L1

    Then there is the harder to use:

    [data->GDB1
    conj(GDB1,L1,size)

    This is a lot harder to use due to how it is stored.

    Store 'map' or screen to back buffer

    This can be kind of hard, you can either draw it manually or you can just use a map type engine to draw using sprites.

    Map data format:

    The data uses run length encoding for compression. Lets say we had a simple map:

    11111000001111
    00000222220000

    Pretty simple, each number representing a different tile. With normal storage this would take a single byte per tile. Or we could represent the data in a different way:

    150514052504

    Seems much smaller, but what does it mean? lets insert some imaginary commas and dashes to make it easier:

    1-5,0-5,1-4,0-5,2-5,0-4

    Now you may or may not be able to see how the data is represented. The first segment is 1-5, or 5 '1's in a row, followed by 0-5, or five '0's in a row, and so on. This is how the data in run length encoding is represented. And to further the compression (or confusion), each #-# segment is packed into a single byte. Instead of two hex digits to represent a number from 0-255, we will have 2 hex digits, each from 0-15, representing the two numbers of each #-# element.

    The first Hex digit 0 to 15 is the tile number. The second hex digit is the number of tiles to add to the tilemap. The digit goes from 0-15, but 0 doesnt make much sense, since that would mean this element doesnt do anything , so we will add one to this after we decompress it so that it has a range of 1 to 16.

    There is a small disadvantage that if you have empty spaces of 17 or more in a row, it will take more than 1 byte to represent in the code.

    Decompressing the Map:

    [Data]->GDB1	 //map data to GDB1
    [tileData]->Pic1	 //tile data for tilemap
    
    0->N	 //element index for map data
    0->I	 //map index for storing tile data
    
    While I>=96	 //until we have stored all tiles
    {GBD+N}->A	 //Take the first element of the map data
    N+1->N	 //Increment the map index
    A^16->B	 //spit the map element into it
    A/16->A	 two separate elements
    
    For(F,0,B	 //fill the map from current position I to I+B
    A->{L1+I}	 //could be optimized with Fill but i couldn't get it 
    I+1->I	 //working :/
    End
    
    End	 //End while

    After this code is run, the tile data will be decompressed into L1, as follows:

    0  1  2  3  4  5  6  7  8  9  10 11 12 13...

    It will be in a straigt line, but you will have to access it using your own routine. Something like this

    {Y*W+X+L1}

    where W is the width in tiles of your map. X and Y would be the tile coordinates starting at the top left at 0,0.
    Displaying the map:

    Here is a rudimentary program that should be run right after the previous decompressing program:

    For(X,0,11	 //loop through the entire screen coordinates with tiles of 8x8
    For(Y,0,7
    {Y*12+X+L1}->A	 //retrieve the correct tile from the data in L1
    Pt-On(X*8,Y*8,A*8+Pic1	//draw the sprite to the screen
    End
    End

    Also attached is a PEDIT program to create and compress maps into a Hex String into Str1, as well as an Axe program to decompress and display them. Just put the string data into GDB1.

    Repeat getkey(15) and (other checks

    This is the core of the program, the tag if you will. All your checks to see if the game has ended go here in this format:

    getkey(15) and (check 0 and (check 1 and (check 2

    The easiest way is to make a variable your game end flag, I usually use F, and do all your checks in the loop.

    Animations/Sprites

    All animation code should be put before the screen is updated as well as all the sprite code. The most basic one would be to just display the enemy and the character.

    pt-change(X,Y,Pic1
    pt-change({L1},{L1+1},Pic2

    Updating the Screen

    Next You want to update the screen and then prepare if for collision detection.

    DispGraph
    RecallPic

    If you store the map to the back-buffer then you will want to recall it so that you can use pixel-based collision detection.

    Collision Check

    The easiest way to do collision check in a pixel-based way is to check one pixel off of the side:

    0->Z
    0->V
    0->S
    0->T
    For(A,0,7
    Z+pxl-test(X-1+A,Y)->Z
    V+pxl-test(X+8+A,Y)->V
    S+pxl-test(X,Y-1+A)->S
    T+pxl-test(X,Y+8+A)->T
    End

    Z will return the amount of pixels on on the left of the character, V returns on the right, S above, and T below. the way to return where the pixels are would be:

    0->Z
    0->V
    0->S
    0->T
    For(A,0,7
    Z+(8-A*(pxl-test(X-1+A,Y)))->Z
    V+(8-A*(pxl-test(X+8+A,Y)))->V
    S+(8-A*(pxl-test(X,Y-1+A)))->S
    T+(8-A*(pxl-test(X,Y+8+A)))->T
    End

    This will return 1 if the first pixel tested is on, 2 if the second, 3 if the third, etc. This can be good for detecting slopes.

    Gravity and Jumping

    Now there are many different way's to do gravity, but the easiest way is to apply a constant force in one direction.

    Y+(!collision)->Y

    Your character will fall until you a collision is detected, creating the effect of gravity. Jumping is harder, you have to have a jump variable which changes as your jump progresses.

    !If J
    10*(getkey(4) and (collision))-J
    Else
    J-1->J
    End
    Y+(2*(!collision and (J)))->Y

    If the ground beneath you is solid, and J is 0 and you are pressing the up key then 10 will be stored to J. If J !=0 then it will decrement. If J !=0 then your character will move up two pixels. two pixels compensates for the gravity so in reality you move down one pixel and up two, which balaces out to 1 pixel up.

    End/Return and Routines

    The last part of your code will include and End statement to end the loop and then whatever closing code you want. then you will place all your routines due to the fact that it is the logical place to place them

    Conclusion

    In this tutorial I have taught you how to set up your program flow easily for platformer games in AXE. So remember anything involving animation or sprites should go before the dispGraph command and everything involving movement should be after it.

    Click here to go back to the top