Skip to content

Your first GX game

We are going to make a tiny game with an actual rule: move the blue explorer onto the gold beacon. It is intentionally turn-based—one arrow key, one move—so every piece of the engine is visible before timing, art, and sound join the party.

The finished program uses four ideas:

  1. DIM allocates the game-owned scene and entity carriers.
  2. GX changes and questions that model.
  3. the key handler turns input into one game turn; and
  4. DRAW projects the model through portable GFX Intents.

Open any BASIC block with its Playground button. In the Example menu, choose GX engine first light for the smallest carrier probe or GX Gravity (official sample port) for movement, jumping, reusable projectiles, collision, animation, and continuous frame pacing.

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 ten-word entity slots. GX does not hide another copy elsewhere; these are the game state.

The default import is baked into the program image. A packaged D/OS application can instead name an installed, exact library artifact:

IMPORT gx SHARED

The BASIC API is the same either way. The delivery choice changes where the reviewed engine body 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 unremarkable.

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

Kind values 1 and 2 are meanings chosen by this game. GX stores them; it does not decide that one means “hero” and the other means “treasure.”

3. Turn keys into motion

D/BASIC runners normalize arrow keys, so the source does not ask whether the player used a Mac keyboard, a USB controller bridge, or a classic-machine adapter.

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 a bounded game turn. It does not sit in an endless input loop; the host delivers an event, the game updates, and control returns to the runner.

4. Ask a collision question

GX reports geometry. Your game supplies the story.

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

Rectangle edges merely touching are not an overlap. Both entity slots must be valid and alive. A bad slot returns false instead of wandering into another part of memory.

5. Draw state, not rules

Painting reads the current model and emits one complete frame. It does not move the explorer or 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 runner can present them with a richer compositor; a limited head lowers them deterministically. Neither case changes 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 before using the arrow keys. Press Escape to reset the round.

Experiments worth making

  • Move the beacon or change its size and watch collision remain a model question.
  • Change movement from 4 to 1 for fine control or 12 for chaos.
  • Add a third ten-word entity slot and make it an obstacle.
  • Clamp the hero to the world after each step.
  • Replace the rectangles with packaged image resources without changing the movement code.
  • Play a short AUDIO event only on the transition from Won = 0 to Won = -1.

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


Next: Tutorial — Worlds, Animation & Effects →