Repository navigation
Platformer Tutorial
For this tutorial we will build this little game below, starting from scratch and building it step-by-step, highlighting the various features of the GX engine along the way.

If you want to try it out now press play and use the arrow keys to move and space bar to jump:
Open in QBJS |
Download
This sample includes all of the code needed for building the game in QB64, so you can download the project zip, export the contents and build it with QB64(PE). Strictly speaking, the contents of the "lib" directory are only needed for building the game in QB64 as QBJS has the GX engine already included. All of the included artwork is from opengameart.org.
- Part 1 - The Basics
- Part 2 - Let's Start Coding
- Part 3 - Game Events & Movement
- Part 4 - Maps
- Part 5 - Collision Detection
- Part 6 - Refining the Controls
- Part 7 - Using the Map Maker
- Part 8 - Collision Layer
- Part 9 - Side Scrolling
- Part 10 - Game Over
If this is your first time making a GX game it is recommended that you take a quick look at the [Basic Concepts](Basic Concepts) page to familiarize yourself with some of the key concepts of the GX engine.
All methods and constants in GX library are prefixed with "GX" to prevent naming collisions. Methods are further grouped by concept. For example, all of the methods that can interact with the scene are prefixed with "GXScene" (e.g. GXSceneCreate, GXSceneX, GXSceneMove). A listing of the library methods can be found here (still a work in progress).
For any GX project there are a couple of steps that we always need to do in order to use the library. First, we need to include the GX engine code:
'$Include: 'lib/gx.bi'
'$Include: 'lib/gx.bm'This will include all of the GX game data structures and methods that we will need to build our game. All of our specific game code will be added between these two include statements.
Next, we need to define our game event method. The GX engine will call this method for all events that occur in the game... more on this later.
'$Include: 'lib/gx.bi'
Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: 'lib/gx.bm'Now we need to create our scene with GXSceneCreate. This is usually the first step in the setup of our game. Let's create a new scene that is 256 pixels by 144 pixels.
'$Include: 'lib/gx.bi'
GXSceneCreate 256, 144
Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: 'lib/gx.bm'If you run the program at this point you will just see a small black screen. So, let's add something there to look at...
Let's create a new entity which represents our player character with the GXEntityCreate method:
...
GXSceneCreate 256, 144
' Create the player
Dim Shared player As Long
player = GXEntityCreate("img/character.png", 16, 20, 4)
...The first argument references the spritesheet image which should be used for the entity display. The next two arguments define the width and height of the entity, respectively, and the last argument indicates how many frames of animation to expect. Let's look a bit more closely at the character.png to see how GX expects spritesheets to be constructed...

