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:
DIMallocates the game-owned scene and entity carriers.- GX changes and questions that model.
- the key handler turns input into one game turn; and
DRAWprojects 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¶
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:
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.
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
4to1for fine control or12for 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
AUDIOevent only on the transition fromWon = 0toWon = -1.
The last two experiments are the heart of portable GX: presentation becomes richer while the game rule stays small.