My first attempt at ship controls in an MMBasic game went through INKEY$. Great for menus and text entry, but the moment you’re building a game you hit a wall. INKEY$ only ever returns one character from the keyboard buffer, one at a time. If you want to rotate, thrust, and fire at the same time, you simply can’t get there.
What you actually want is KEYDOWN
KEYDOWN(0) returns how many keys are currently held, and KEYDOWN(1) through KEYDOWN(4) give you the codes of the first four pressed keys. That’s exactly the shape you want for game input.
My pattern: once per frame I copy the state into a small array, and every game sub just looks at the array from then on.
DIM INTEGER keys(3)
SUB ReadKeys()
IF KEYDOWN(0) THEN
keys(0) = KEYDOWN(1)
keys(1) = KEYDOWN(2)
keys(2) = KEYDOWN(3)
keys(3) = KEYDOWN(4)
ELSE
keys(0) = 0: keys(1) = 0: keys(2) = 0: keys(3) = 0
END IF
END SUB
To keep the game logic readable I wrap the lookup in a tiny helper:
FUNCTION IsKeyPressed(key_code) AS INTEGER
IsKeyPressed = (keys(0) = key_code OR keys(1) = key_code OR keys(2) = key_code OR keys(3) = key_code)
END FUNCTION
The control code reads naturally, and several keys held at once just work:
IF IsKeyPressed(KEY_LEFT) THEN ship_angle = ship_angle - 2
IF IsKeyPressed(KEY_RIGHT) THEN ship_angle = ship_angle + 2
IF IsKeyPressed(KEY_UP) THEN ApplyThrust()
IF IsKeyPressed(KEY_SPACE) THEN FireShot()
What cost me real time figuring out: the cursor keys aren’t ASCII. Up is 128, down 129, left 130, right 131. Until I found that in the manual, I was futzing with multi-byte INKEY$ strings, which was nonsense. Four simultaneously held keys is also the cap from KEYDOWN(1..4); plenty for a game, but anyone planning chord shortcuts should know. And inside the game loop I don’t mix INKEY$ and KEYDOWN. Leave a stray INKEY$ next to KEYDOWN and the keyboard buffer fills with leftovers, which makes the input feel twitchy.