As you can see from the image above, this spritesheet contains four different animation sequences with each distinct sequence placed on a separate row in the spritesheet image. Each animation sequence has four frames of animation.
In order to see what affect this has had we need to tell GX to start handing game events and display. We do this by calling the GXSceneStart method:
...
' Create the player
Dim Shared player As Long
player = GXEntityCreate("img/character.png", 16, 20, 4)
GXSceneStart
...If you run the program now you will see our little guy in the upper left corner. Since we haven't yet specified any position for our player entity it has defaulted to (0, 0). We can also see that the sprite image has defaulted to the first frame of the first animation sequence.
This is way too small though, let's scale it up a bit. GX has this scaling built in for us. We can do this by adding a call to GXSceneScale right after we create the scene:
'$Include: 'lib/gx.bi'
GXSceneCreate 256, 144
GXSceneScale 2
...This will tell the engine to scale the entire scene up to two times its original size and start to give us that retro pixel art look. While we're in there, let's also reposition the player character. We can do this with a call to the GXEntityPos method:
...
' Create the player
Dim Shared player As Long
player = GXEntityCreate("img/character.png", 16, 20, 4)
GXEntityPos player, 120, 75
GXSceneStart
...The first parameter is the handle to the player object that was returned from the call to GXEntityCreate. This will be the case or all of the other GXEntity* methods. The next two parameters specify the x and y position of the entity. This will cause the upper left corner of the entity to be positioned at the indicated coordinates.
Ok, this is great and all, but a game needs to be more than a static image, next time we'll start tackling some movement and talk about game events...
Before we start adding movement to our game we need to first understand the basics of the game event model in GX. And to understand the game event model let's first look at the game loop.
An important aspect of any game is the game loop. The pseudo-code for a typical game loop looks something like this:
Initialization
Do
Input Handling
Game Logic / Updates
Physics & Collision Detection
Rendering
Timing/Frame Regulation
Loop
| Step | Description |
|---|---|
| Initialization | This is where the game sets up everything it needs, like loading assets, initializing variables, and preparing game objects. |
| Input Handling | The loop checks for user inputs (keyboard, mouse, controller) and processes them so the game can react accordingly. |
| Game Logic/Updates | Here, the game updates the state of the world based on inputs, AI behavior, physics, and other rules of the game. |
| Physics & Collision Detection | The loop calculates object movements and interactions, ensuring things like gravity, collisions, and object responses are handled correctly. |
| Rendering | The game draws the updated world to the screen. This includes rendering characters, backgrounds, UI elements, and visual effects. |
| Timing/Frame Regulation | To ensure the game runs at a consistent speed, the loop manages time, often using a fixed or variable timestep to control updates and rendering. |
You'll notice in the examples shown so far there is no main loop in our actual game code. That is because the GX engine is taking care of a lot of the heavy lifting here for us. Instead, the engine sends events to our game at various points so we can implement the desired behavior that is specific to our game. It does this by calling the GXOnGameEvent method that we added at the beginning of Part 1.
Ok, getting back to our code, if we want to move our little player character when the user presses certain keys we will need to handle the Update event. Let's add a Select...Case statement to our GXOnGameEvent to set ourselves up for handling multiple events and add a Case statement for the GXEVENT_UPDATE event type. To keep things organized, let's call a new OnUpdate method when the event is fired.
...
Sub GXOnGameEvent (e As GXEvent)
Select Case e.event
Case GXEVENT_UPDATE: OnUpdate e
End Select
End Sub
Sub OnUpdate (e As GXEvent)
End Sub
...Now, let's add the logic in our new OnUpdate method to make the player move left or right when the left or right arrow keys are pressed:
...
Sub OnUpdate (e As GXEvent)
If GXKeyDown(GXKEY_LEFT) Then
GXEntityVX player, -40
ElseIf GXKeyDown(GXKEY_RIGHT) Then
GXEntityVX player, 40
Else
GXEntityVX player, 0
End If
End Sub
...The GXKeyDown function will return true (-1) if the specified key is currently being pressed and false (0) when it is not. The GXEntityVX sets the entity's X or horizontal velocity. A negative value will indicate movement to the left. A positive value will indicate movement to the right. The value passed for the velocity is in pixels per second.
Ok, we're starting to get somewhere now, we can move our player. However, he just seems to be sliding around without moving his feet and he doesn't change direction. So, let's add some animation. First let's add some constants to define our left and right animation sequences that we looked at in Part 2:
...
'$Include: 'lib/gx.bi'
Const MOVE_RIGHT = 1
Const MOVE_LEFT = 2
GXSceneCreate 256, 144
GXSceneScale 2
...Then we'll add a call to GXEntityAnimate to show the appropriate animation when the user presses a direction key. The third parameter to this method indicates the speed of the animation in frames per second.
...
Sub OnUpdate (e As GXEvent)
If GXKeyDown(GXKEY_LEFT) Then
GXEntityVX player, -40
GXEntityAnimate player, MOVE_LEFT, 10
ElseIf GXKeyDown(GXKEY_RIGHT) Then
GXEntityVX player, 40
GXEntityAnimate player, MOVE_RIGHT, 10
Else
GXEntityVX player, 0
GXEntityAnimateStop player
End If
End Sub
...Now if you run the program you will see that our player now appears to be walking left or right based on the keys pressed. When the keys are released he stops and the walk animation is stopped since we called the GXAnimationStop method.
Open in QBJS |
Download
Ok, so far we have just been walking around in the dark. Let's look at creating an environment for our player to inhabit. We'll start by creating a map.
GX has pretty robust support for tile-based maps. What are tile-based maps? Well, they are used in many 2D games to be able to construct large worlds from a relatively small set of reusable image tiles called a tileset. For our game sample we will be using the following tileset image:
This tileset image is made up of 16x16 image tiles. Each tile in the set is assigned a number based on its position:
A map, then, is made up of a collection of these tiles arranged in a grid. If we want to create a map based on this tileset that fills the screen we can determine the dimensions of the map by dividing the screen dimensions by the tileset size. Our screen size is 256x144. Dividing this by the tileset size 16x16 we get a map that has 16 columns x 9 rows of tiles.
In order to make creating and maintaining maps a bit easier, GX includes a [Map-Maker](Map Maker) application. Originally, this was built with InForm and the (source)[https://github.com/boxgaming/gx/tree/main/mapmaker} is included in the GX Github project. Going forward, there is a web-based version.
We'll come back to this later, but first, let's look at what's involved in creating a map programmatically using the GX API. Understanding this will be important if we want to load map data from an existing source or if we want to get into more advanced procedurally generated maps. (An example of this is the [url=https://qb64phoenix.com/forum/showthread.php?tid=2824]Wave Function Collapse POC.)
Picking up where we left off in Part 3, let's create a new method to set up our map:
Sub LoadMap
GXTilesetCreate "img/tileset.png", 16, 16
GXMapCreate 16, 9, 1
GXMapTile 0, 2, 1, 3
GXMapTile 0, 3, 1, 15
GXMapTile 0, 4, 1, 15
GXMapTile 0, 5, 1, 15
End SubBefore creating our map we need to first load the tileset we described above with the GXTilesetCreate method. The first parameter should indicate the image file which contains the tileset. The next two parameters indicated the width and height of the tiles in the set. Then we create a new map with the GXMapCreate method. This method expects us to pass in the number of columns, rows, and layers the map should contain. We'll keep it simple for now and start with a single layer.
The next set of calls to GXMapTile will place a tile in the map at the specified column, row and layer. The last parameter is the tile id which is derived from its position in the tileset image.
Let's call our new method after we initialize the scene:
...
GXSceneCreate 256, 144
GXSceneScale 2
LoadMap
...If we run the program now, we'll see the beginnings of our map:
This would be a bit tedious to add an individual call to GXMapTile for every single tile in the map. So, let's add some basic map data:
...
mapLayer1:
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
Data 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,13
Data 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,13
Data 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,13
Data 15, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0,13
Data 15, 0, 0, 0, 0, 0, 0,13,15, 0, 0, 0, 0, 0, 0,13
Data 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,30
Sub GXOnGameEvent (e As GXEvent)
...Then let's update our LoadMap method to load the map tiles from our data section:
Sub LoadMap
GXTilesetCreate "img/tileset.png", 16, 16
GXMapCreate 16, 9, 1
Dim As Integer col, row, tile, layer
For layer = 1 To GXMapLayers
For row = 0 To GXSceneRows - 1
For col = 0 To GXSceneColumns - 1
Read tile
GXMapTile col, row, layer, tile
Next col
Next row
Next layer
End SubThat's starting to look more interesting:
Let's add some more detail now by adding another layer of tiles. First add the additional layer data:
...
mapLayer2:
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0,53,54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
Data 0, 0,65,66, 0, 0, 0, 0, 0, 0, 0, 0,50, 0, 0, 0
Data 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
...Then adjust the call to GXMapCreate in our LoadMap method to initialize two layers:
...
GXMapCreate 16, 9, 2
...Here is the completed exercise:
Open in QBJS |
Download
Now we have the beginnings of an interesting environment for our player. However, there are two issues that become apparent... He can walk right through walls and seems to be floating in the air.
In part 5 we'll look at addressing this with collision detection.
© 2022-2026 boxgaming