In this tutorial, you'll learn to read the joypad registers and react to player input ;). We're going to need to store the player's input somewhere, so we'll declare some variables in the system's RAM (
$0000-$1FFF). If you make your own variables later in this part of RAM, keep in mind that the stack is set to
$1FFF, so try not to get too close to that area (of course). We'll define variables for both Controllers (aka joypads) 1 & 2.
Note: Up to 4 controllers can receive input through a multitap. You can expand my code to do so yourself :P. There is a doc explaining this in the snesbase pack.
.ENUM $0000 Joy1Raw DW ; Holder of RAW joypad data from register (from last frame) Joy2Raw DW Joy1Press DW ; Contains only pressed buttons (not held down) Joy2Press DW Joy1Held DW ; Contains only buttons that are Held Joy2Held DW .ENDE
Alright, that's the variables.
JoyRaw will act as a log of the raw data received from reading the joypad registers.
JoyPress will hold the buttons that only been pressed for 1 frame.
JoyHeld will hold data for the buttons that have been pressed continuously for > 1 frame. One example of the effects you can do with button holds is running, such as in Super Mario World. Once Y is pressed, Mario will accelerate as long Y is held until he reaches max speed. Once Y is released, he slows down again. ta-da.
.ENUM is an easy way to map names to addresses.
Joy1 is a word in size (DW), so WLA just mapped
Joy1, a word). The other variables are mapped in the same way. If you wanted to do it manually, you could
xxxx $0000, but I personally like this method because it automatically skips 1 or 2 addresses if it's a byte or word..
Now, let's get on to reading the controllers, shall we? Onto some registers.
Register $4200: Counter Enable (1b/W) n-vh---j n: NMI enable v: vertical counter enable h: horizontal enable register j: joypad enable
We encountered this register in the VBlank tutorial. This time we will write to bit 0 of this register as well. This causes the SNES to read the joypad(s).
Register $4212: Status Register (1b/RW) vh-----j v: 0 = Not in VBlank state. 1 = In VBlank state. h: 0 = Not in HBlank state. 1 = In HBlank state. j: 0 = Joypad not ready. 1 = Joypad ready.
We just check bit 0 here to see if the joypad is ready to be read from. If the bit is set, the joypads are NOT ready to be read from.
Register $4218: Joypad #1 status register (Low Byte) (1b/R) axlriiii a: A button x: X button l: L r: R i: Identification code
The button bits are set when the buttons are pressed. Also, the first 4 bits (i) identify the type of controller connected. 0000 is the ID for a standard snes controller. If these bits output anything else, there is something else plugged in or corrupt data. I'm not going to check these in the routine because it's kind of pointless for homebrew. You either plug in the normal controller or the program won't work.. I mean DUH. More important for commercial games to check that...
Register $4219: Joypad #1 status register (High Byte) (1b/R) bystudlr b: B button u: Up y: Y button d: Down s: Select l: Left t: Start r: Right
$4218 can be read with a 16 bit
A/X/Y and both
$4219 will be read at the same time. I think you can read or write to an address like that. But remember that some registers require writing twice, one byte at a time.
Register $421A-$421F - Same as $4218-$4219 but for joypads 2-4. Registers $4016-$4017: Old-style joypad registers (2*1b/RW) $4016 - Player 1 Joypad $4017 - Player 2 Joypad
You can use this register to read the buttons like the NES did. Bit 0 of
$4200 needs to be clear for this to work. If it is not, then the registers can be used to return whether the joypad is connected (0=Not Connected). First writing a 0 to
$4016 must be done for this to work..
So here's what's going to happen. When we enable NMI we'll also enable reading of the joypads. That will be done through
$4200. We'll also write a 0 to
$4016 to have the ability of reading it to check if the pads are connected. After that it's just a matter of jumping to the input routine during VBlank.
; Input Cheat Sheet ; $4218 ; $80 = A ; $40 = X ; $20 = L ; $10 = R ; ; $4219: ; $80 = B ; $40 = Y ; $20 = Select ; $10 = Start ; $08 = Up ; $04 = Down ; $02 = Left ; $01 = Right ; Let's define it to make it easier.. ; $4218 .EQU Button_A $80 .EQU Button_X $40 .EQU Button_L $20 .EQU Button_R $10 ; $4219 .EQU Button_B $80 .EQU Button_Y $40 .EQU Button_Select $20 .EQU Button_Start $10 .EQU Button_Up $08 .EQU Button_Down $04 .EQU Button_Left $02 .EQU Button_Right $01 ; .... ; .. ; During initialization.. stz $4016 ; Write a byte of nothing to $4016. You know why I did that? ;.. almost ready to enter main loop.. lda #$81 sta $4200 ; Enable NMI and auto-joypad read Gameloop: WAI ;... ;. ;.... ; react to input here.. jmp Gameloop VBlank: ;... ;.. ;.. ;. jsr Joypad ;. ;.. rti ; Time for the real code Joypad: lda $4212 ; auto-read joypad status and #$01 ; bne Joypad ; read is done when 0 rep #$30 ; A/X/Y - 16 bit ; Player 1 ldx Joy1Raw ; load log of last frame's RAW read of $4218 ; the log will be 0 the first time read of course.. lda $4218 ; Read current frame's RAW joypad data sta Joy1Raw ; save it for next frame.. (last frame log is still in X) txa ; transfer last frame input from X -> A (it's still in X) eor Joy1Raw ; Xor last frame input with current frame input ; shows the changes in input ; buttons just pressed or just released become set. ; Held or unactive buttons are 0 and Joy1Raw ; AND changes to current frame's input. ; this ends up leaving you with the only the buttons that ; are pressed.. It's MAGIC! sta Joy1Press ; Store just pressed buttons txa ; Transfer last frame input from X -> A again and Joy1Raw ; Find buttons that are still pressed (held) sta Joy1Held ; by storing only buttons that are pressed both frames ; Player 2 ; Repeat :) ldx Joy2Raw lda $421A ; Read Joypad2 Regs sta Joy2Raw txa eor Joy2Raw ; Find just triggered buttons and Joy2Raw sta Joy2Press txa and Joy2Raw ; Find buttons that are still pressed (held) sta Joy2Held ; Joypads standard (ie not a mouse or superscope..) and connected? sep #$20 ldx #$0000 ; we'll clear recorded input if pad is invalid lda $4016 ; Pad 1 - now we read this (after we stored a 0 to it earlier) bne _check2 ; $4016 returns 0 if not connected, 1 if connected stx Joy1Raw ; otherwise clear all recorded input.. it's not valid.. stx Joy1Press stx Joy1Held _check2: lda $4017 ; Pad 2 bne _done ; 0=not connected, stx Joy2Raw stx Joy2Press stx Joy2Held _done: RTS
On to Programming with FastROM!
Tutorial by bazz