Tutorial - boxgaming/gx GitHub Wiki

Let's Make a Game!

This tutorial will walk you step-by-step through the basics of creating a game in GX! If you haven't already downloaded the latest version of GX, check out the Getting Started page. To prepare for this tutorial, create a new folder for your game in the "games" folder where you installed GX. Name it something creative like "tutorial1". Then fire up QB64 and save your blank program in the tutorial1 directory. (This will ensure that all of the relative paths used below will work as expected.)

GX Setup

First, we need to include the GX engine code:

'$Include: '../../gx/gx.bi'

'$Include: '../../gx/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: '../../gx/gx.bi'

Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: '../../gx/gx.bm'

Setting the Scene

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 320 pixels by 180 pixels.

'$Include: '../../gx/gx.bi'
GXSceneCreate 320, 180

Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: '../../gx/gx.bm'

Ok, so we have our scene created, now let's put something in there to look at. How about a little player character? That's a great suggestion, let's create a new little player guy using a spritesheet asset from the overworld sample using the GXEntityCreate function:

'$Include: '../../gx/gx.bi'
GXSceneCreate 320, 180
Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)

Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: '../../gx/gx.bm'

The first parameter specifies the spritesheet to use for the entity image. In this case we are using the character.png file:

The next two parameters define the size of the entity. Our character is 16 pixels wide and 20 pixels tall. As we can see from the spritesheet each animation sequence for the character has four frames of animation. This is passed in as the final parameter to our GXEntityCreate function.

First Run

We're almost ready to run our game. Let's add a call to GXSceneStart to start the game loop. Then press F5 to compile and run the game. This will launch a new window with our game running:

Ok, so not much to see here yet, but we have our 320x180 scene with our little player guy. Since we haven't given him any positioning yet he has defaulted to (0,0) which is the same as our default scene location, so he is appearing in the upper left corner. We haven't added any event handling yet, so we'll just have to end the game by clicking the close window button.

Also, it's pretty small. Kind of hard to see that little guy. Let's make it bigger so we can see it better. We'll add a call to the GXSceneScale method to scale the display up to a larger size. Let's also change the starting position of our little guy to a more interesting location with GXEntityPos and make him face forward by changing the current animation frame with GXEntityFrameSet.

'$Include: '../../gx/gx.bi'
GXSceneCreate 320, 180
GXSceneScale 2
Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)
GXEntityPos player, 150, 80
GXEntityFrameSet player, 3, 1

GXSceneStart

Sub GXOnGameEvent (e As GXEvent)
End Sub
'$Include: '../../gx/gx.bm'


Nice! Now our little player character is facing forward and more or less centered on the screen. And we can see everything a little better at 2X scale. You can experiment when setting different scaling to get the size you like best. If you'd like the game to run in full screen mode then replace the call to GXSceneScale with a call to GXFullScreen:

GXFullScreen GX_TRUE

Making it Interactive

It's not really a game unless we can interact with it, right? So, let's look at handling some events. We'll start simple and provide a way to exit the game when the user presses the Escape key. To do this we will need to add some logic to handle the UPDATE event in our GXOnGameEvent method. Add the following to the GXOnGameEvent method:

...
Sub GXOnGameEvent (e As GXEvent)
    Select Case e.event
        Case GXEVENT_UPDATE:
            If GXKeyDown(GXKEY_ESC) Then GXSceneStop
    End Select
End Sub

The example here is using a Select Case statement so we can expand this to handle multiple events that the engine will be sending our game. The UPDATE event is fired at the beginning of every frame of the game loop. This allows us to do things like detect player input and move non-player characters in the game. Now when we run the application we can see that if we press the Escape key that we see the standard QBasic end of program message:
If you wish to have the game exit immediately after the scene stops add a call to the System method:

...
GXSceneStart
System
...

Player Movement

Ok, now that we know how to detect input events, let's add some code to move our player character around on the screen when the user presses the arrow keys. We'll create a new method to handle the player controls and call it from the UPDATE section in our GXOnGameEvent method:

...
Sub GXOnGameEvent (e As GXEvent)
    Select Case e.event
        Case GXEVENT_UPDATE:
            If GXKeyDown(GXKEY_ESC) Then GXSceneStop
            HandlePlayerControls
    End Select
End Sub

Sub HandlePlayerControls
    If GXKeyDown(GXKEY_DOWN) Then
        GXEntityVX player, 0
        GXEntityVY player, 25

    ElseIf GXKeyDown(GXKEY_UP) Then
        GXEntityVX player, 0
        GXEntityVY player, -25

    ElseIf GXKeyDown(GXKEY_RIGHT) Then
        GXEntityVX player, 25
        GXEntityVY player, 0

    ElseIf GXKeyDown(GXKEY_LEFT) Then
        GXEntityVX player, -25
        GXEntityVY player, 0

    Else
        GXEntityVX player, 0
        GXEntityVY player, 0
    End If
End Sub
'$Include: '../../gx/gx.bm'

In this method we detect whether one of the arrow keys is currently being pressed. If so, we set the player character's horizontal and vertical movement (velocity) accordingly using the GXEntityVX and GXEntityVY methods. In this example we are using 25 pixels per second. If no directional keys are being pressed we set both horizontal and vertical movement to zero to stop the player movement.

Animation

Great, so now we are moving our character around in the scene. So fun, right? Wheeeee! Wait, what's that? The player seems to just be sliding around the screen without changing direction or moving his feet at all? Well let's add some entity animations then!

Ok, let's look back at our character spritesheet for a minute:

