Skip to content

title: "GX Tutorial: Your First Game" description: Build a small, complete game in d/BASIC with GX, then play it in the browser: an explorer, a beacon, arrow keys, and a win.


Your first GX game

This page walks you through a tiny but complete game in d/BASIC using GX, d/BASIC's library for 2D games (scenes, sprites, tilemaps, cameras, collision). You will move a blue explorer onto a gold beacon with the arrow keys and win. By the end you will have written a game with movement, collision and a restart key, and you will understand how each part fits.

The game is deliberately turn-based: one arrow key, one move. That way every piece of the engine is visible before timing, art, and sound join the party.

The finished program rests on four ideas:

  1. DIM sets aside the arrays that hold the scene and the entities; the game owns them.
  2. GX changes and questions that state.
  3. the key handler turns one key press into one game turn; and
  4. DRAW paints the state through GFX intents: named requests such as "fill this rectangle" that the machine carries out however it can.

Every BASIC block on this page has a Playground button that loads it. In the playground's Example menu, GX engine first light is the smallest possible GX program, and GX Gravity (official sample port) shows movement, jumping, reusable projectiles, collision, animation, and continuous frame timing.

1. Import one engine

IMPORT gx

DIM SHARED Scene(0 TO 7) AS INTEGER
DIM SHARED Actors(0 TO 19) AS INTEGER

Scene() is eight words. Actors() is twenty words: two entity slots of ten words each. GX does not keep another copy anywhere; these arrays are the game state.

By default the import builds the library into your program. A packaged application for d/OS (the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II) can instead name an installed shared library that matches exactly:

IMPORT gx SHARED

The BASIC API is the same either way. The choice changes where the engine's code lives, not what the game means.

2. Start a round

Slot 0 will be the explorer and slot 1 the beacon. A small reset procedure makes replaying the game easy.

DECLARE SUB StartRound ()

SUB StartRound ()
  CALL GXSceneOpen(Scene(), 320, 192)
  CALL GXEntitySpawn(Actors(), 0, 24, 88, 12, 12, 1)
  CALL GXEntitySpawn(Actors(), 1, 276, 88, 12, 12, 2)
END SUB

The last argument is the entity's Kind: 1 for the explorer and 2 for the beacon. Those meanings belong to this game. GX stores the numbers; it does not decide that one means "hero" and the other means "treasure."

3. Turn keys into motion

Every d/BASIC host reports the arrow keys as the same values, so the source does not ask whether the player used a Mac keyboard, a USB controller bridge, or an adapter on a classic machine.

ON KEY SUB (K AS INTEGER)
  DIM DX AS INTEGER
  DIM DY AS INTEGER

  DX = 0
  DY = 0
  IF K = 257 THEN DY = -4  ' Up
  IF K = 258 THEN DY = 4   ' Down
  IF K = 259 THEN DX = -4  ' Left
  IF K = 260 THEN DX = 4   ' Right

  CALL GXEntityVelocity(Actors(), 0, DX, DY)
  CALL GXEntityStep(Actors(), 0)
  CALL GXSceneAdvance(Scene())
  SERVICE INVALIDATE()
END SUB

This is one bounded game turn. There is no endless input loop: the host delivers an event, the game updates, and control returns to the host.

4. Ask a collision question

GX reports geometry. Your game supplies the story.

IF GXEntitiesOverlap(Actors(), 0, 1) THEN
  Won = -1
END IF

Rectangles whose edges merely touch do not overlap. Both entity slots must be valid and alive. A bad slot number returns false instead of reading some other part of memory.

5. Draw state, not rules

Painting reads the current state and produces one complete frame. It does not move the explorer, and it does not award the win a second time.

WINDOW SUB DRAW()
  DIM HeroX AS INTEGER
  DIM HeroY AS INTEGER
  HeroX = GXEntityX(Actors(), 0)
  HeroY = GXEntityY(Actors(), 0)

  INTENT GFX.BEGIN_FRAME()
  INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
  INTENT GFX.FILL_RECT(276, 88, 12, 12, 2016)
  INTENT GFX.FILL_RECT(HeroX, HeroY, 12, 12, -2048)
  INTENT GFX.DRAW_TEXT(12, 12, "ARROWS FIND THE BEACON", 3, -1, 2081)
  INTENT GFX.PRESENT()
END SUB

The numeric colours are compact RGB565 values. A capable host can present them with richer colour; a limited screen lowers them in a predictable way. In neither case does that change where the entities are or whether they collided.

The complete game

IMPORT gx

CONST HERO = 0
CONST BEACON = 1

DIM SHARED Scene(0 TO 7) AS INTEGER
DIM SHARED Actors(0 TO 19) AS INTEGER
DIM SHARED Won AS INTEGER

DECLARE SUB StartRound ()

SUB StartRound ()
  Won = 0
  CALL GXSceneOpen(Scene(), 320, 192)
  CALL GXEntitySpawn(Actors(), HERO, 24, 88, 12, 12, 1)
  CALL GXEntitySpawn(Actors(), BEACON, 276, 88, 12, 12, 2)
END SUB

WINDOW SUB OPEN()
  CALL StartRound()
  SERVICE SET_TITLE("GX Treasure Steps")
  SERVICE INVALIDATE()
END SUB

WINDOW SUB DRAW()
  DIM HeroX AS INTEGER
  DIM HeroY AS INTEGER
  HeroX = GXEntityX(Actors(), HERO)
  HeroY = GXEntityY(Actors(), HERO)

  INTENT GFX.BEGIN_FRAME()
  INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
  INTENT GFX.FILL_RECT(276, 88, 12, 12, 2016)
  INTENT GFX.FILL_RECT(HeroX, HeroY, 12, 12, -2048)
  INTENT GFX.DRAW_TEXT(12, 12, "ARROWS FIND THE BEACON", 3, -1, 2081)
  IF Won <> 0 THEN
    INTENT GFX.DRAW_TEXT(92, 144, "FOUND IT! ESC RESTARTS", 3, -1, 2081)
  END IF
  INTENT GFX.PRESENT()
END SUB

WINDOW SUB CLOSE()
END SUB

ON KEY SUB (K AS INTEGER)
  DIM DX AS INTEGER
  DIM DY AS INTEGER

  IF K = 27 THEN
    CALL StartRound()
    SERVICE INVALIDATE()
    EXIT SUB
  END IF
  IF Won <> 0 THEN EXIT SUB

  DX = 0
  DY = 0
  IF K = 257 THEN DY = -4
  IF K = 258 THEN DY = 4
  IF K = 259 THEN DX = -4
  IF K = 260 THEN DX = 4

  CALL GXEntityVelocity(Actors(), HERO, DX, DY)
  CALL GXEntityStep(Actors(), HERO)
  CALL GXSceneAdvance(Scene())
  IF GXEntitiesOverlap(Actors(), HERO, BEACON) THEN Won = -1
  SERVICE INVALIDATE()
END SUB

Click the live surface first so it receives the arrow keys. Press Escape to reset the round.

Experiments worth making

  • Move the beacon or change its size, and notice that collision is still a question about the state, not the pixels.
  • Change the step from 4 to 1 for fine control or 12 for chaos.
  • Add a third ten-word entity slot and make it an obstacle.
  • Keep the hero inside the world after each step.
  • Replace the rectangles with packaged image resources without touching the movement code.
  • Play a short AUDIO event only at the moment Won changes from 0 to -1.

The last two are the heart of portable GX: the presentation gets richer while the game rule stays small.


Next: Tutorial: Worlds, Animation & Effects →