In Ramses Game, monsters pop out of holes and the player needs to whack them. There are only 8 buttons on the NES controller, really 7 if you reserve Start for pausing the game. This means that if I want more than 7 holes, I need to assign the holes to combinations of buttons rather than individual buttons.
To me the most obvious way to do this is to look for combinations like up+A, down+A, left+A, right+A. Even if I reserve the Start button for pausing, that still leaves me with 12 combinations (4 each for A, B and Select). 12 holes is plenty for this kind of game, and actually due to screen space limitations I’ve decided to limit myself to 10 holes.
Detecting button combinations on the NES is a little bit tricky. I don’t want to allow the player to hold down B and spam the dpad to hit many holes very quickly. Also, if the player presses up+left at the same time while pressing B, I don’t want two holes to be hit simulataneously. On the other hand, it would be unreasonable to expect the player to hit two buttons at once in the exact same frame. Nobody is that fast. So I have to allow some leeway. With that in mind, here is how my Button Combo routine works.
Button Combo Routine
Output
My button combo routine takes what the user has pressed and outputs a value 0-12. The output possibilities are laid out like this:
- 0: no combination was pressed.
- 1-4: B+dpad was pressed (in order: up, down, left, right)
- 5-8: A+dpad
- 9-12: Select+dpad
Each of these values is mapped to a hole, and I use the outputted value to index into hole-related tables. For example, here is a table that I use to determine if a valid hole was whacked:
valid_holes:
.byte 0 ;unused (no combo)
.byte 1, 1, 1, 1 ;B (up, down, left, right order)
.byte 1, 1, 1, 1 ;A
.byte 1, 1, 0, 0 ;Select
.byte 0, 0, 0 ;unused (select+left, select+right unused)
I have tables for attribute table PPU addresses and the like as well.
Input
When I read the controller every frame, I store two sets of data in RAM. One is the current button states (pressed or not pressed), which I store in a variable called joypad1. The other is off-to-on transitions, ie what was unpressed last frame but is pressed this frame. Off-to-on transitions are stored in a variable called joypad1_pressed.
Each variable is one byte, basically a bitflag with each bit corresponding to a button. My controller reading routine stores button information in this order:
joypad1 and joypad1_pressed bits:
76543210
||||||||
|||||||+- right
||||||+-- left
|||||+--- down
||||+---- up
|||+----- start
||+------ select
|+------- B
+-------- A
Logic
The first thing I do in my button combo routine is to check if A, B or select is pressed. If not, obviously we can’t have a combo, so skip to the end.
;---------------------------------------------------
; check for dpad+button combos
.proc check_combos
lda joypad1
and #%11100000 ;isolate A, B, select
beq @somewhere_near_the_end
;....snip
If a button is pressed, I want to grab its combo value and store it in RAM:
.proc check_combos
lda joypad1
and #%11100000 ;isolate A, B, select
beq @clear_btn_val ;if not pressed, clear btn_val
lda joypad1_pressed
and #%11100000 ;if off_to_on, we need to set the flag, else skip to dpad check
beq @check_dpad
@set_btn_val:
asl ;shift the A button bit to the CF
bcc @B
lda #$05 ;if A bit is set, store 5 in btn_flag (5, 6, 7, 8 = A combo)
bne @store
@B:
asl ;shift the B button bit to the CF
bcc @Select
lda #$01 ;else if B bit is set, store 1 in btn_flag (1, 2, 3, 4 = B combo)
bne @store
@Select:
lda #$09 ;else Select bit must be set, so store 9 in btn_flag (9, 10, 11, 12 = Select combo)
@store:
sta btn_val ;add dpad value to this to get index
@check_dpad:
;.....snip
@clear_btn_val:
lda #$00
sta btn_val
@end:
rts
.endproc
Notice that I check if the button is newly pressed or if it was held from last frame. This check is for a special case. Say the user is holding A. Then say a monster they want to smack appears in the up+B hole. The player will press up+B, but there is a chance that there will be several frames of A+B as the player slides their thumb from A to B. I want to catch the newest button press (B), not the older one (A). So I check joypad1_pressed. If a button is newly pressed this frame, I set btn_val. If not, I skip straight to the dpad (btn_val having been set in a previous frame).
To say it another way, I only alter the value of btn_val in two cases: when a new button is pressed, or when no buttons are pressed. This is important for another case too, which I will get to in a moment.
Note that if on the off-chance that B, A or Select are newly pressed simultaneous in the same frame, A takes precedence over B, which takes precedence over Select. Not going to happen often, and when it does it’s not a big deal – the player will know they mashed both buttons.
Finally we check the dpad to complete the combo:
@check_dpad:
lda btn_val
beq @end ;if A, B, Select not pressed, no combo
lda joypad1
and #%00001111 ;isolate dpad
beq @end ;if no arrows pressed, no combo
tay
lda dpad_add, y ;else, grab arrow value from table (up = 0, down=1, left=2, right=3)
clc
adc btn_val ;add arrow value to button value. Results in a number 1-12
sta joypad1_combo
jsr do_button_combo
@clear_btn_val:
lda #$00
sta btn_val
@end:
rts
First thing I check is if btn_val is 0. But if we made it this far, shouldn’t this be non-zero? The player is definitely holding down either A, B or Select afterall. Yes they are, but btn_val could still be zero. After a button combo is registered, I process the combo via do_button_combo and then I clear btn_val. This check for btn_val == 0 covers the case that a button combination was recently processed (say last frame), but the player hasn’t released the buttons yet. It took me some bug-hunting before I realized I needed this check. Button Combinations are tricky, aren’t they?
The rest is pretty straightforward. I check the dpad. If no arrow is pressed, there is no combo so I return. If an arrow is pressed, I grab its value from a table and add it to btn_val to get the final combo value. The reason for the dpad_add table is to protect against cases of multiple arrows being pressed at the same time. If up+left is pressed, for example, I resolve it to up. If left+right is pressed, I resolve it to left. If you’re curious, my table looks like this:
;this table maps each possible d-pad combination to a single d-pad direction
; 0 = up, 1 = down, 2 = left, 3 = right
dpad_add:
.byte 0 ;unused (will never be read)
.byte 3 ;r -> right
.byte 2 ;l -> left
.byte 2 ;r+l -> left
.byte 1 ;d -> down
.byte 1 ;d+r -> down
.byte 1 ;d+l -> down
.byte 1 ;d+r+l -> down
.byte 0 ;u -> up
.byte 0 ;u+r -> up
.byte 0 ;u+l -> up
.byte 0 ;u+r+l -> up
.byte 0 ;u+d -> up
.byte 0 ;u+d+r -> up
.byte 0 ;u+d+l -> up
.byte 0 ;u+d+r+l -> up
Having this in table form makes it easy to modify in the future if I need to tweak some of the values (poor right arrow gets overruled a lot, doesn’t he?). If you want to know more about this technique, I have written about protecting against odd dpad combinations using tables before.
That’s it. It’s a little complicated but it works. I haven’t optimized this routine, so it’s possible that I could improve it somehow. I’d rather move forward though, since this works fine the way it is.
Incidentally, the player is allowed to hold a dpad arrow and spam A/B/Select. Doing so is of such limited use that I don’t see it being abusive. If testing reveals otherwise though, I’ll rewrite the routine to protect against that.
Full Routine
Here’s the full routine in case anyone wants to copy/paste it into their game. If you do, I will feel really happy if you mention me in the credits somewhere. :)
;---------------------------------------------------
; check for dpad+button combos
.proc check_combos
lda joypad1
and #%11100000 ;isolate A, B, select
beq @clear_btn_val ;if not pressed, clear btn_val
lda joypad1_pressed
and #%11100000 ;if off_to_on, we need to set the flag, else skip to dpad check
beq @check_dpad
@set_btn_val:
asl ;shift the A button bit to the CF
bcc @B
lda #$05 ;if A bit is set, store 5 in btn_flag (5, 6, 7, 8 = A combo)
bne @store
@B:
asl ;shift the B button bit to the CF
bcc @Select
lda #$01 ;else if B bit is set, store 1 in btn_flag (1, 2, 3, 4 = B combo)
bne @store
@Select:
lda #$09 ;else Select bit must be set, so store 9 in btn_flag (9, 10, 11, 12 = Select combo)
@store:
sta btn_val ;add dpad value to this to get index
@check_dpad:
lda btn_val
beq @end ;if A, B, Select not pressed, no combo
lda joypad1
and #%00001111 ;isolate dpad
beq @end ;if no arrows pressed, no combo
tay
lda dpad_add, y ;else, grab arrow value from table (up = 0, down=1, left=2, right=3)
clc
adc btn_val ;add arrow value to button value. Results in a number 1-12
sta joypad1_combo
jsr do_button_combo
@clear_btn_val:
lda #$00
sta btn_val
@end:
rts
.endproc
;this table maps each possible d-pad combination to a single d-pad direction
; 0 = up, 1 = down, 2 = left, 3 = right
dpad_add:
.byte 0 ;unused
.byte 3 ;r -> right
.byte 2 ;l -> left
.byte 2 ;r+l ->left
.byte 1 ;d -> down
.byte 1 ;d+r -> down
.byte 1 ;d+l -> down
.byte 1 ;d+r+l -> down
.byte 0 ;u -> up
.byte 0 ;u+r -> up
.byte 0 ;u+l -> up
.byte 0 ;u+r+l -> up
.byte 0 ;u+d -> up
.byte 0 ;u+d+r -> up
.byte 0 ;u+d+l -> up
.byte 0 ;u+d+r+l -> up
Conclusion
So button combinations are done with, and if you watched the video embedded in the last post you will see that they work quite well! This game is really coming along.