Input
We have the building blocks of a game here, but we’re still lacking player input. A game that plays itself isn’t very much fun, so let’s fix that.
Paste this code below your Main
loop.
Like Memcpy
, this is a function that can be reused from different places, using the call
instruction.
UpdateKeys:
; Poll half the controller
ld a, P1F_GET_BTN
call .onenibble
ld b, a ; B7-4 = 1; B3-0 = unpressed buttons
; Poll the other half
ld a, P1F_GET_DPAD
call .onenibble
swap a ; A3-0 = unpressed directions; A7-4 = 1
xor a, b ; A = pressed buttons + directions
ld b, a ; B = pressed buttons + directions
; And release the controller
ld a, P1F_GET_NONE
ldh [rP1], a
; Combine with previous wCurKeys to make wNewKeys
ld a, [wCurKeys]
xor a, b ; A = keys that changed state
and a, b ; A = keys that changed to pressed
ld [wNewKeys], a
ld a, b
ld [wCurKeys], a
ret
.onenibble
ldh [rP1], a ; switch the key matrix
call .knownret ; burn 10 cycles calling a known ret
ldh a, [rP1] ; ignore value while waiting for the key matrix to settle
ldh a, [rP1]
ldh a, [rP1] ; this read counts
or a, $F0 ; A7-4 = 1; A3-0 = unpressed keys
.knownret
ret
Unfortunately, reading input on the Game Boy is fairly involved (as you can see!), and it would be quite difficult to explain what this function does right now. So, I ask that you make an exception, and trust me that this function does read input. Alright? Good!
Now that we know how to use functions, let’s call the UpdateKeys
function in our main loop to read user input.
UpdateKeys
writes the held buttons to a location in memory that we called wCurKeys
, which we can read from after the function returns.
Because of this, we only need to call UpdateKeys
once per frame.
This is important, because not only is it faster to reload the inputs that we’ve already processed, but it also means that we will always act on the same inputs, even if the player presses or releases a button mid-frame.
First, let’s set aside some room for the two variables that UpdateKeys
will use; paste this at the end of the main.asm
:
SECTION "Input Variables", WRAM0
wCurKeys: db
wNewKeys: db
Each variable must reside in RAM, and not ROM, because ROM is “Read-Only” (so you can’t modify it).
Additionally, each variable only needs to be one byte large, so we use db
(“Define Byte”) to reserve one byte of RAM for each.
Before we read these variables we will also want to initialize them.
We can do that below our initialization of wFrameCounter
.
; Initialize global variables
ld a, 0
ld [wFrameCounter], a
ld [wCurKeys], a
ld [wNewKeys], a
We’re going to use the and
opcode, which we can use to set the zero flag (z
) to the value of the bit.
We can use this along with the PADF
constants in hardware.inc to read a particular key.
Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
; Check the current keys every frame and move left or right.
call UpdateKeys
; First, check if the left button is pressed.
CheckLeft:
ld a, [wCurKeys]
and a, PADF_LEFT
jp z, CheckRight
Left:
; Move the paddle one pixel to the left.
ld a, [_OAMRAM + 1]
dec a
; If we've already hit the edge of the playfield, don't move.
cp a, 15
jp z, Main
ld [_OAMRAM + 1], a
jp Main
; Then check the right button.
CheckRight:
ld a, [wCurKeys]
and a, PADF_RIGHT
jp z, Main
Right:
; Move the paddle one pixel to the right.
ld a, [_OAMRAM + 1]
inc a
; If we've already hit the edge of the playfield, don't move.
cp a, 105
jp z, Main
ld [_OAMRAM + 1], a
jp Main
Now, if you compile the project, you should be able to move the paddle left and right using the d-pad!! Hooray, we have the beginnings of a game!