So we can see that there are animation frames defined for each direction the character can move. When we create an entity in GX the animation sequence is identified by the row of images that make up that sequence. In our example above, the first row has the walk right animation, the second row has the walk left animation, the third row has the walk down animation and the fourth row has the walk up animation. Let's add some constants to our game to keep track of this.

'$Include: '../../gx/gx.bi'
Const RIGHT = 1
Const LEFT = 2
Const DOWN = 3
Const UP = 4

GXSceneCreate 320, 180
GXSceneScale 2
...

Now let's update our HandlePlayerControls method to animate the character with the appropriate sequence when the directional keys are pressed. We'll use the GXEntityAnimate method to set the animation sequence and speed (in frames per second) when a direction key is down and GXEntityAnimateStop when no key is pressed to stop the animation.

...
Sub HandlePlayerControls
    If GXKeyDown(GXKEY_DOWN) Then
        GXEntityVX player, 0
        GXEntityVY player, 40
        GXEntityAnimate player, DOWN, 10

    ElseIf GXKeyDown(GXKEY_UP) Then
        GXEntityVX player, 0
        GXEntityVY player, -40
        GXEntityAnimate player, UP, 10

    ElseIf GXKeyDown(GXKEY_RIGHT) Then
        GXEntityVX player, 40
        GXEntityVY player, 0
        GXEntityAnimate player, RIGHT, 10

    ElseIf GXKeyDown(GXKEY_LEFT) Then
        GXEntityVX player, -40
        GXEntityVY player, 0
        GXEntityAnimate player, LEFT, 10

    Else
        GXEntityVX player, 0
        GXEntityVY player, 0
        GXEntityAnimateStop player
    End If
End Sub
'$Include: '../../gx/gx.bm'

Now just look at that little guy walking around! He can even walk in and out of the scene. Ok so now you just need to think about a catchy title like "Walking Alone in the Dark!" and start making your itch.io page... What's that? You think it needs more content? Ok, then let's talk about maps...

Maps

Since you don't want to just walk around in the dark perhaps we should load a tile-based map to create an interesting environment for our game. Maps can be created with the GX Map Maker application that is included with the engine. For this example, let's use a map that already exists in our samples directory. We just need to add a call to the GXMapLoad method to load the map into our game:

...
GXSceneCreate 320, 180
GXSceneScale 2
GXMapLoad "../../samples/overworld/map/overworld.gxm"

Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)
GXEntityPos player, 150, 80
GXEntityFrameSet player, 3, 1
...


Now we're talking! This is really starting to look like a game! But we seem to be stuck here, we can't explore the rest of the map. We need the scene to move so that we don't just walk out of frame.

Exploring the World

We can tell GX to keep the scene centered on our player character by using the GXSceneFollowEntity method. We will just need to tell it what entity to follow (which will be our player character) and the follow mode to use. In this case we will use the option that will center the view on the specified entity:

...
Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)
GXEntityPos player, 150, 80
GXEntityFrameSet player, 3, 1

GXSceneFollowEntity player, GXSCENE_FOLLOW_ENTITY_CENTER

GXSceneStart
...

Awesome, we can explore the world... but now we have a new challenge. Our player character can walk right off the map.
We really only want to follow the player unless he gets to the edge of the map. We can do this by adding a scene constraint with the GXSceneConstrain method.

Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)
GXEntityPos player, 150, 80
GXEntityFrameSet player, 3, 1

GXSceneFollowEntity player, GXSCENE_FOLLOW_ENTITY_CENTER
GXSceneConstrain GXSCENE_CONSTRAIN_TO_MAP

GXSceneStart

Now when we get close to the edge of the map the scene will stop moving with the player in that direction.

We Made a Game!

Congratulations, you've finished the first GX tutorial and you've got a great little start to a game with less than 60 lines of code! Check back here for more tutorials to come that will cover collision detection, sound, game controller input and more. In the meantime, check out the samples directory for more examples.

Here is the completed tutorial code:

'$Include: '../../gx/gx.bi'
Const RIGHT = 1
Const LEFT = 2
Const DOWN = 3
Const UP = 4

GXSceneCreate 320, 180
GXSceneScale 2
GXMapLoad "../../samples/overworld/map/overworld.gxm"

Dim Shared player As Long
player = GXEntityCreate("../../samples/overworld/img/character.png", 16, 20, 4)
GXEntityPos player, 150, 80
GXEntityFrameSet player, 3, 1

GXSceneFollowEntity player, GXSCENE_FOLLOW_ENTITY_CENTER
GXSceneConstrain GXSCENE_CONSTRAIN_TO_MAP

GXSceneStart

Sub GXOnGameEvent (e As GXEvent)
    Select Case e.event
        Case GXEVENT_UPDATE:
            If GXKeyDown(GXKEY_ESC) Then GXSceneStop
            HandlePlayerControls
    End Select
End Sub

Sub HandlePlayerControls
    If GXKeyDown(GXKEY_DOWN) Then
        GXEntityVX player, 0
        GXEntityVY player, 40
        GXEntityAnimate player, DOWN, 10

    ElseIf GXKeyDown(GXKEY_UP) Then
        GXEntityVX player, 0
        GXEntityVY player, -40
        GXEntityAnimate player, UP, 10

    ElseIf GXKeyDown(GXKEY_RIGHT) Then
        GXEntityVX player, 40
        GXEntityVY player, 0
        GXEntityAnimate player, RIGHT, 10

    ElseIf GXKeyDown(GXKEY_LEFT) Then
        GXEntityVX player, -40
        GXEntityVY player, 0
        GXEntityAnimate player, LEFT, 10

    Else
        GXEntityVX player, 0
        GXEntityVY player, 0
        GXEntityAnimateStop player
    End If
End Sub
'$Include: '../../gx/gx.bm'