How to get controller working? (reading/writing)

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

How to get controller working? (reading/writing)

Post by Sebastian_L » Wed Feb 24, 2021 10:22 am

Hello,

just tried to get my sprite moving with the controller, but I failed.

Do I use the right code? At the right position?

And do I speak with the right sprite?

Thank you in advance for your answers!

Here is my code so far:

Code: Select all

;; 1. The iNES Header
   .db "NES", $1a		;INES identifier
   .db $01				;number of PRG-Rom blocks the game will have
   .db $01				;number of CHR-Rom blocks the game will have
   .db $00, $01			;control bytes
   .db $00, $00, $00, $00, $00, $00, $00, $00		;filler


;; 2. Constants and Variables
   .enum $0000			;variables will eventually go here
   .ende


;; 3. Set the code starting point
   .org $C000			;this starts the code at the address $C000


;; 4. The RESET routine
;;turn things off and initialize
RESET:
   SEI					;SEI tells the code to ignore interrupts for the routine
   LDA #$00				;load 0 into the accumulator
   STA $2000			;disable the NMI
   STA $2001			;disable rendering
   STA $4010
   STA $4015
   LDA #$40				;loads HEX value 40 which is dec value 64
   STA $4017
   CLD					;disable decimal mode
   LDX #$FF				;loads value 255
   TXS					;initialize the stack
   bit $2002
vBlankWait1:
   bit $2002
   BPL vBlankWait1
   
;;clear out memory etc.
   LDA #$00				;loads zero into the accumulator
   LDX #$00				;loads zero into x
;;ready to start loop
ClearMemomryLoop:
   STA $0000,x			;store accumulator 0 into address $0000+x
   STA $0100,x			;store accumulator 0 into address $0100+x
   STA $0300,x			;store accumulator 0 into address $0300+x
   STA $0400,x			;store accumulator 0 into address $0400+x
   STA $0500,x			;store accumulator 0 into address $0500+x
   STA $0600,x			;store accumulator 0 into address $0600+x
   STA $0700,x			;store accumulator 0 into address $0700+x
   LDA #$FF				
   STA $0200,x			;store accumulator 255 into address $0200+x (sprite tiles)
   INX					;x goes up by one, so all of those + x's at the end that were zero the first time through are increased
   BNE ClearMemomryLoop	;loop again until all adresses + x (#$FF = 255) become 0



vBlankWait2:
   bit $2002
   BPL vBlankWait2

;;turn things back on now that we're set up
   LDA #%10010000		;loads this binary number to accumulator = 144 decimal
   STA $2000			;storing it here, turns NMI back on
   LDA #%00011110		;loads this binary number to accumulator = 30 decimal
   STA $2001			;enables rendering
   
   JMP MainGameLoop


;; 5. The NMI
NMI:
   ;PUSH registers to the stack to preserve them
   PHA					;this pushes the accumulator to the stack, it is the first thing there.
   TXA					;this loads whatever is in X into the accumulator
   PHA					;and pushes it into the accumulator, now the old 'A' is on the bottom and X is on top of it
   TYA					;this loads whatever is in Y into the accumulator
   PHA					;and pushes the accumulator to the stack, now Y is on top, X is in the middle and A is in the bottom
   
   
;;do NMI things here
;;transfer sprites to PPU
   LDA #$00				;puts 0 into the accumulator (low byte)
   STA $2003			;sets the low byte of the sprite RAM address
   LDA #$02				;puts 02 into the accumulator (high byte)
   STA $4014			;sets high byte of the RAM address and starts transfer
   ;read backwards: RAM address we loaded 00
   ;then loaded 02 (this has loaded low byte of 00, high byte 02 = it is a 16-bit address)
   ;high+low (02+00 or $0200), this the address for our sprite data
   
  
   
   
   
   
   
   
   ;load the palettes
   LDA $2002
   LDA #$3F
   STA $2006
   LDA #$00
   STA $2006
   LDX #$00
   
   ;ready to start loop
LoadPaletteLoop:
   LDA MyPalettes,x		;load value from the table that x equals to
   STA $2007			;store it to the address that handles palettes
   INX					;increase x
   CPX #$20				;compare it to hex 20 = 32 decimal (8 banks of 4 colors), so 32-times the loop
   BNE LoadPaletteLoop	;if 32-x is not 0, then start loop again
   
   
   
   
   LDA #%10010000		;turns on NMI, like in RESET = 144 decimal
   STA $2000
   LDA #%00011110		;turns on rendering, like in RESET = 30 decimal   
   STA $2001

   
 ;CONTROLLER   
LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons


ReadA: 
  LDA $4016       ; player 1 - A
  AND #%00000001  ; only look at bit 0
  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  CLC             ; make sure the carry flag is clear
  ADC #$01        ; A = A + 1
  STA $0203       ; save sprite X position
ReadADone:        ; handling this button is done
  

ReadB: 
  LDA $4016       ; player 1 - B
  AND #%00000001  ; only look at bit 0
  BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  SEC             ; make sure carry flag is set
  SBC #$01        ; A = A - 1
  STA $0203       ; save sprite X position
ReadBDone:        ; handling this button is done  
   
   
     

   

   ;PULL registers from the stack and restore them
   ;first thing to pull is the top, then go down
   PLA					;pulls the top stack and put it in the accumulator
   TAY					;puts that value into Y, now Y is restored to what it was before NMI
   PLA					;pulls second stack value and puts it in accumulator
   TAX					;puts that value into X, now X is restored to what it was before NMI
   PLA					;pulls third stack value and puts it in accumulator, now Y, X and A are all restored

   
   
   
   RTI					;at the end of NMI, return from the interrupt

;; 6. The Main Game Loop
MainGameLoop:
   ;game logic will go here
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202
   JMP MainGameLoop		;jumps back to MainGameLoop, create an infinite loop


;; 7. Sub Routines



;; 8. Includes and data tables
MyPalettes:
   ;backgroud
   .db $0F,$11,$17,$0a, $0F,$31,$01,$1c, $0F,$3c,$01,$14, $0F,$21,$39,$03
   ;sprites
   .db $0F,$27,$07,$30, $0F,$06,$05,$31, $0F,$2a,$2c,$16, $0F,$13,$14,$00


;; 9. The Vectors
   .org $fffa			;sets up at the very end of the code
   .dw NMI				;now the NMI points to our label NMI
   .dw RESET			;now the RESET points to our label RESET
   .dw 00
   
   .incbin "mario.chr"	;this includes the graphics for the sprites in the assembly of the game
I have started NES programming! - Please be kind :D

User avatar
Quietust
Posts: 1719
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: How to get controller working? (reading/writing)

Post by Quietust » Wed Feb 24, 2021 10:56 am

Your "MainGameLoop" is constantly resetting the first sprite, so when your NMI routine changes its position (in response to controller input), it gets changed back the instant it returns.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Wed Feb 24, 2021 11:24 am

I looks like you're going for the "all in the NMI" approach, except for the initialization of the sprite, which's in the main loop. Like Quietust said, you're constantly resetting the sprite to its initial position in the main loop, so any movement that takes place in the NMI handler is undone by the main loop. You can simply change the main loop like this and it should work:

Code: Select all

   ;game logic will go here
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202
MainGameLoop:
   JMP MainGameLoop		;jumps back to MainGameLoop, create an infinite loop
However, unlike the comment states, the game logic is NOT running in the main loop, since you're reading the controllers and processing buttom presses in the NMI. You have to decide where you will actually place the game logic.

Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

Re: How to get controller working? (reading/writing)

Post by Sebastian_L » Thu Feb 25, 2021 10:31 am

Hello,

thank you for your answers!

I got it that, the loops sends the sprite in the same position as before.

But if I put the sprite setup before the MainGameLoop, the sprite only moves one pixel left/right and jumps backs to $80/$80 position.

What my wish is, that it stays there and I could move the sprite as long as I want.

Could you please help me?

Here is my code so far:

Code: Select all

;; 1. The iNES Header
   .db "NES", $1a		;INES identifier
   .db $01				;number of PRG-Rom blocks the game will have
   .db $01				;number of CHR-Rom blocks the game will have
   .db $00, $01			;control bytes
   .db $00, $00, $00, $00, $00, $00, $00, $00		;filler


;; 2. Constants and Variables
   .enum $0000			;variables will eventually go here
   .ende


;; 3. Set the code starting point
   .org $C000			;this starts the code at the address $C000


;; 4. The RESET routine
;;turn things off and initialize
RESET:
   SEI					;SEI tells the code to ignore interrupts for the routine
   LDA #$00				;load 0 into the accumulator
   STA $2000			;disable the NMI
   STA $2001			;disable rendering
   STA $4010
   STA $4015
   LDA #$40				;loads HEX value 40 which is dec value 64
   STA $4017
   CLD					;disable decimal mode
   LDX #$FF				;loads value 255
   TXS					;initialize the stack
   bit $2002
vBlankWait1:
   bit $2002
   BPL vBlankWait1
   
;;clear out memory etc.
   LDA #$00				;loads zero into the accumulator
   LDX #$00				;loads zero into x
;;ready to start loop
ClearMemomryLoop:
   STA $0000,x			;store accumulator 0 into address $0000+x
   STA $0100,x			;store accumulator 0 into address $0100+x
   STA $0300,x			;store accumulator 0 into address $0300+x
   STA $0400,x			;store accumulator 0 into address $0400+x
   STA $0500,x			;store accumulator 0 into address $0500+x
   STA $0600,x			;store accumulator 0 into address $0600+x
   STA $0700,x			;store accumulator 0 into address $0700+x
   LDA #$FF				
   STA $0200,x			;store accumulator 255 into address $0200+x (sprite tiles)
   INX					;x goes up by one, so all of those + x's at the end that were zero the first time through are increased
   BNE ClearMemomryLoop	;loop again until all adresses + x (#$FF = 255) become 0



vBlankWait2:
   bit $2002
   BPL vBlankWait2

;;turn things back on now that we're set up
   LDA #%10010000		;loads this binary number to accumulator = 144 decimal
   STA $2000			;storing it here, turns NMI back on
   LDA #%00011110		;loads this binary number to accumulator = 30 decimal
   STA $2001			;enables rendering
   
   JMP MainGameLoop


;; 5. The NMI
NMI:
   ;PUSH registers to the stack to preserve them
   PHA					;this pushes the accumulator to the stack, it is the first thing there.
   TXA					;this loads whatever is in X into the accumulator
   PHA					;and pushes it into the accumulator, now the old 'A' is on the bottom and X is on top of it
   TYA					;this loads whatever is in Y into the accumulator
   PHA					;and pushes the accumulator to the stack, now Y is on top, X is in the middle and A is in the bottom
   
   
;;do NMI things here
;;transfer sprites to PPU
   LDA #$00				;puts 0 into the accumulator (low byte)
   STA $2003			;sets the low byte of the sprite RAM address
   LDA #$02				;puts 02 into the accumulator (high byte)
   STA $4014			;sets high byte of the RAM address and starts transfer
   ;read backwards: RAM address we loaded 00
   ;then loaded 02 (this has loaded low byte of 00, high byte 02 = it is a 16-bit address)
   ;high+low (02+00 or $0200), this the address for our sprite data
   
  
   
   
   
   
   
   
   ;load the palettes
   LDA $2002
   LDA #$3F
   STA $2006
   LDA #$00
   STA $2006
   LDX #$00
   
    
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202  
   
   
   
   
   
   
   
   
   
   ;ready to start loop
LoadPaletteLoop:
   LDA MyPalettes,x		;load value from the table that x equals to
   STA $2007			;store it to the address that handles palettes
   INX					;increase x
   CPX #$20				;compare it to hex 20 = 32 decimal (8 banks of 4 colors), so 32-times the loop
   BNE LoadPaletteLoop	;if 32-x is not 0, then start loop again
   
   
   
   
   LDA #%10010000		;turns on NMI, like in RESET = 144 decimal
   STA $2000
   LDA #%00011110		;turns on rendering, like in RESET = 30 decimal   
   STA $2001

  

   
   
 ;CONTROLLER   
LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons


ReadA: 
  LDA $4016       ; player 1 - A
  AND #%00000001  ; only look at bit 0
  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  CLC             ; make sure the carry flag is clear
  ADC #$01        ; A = A + 1
  STA $0203       ; save sprite X position
ReadADone:        ; handling this button is done
  

ReadB: 
  LDA $4016       ; player 1 - B
  AND #%00000001  ; only look at bit 0
  BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  SEC             ; make sure carry flag is set
  SBC #$01        ; A = A - 1
  STA $0203       ; save sprite X position
ReadBDone:        ; handling this button is done  
   

 
   
      

   

   ;PULL registers from the stack and restore them
   ;first thing to pull is the top, then go down
   PLA					;pulls the top stack and put it in the accumulator
   TAY					;puts that value into Y, now Y is restored to what it was before NMI
   PLA					;pulls second stack value and puts it in accumulator
   TAX					;puts that value into X, now X is restored to what it was before NMI
   PLA					;pulls third stack value and puts it in accumulator, now Y, X and A are all restored
   RTI					;at the end of NMI, return from the interrupt

   
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202





;; 6. The Main Game Loop
MainGameLoop:
   ;game logic will go here

   JMP MainGameLoop		;jumps back to MainGameLoop, create an infinite loop


;; 7. Sub Routines



;; 8. Includes and data tables
MyPalettes:
   ;backgroud
   .db $0F,$11,$17,$0a, $0F,$31,$01,$1c, $0F,$3c,$01,$14, $0F,$21,$39,$03
   ;sprites
   .db $0F,$27,$07,$30, $0F,$06,$05,$31, $0F,$2a,$2c,$16, $0F,$13,$14,$00


;; 9. The Vectors
   .org $fffa			;sets up at the very end of the code
   .dw NMI				;now the NMI points to our label NMI
   .dw RESET			;now the RESET points to our label RESET
   .dw 00
   
   .incbin "mario.chr"	;this includes the graphics for the sprites in the assembly of the game
I have started NES programming! - Please be kind :D

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Thu Feb 25, 2021 10:49 am

Wait a minute, by JMPing directly to the infinite loop you're completely skipping the sprite initialization to ($80, $80), I don't see how the spire could end up at that position at all. You have to make sure that the initialization to ($80, $80) runs only once, before the game enters the infinite loop.

Also, I just realized that your RAM clearing loop is wrong... You added the LDA #$FF to hide the sprites, but you never restore A back to $00, so you end up storing $FF to all other pages on the subsequent iterations. You have to move the LDA #$00 from outside the loop to the beginning of it so pages 0, 1, 3, 4, 5, 6 and 7 have 0 written to them while only page 2 ends up with $FF.

Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

Re: How to get controller working? (reading/writing)

Post by Sebastian_L » Thu Feb 25, 2021 11:04 am

tokumaru wrote:
Thu Feb 25, 2021 10:49 am
Wait a minute, by JMPing directly to the infinite loop you're completely skipping the sprite initialization to ($80, $80), I don't see how the spire could end up at that position at all. You have to make sure that the initialization to ($80, $80) runs only once, before the game enters the infinite loop.

Also, I just realized that your RAM clearing loop is wrong... You added the LDA #$FF to hide the sprites, but you never restore A back to $00, so you end up storing $FF to all other pages on the subsequent iterations. You have to move the LDA #$00 from outside the loop to the beginning of it so pages 0, 1, 3, 4, 5, 6 and 7 have 0 written to them while only page 2 ends up with $FF.

Thank you for your answer!

Where would you put the sprite initalization $80/$80?
Is the RAM clearing loop now correct?

Thank you in advance for your answer!

Code: Select all

;; 1. The iNES Header
   .db "NES", $1a		;INES identifier
   .db $01				;number of PRG-Rom blocks the game will have
   .db $01				;number of CHR-Rom blocks the game will have
   .db $00, $01			;control bytes
   .db $00, $00, $00, $00, $00, $00, $00, $00		;filler


;; 2. Constants and Variables
   .enum $0000			;variables will eventually go here
   .ende


;; 3. Set the code starting point
   .org $C000			;this starts the code at the address $C000


;; 4. The RESET routine
;;turn things off and initialize
RESET:
   SEI					;SEI tells the code to ignore interrupts for the routine
   LDA #$00				;load 0 into the accumulator
   STA $2000			;disable the NMI
   STA $2001			;disable rendering
   STA $4010
   STA $4015
   LDA #$40				;loads HEX value 40 which is dec value 64
   STA $4017
   CLD					;disable decimal mode
   LDX #$FF				;loads value 255
   TXS					;initialize the stack
   bit $2002
vBlankWait1:
   bit $2002
   BPL vBlankWait1
   
   LDX #$00				;loads zero into x
;;ready to start loop
ClearMemomryLoop:
;;clear out memory etc.
   LDA #$00				;loads zero into the accumulator

   STA $0000,x			;store accumulator 0 into address $0000+x
   STA $0100,x			;store accumulator 0 into address $0100+x
   STA $0300,x			;store accumulator 0 into address $0300+x
   STA $0400,x			;store accumulator 0 into address $0400+x
   STA $0500,x			;store accumulator 0 into address $0500+x
   STA $0600,x			;store accumulator 0 into address $0600+x
   STA $0700,x			;store accumulator 0 into address $0700+x
   LDA #$FF				
   STA $0200,x			;store accumulator 255 into address $0200+x (sprite tiles)
   INX					;x goes up by one, so all of those + x's at the end that were zero the first time through are increased
   BNE ClearMemomryLoop	;loop again until all adresses + x (#$FF = 255) become 0



vBlankWait2:
   bit $2002
   BPL vBlankWait2

;;turn things back on now that we're set up
   LDA #%10010000		;loads this binary number to accumulator = 144 decimal
   STA $2000			;storing it here, turns NMI back on
   LDA #%00011110		;loads this binary number to accumulator = 30 decimal
   STA $2001			;enables rendering
   
   JMP MainGameLoop


;; 5. The NMI
NMI:
   ;PUSH registers to the stack to preserve them
   PHA					;this pushes the accumulator to the stack, it is the first thing there.
   TXA					;this loads whatever is in X into the accumulator
   PHA					;and pushes it into the accumulator, now the old 'A' is on the bottom and X is on top of it
   TYA					;this loads whatever is in Y into the accumulator
   PHA					;and pushes the accumulator to the stack, now Y is on top, X is in the middle and A is in the bottom
   
   
;;do NMI things here
;;transfer sprites to PPU
   LDA #$00				;puts 0 into the accumulator (low byte)
   STA $2003			;sets the low byte of the sprite RAM address
   LDA #$02				;puts 02 into the accumulator (high byte)
   STA $4014			;sets high byte of the RAM address and starts transfer
   ;read backwards: RAM address we loaded 00
   ;then loaded 02 (this has loaded low byte of 00, high byte 02 = it is a 16-bit address)
   ;high+low (02+00 or $0200), this the address for our sprite data
   
  
   
   
   
   
   
   
   ;load the palettes
   LDA $2002
   LDA #$3F
   STA $2006
   LDA #$00
   STA $2006
   LDX #$00
   
    
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202  
   
   
   
   
   
   
   
   
   
   ;ready to start loop
LoadPaletteLoop:
   LDA MyPalettes,x		;load value from the table that x equals to
   STA $2007			;store it to the address that handles palettes
   INX					;increase x
   CPX #$20				;compare it to hex 20 = 32 decimal (8 banks of 4 colors), so 32-times the loop
   BNE LoadPaletteLoop	;if 32-x is not 0, then start loop again
   
   
   
   
   LDA #%10010000		;turns on NMI, like in RESET = 144 decimal
   STA $2000
   LDA #%00011110		;turns on rendering, like in RESET = 30 decimal   
   STA $2001

  

   
   
 ;CONTROLLER   
LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons


ReadA: 
  LDA $4016       ; player 1 - A
  AND #%00000001  ; only look at bit 0
  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  CLC             ; make sure the carry flag is clear
  ADC #$01        ; A = A + 1
  STA $0203       ; save sprite X position
ReadADone:        ; handling this button is done
  

ReadB: 
  LDA $4016       ; player 1 - B
  AND #%00000001  ; only look at bit 0
  BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  SEC             ; make sure carry flag is set
  SBC #$01        ; A = A - 1
  STA $0203       ; save sprite X position
ReadBDone:        ; handling this button is done  
   

 
   
      

   

   ;PULL registers from the stack and restore them
   ;first thing to pull is the top, then go down
   PLA					;pulls the top stack and put it in the accumulator
   TAY					;puts that value into Y, now Y is restored to what it was before NMI
   PLA					;pulls second stack value and puts it in accumulator
   TAX					;puts that value into X, now X is restored to what it was before NMI
   PLA					;pulls third stack value and puts it in accumulator, now Y, X and A are all restored
   RTI					;at the end of NMI, return from the interrupt

   
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202

;; 6. The Main Game Loop
MainGameLoop:
   ;game logic will go here

   JMP MainGameLoop		;jumps back to MainGameLoop, create an infinite loop


;; 7. Sub Routines



;; 8. Includes and data tables
MyPalettes:
   ;backgroud
   .db $0F,$11,$17,$0a, $0F,$31,$01,$1c, $0F,$3c,$01,$14, $0F,$21,$39,$03
   ;sprites
   .db $0F,$27,$07,$30, $0F,$06,$05,$31, $0F,$2a,$2c,$16, $0F,$13,$14,$00


;; 9. The Vectors
   .org $fffa			;sets up at the very end of the code
   .dw NMI				;now the NMI points to our label NMI
   .dw RESET			;now the RESET points to our label RESET
   .dw 00
   
   .incbin "mario.chr"	;this includes the graphics for the sprites in the assembly of the game
I have started NES programming! - Please be kind :D

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Thu Feb 25, 2021 1:29 pm

Yeah, the RAM clearing is correct now. TBH, clearing RAM is not a mandatory step when initializing the NES, and if you code your programs well, it doesn't really matter what values are in RAM at the beginning of the program (this is why the program still worked when you had $FF). But if your goal is to write 0s everywhere and $FF to page 2, that's one way to go about it.

As for the sprite initialization, it really depends on how you're structuring your game. Like I said before, the comments on your program so not agree with what it's actually doing, since you reserved a spot for game logic in the main loop but are actually placing the game logic in the NMI handler.

I prefer to put the game logic in the main loop myself, it's a little more complex but it's more versatile. Normally, each "game mode" consists of a initialization phase, followed by a loop. Setting up the initial position of that sprite should be part of the initialization phase in one particular game mode. It could go something like this:

Code: Select all

Reset:
  ;(basic system initialization here)
  jmp InitializeTitleScreen ;boots up the first game mode

Code: Select all

InitializeTitleScreen:
  ;(draw the title screen here)
LoopTitleScreen:
  jsr ReadControllers
  ;(title screen logic goes here)
  ;(at some point, jmp InitializeGameplay)
  ;(wait for the NMI handler to run)
  jmp LoopTitleScreen

Code: Select all

InitializeGameplay:
  ;(initialize gameplay variables here)
  ;(prepare gameplay screen here)
  ;(reset sprite to initial position here)
LoopGameplay:
  jsr ReadControllers
  ;(gameplay logic goes here)
  ;(wait for the NMI handler to run)
  jmp LoopGameplay
In this case, you'd remove all the game logic from the NMI handler, which would just update the sprites (via DMA), set the scroll, and increment a counter to let the main program know that an NMI happened. Something like this:

Code: Select all

NMI:
  ;(backup registers)
  ;(sprite DMA)
  ;(set the scroll)
  ;(run audio engine)
  inc FrameCounter
  ;(restore registers)
  rti
With this you can wait for the NMI in the main thread by waiting for the FrameCounter variable to change:

Code: Select all

  lda FrameCounter
WaitForNMI:
  cmp FrameCounter
  beq WaitForNMI

Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

Re: How to get controller working? (reading/writing)

Post by Sebastian_L » Fri Feb 26, 2021 9:03 am

tokumaru wrote:
Thu Feb 25, 2021 1:29 pm
Yeah, the RAM clearing is correct now. TBH, clearing RAM is not a mandatory step when initializing the NES, and if you code your programs well, it doesn't really matter what values are in RAM at the beginning of the program (this is why the program still worked when you had $FF). But if your goal is to write 0s everywhere and $FF to page 2, that's one way to go about it.

As for the sprite initialization, it really depends on how you're structuring your game. Like I said before, the comments on your program so not agree with what it's actually doing, since you reserved a spot for game logic in the main loop but are actually placing the game logic in the NMI handler.

I prefer to put the game logic in the main loop myself, it's a little more complex but it's more versatile. Normally, each "game mode" consists of a initialization phase, followed by a loop. Setting up the initial position of that sprite should be part of the initialization phase in one particular game mode. It could go something like this:

Code: Select all

Reset:
  ;(basic system initialization here)
  jmp InitializeTitleScreen ;boots up the first game mode

Code: Select all

InitializeTitleScreen:
  ;(draw the title screen here)
LoopTitleScreen:
  jsr ReadControllers
  ;(title screen logic goes here)
  ;(at some point, jmp InitializeGameplay)
  ;(wait for the NMI handler to run)
  jmp LoopTitleScreen

Code: Select all

InitializeGameplay:
  ;(initialize gameplay variables here)
  ;(prepare gameplay screen here)
  ;(reset sprite to initial position here)
LoopGameplay:
  jsr ReadControllers
  ;(gameplay logic goes here)
  ;(wait for the NMI handler to run)
  jmp LoopGameplay
In this case, you'd remove all the game logic from the NMI handler, which would just update the sprites (via DMA), set the scroll, and increment a counter to let the main program know that an NMI happened. Something like this:

Code: Select all

NMI:
  ;(backup registers)
  ;(sprite DMA)
  ;(set the scroll)
  ;(run audio engine)
  inc FrameCounter
  ;(restore registers)
  rti
With this you can wait for the NMI in the main thread by waiting for the FrameCounter variable to change:

Code: Select all

  lda FrameCounter
WaitForNMI:
  cmp FrameCounter
  beq WaitForNMI

Thank you for your answer.

I tried to transfer your answer to my code. But I am not sure if I did correct.

1. Did I seperate the parts correctly?
2. Where do I have to put the framecounter?
3. What do I have to do that a button do nothing? For example "B" should have no effect, if it says press "START".

Thank you in advance for your answer.

Could you please check my code again:
It really helps me.

Code: Select all

;; 1. The iNES Header
   .db "NES", $1a		;INES identifier
   .db $01				;number of PRG-Rom blocks the game will have
   .db $01				;number of CHR-Rom blocks the game will have
   .db $00, $01			;control bytes
   .db $00, $00, $00, $00, $00, $00, $00, $00		;filler


;; 2. Constants and Variables
   .enum $0000			;variables will eventually go here
   .ende


;; 3. Set the code starting point
   .org $C000			;this starts the code at the address $C000


;; 4. The RESET routine
;;turn things off and initialize
RESET:
   SEI					;SEI tells the code to ignore interrupts for the routine
   LDA #$00				;load 0 into the accumulator
   STA $2000			;disable the NMI
   STA $2001			;disable rendering
   STA $4010
   STA $4015
   LDA #$40				;loads HEX value 40 which is dec value 64
   STA $4017
   CLD					;disable decimal mode
   LDX #$FF				;loads value 255
   TXS					;initialize the stack
   bit $2002
vBlankWait1:
   bit $2002
   BPL vBlankWait1
   
   LDX #$00				;loads zero into x
;;ready to start loop
ClearMemomryLoop:
;;clear out memory etc.
   LDA #$00				;loads zero into the accumulator

   STA $0000,x			;store accumulator 0 into address $0000+x
   STA $0100,x			;store accumulator 0 into address $0100+x
   STA $0300,x			;store accumulator 0 into address $0300+x
   STA $0400,x			;store accumulator 0 into address $0400+x
   STA $0500,x			;store accumulator 0 into address $0500+x
   STA $0600,x			;store accumulator 0 into address $0600+x
   STA $0700,x			;store accumulator 0 into address $0700+x
   LDA #$FF				
   STA $0200,x			;store accumulator 255 into address $0200+x (sprite tiles)
   INX					;x goes up by one, so all of those + x's at the end that were zero the first time through are increased
   BNE ClearMemomryLoop	;loop again until all adresses + x (#$FF = 255) become 0



vBlankWait2:
   bit $2002
   BPL vBlankWait2

;;turn things back on now that we're set up
   LDA #%10010000		;loads this binary number to accumulator = 144 decimal
   STA $2000			;storing it here, turns NMI back on
   LDA #%00011110		;loads this binary number to accumulator = 30 decimal
   STA $2001			;enables rendering

InitializeTitleScreen:
   ;draw title screen

LoopTitleScreen:
   jmp InitializeGameplay

InitializeGameplay:
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202

LoopGameplay:
   jsr LatchController
   jmp LoopGameplay
   

;; 5. The NMI
NMI:
   ;PUSH registers to the stack to preserve them
   PHA					;this pushes the accumulator to the stack, it is the first thing there.
   TXA					;this loads whatever is in X into the accumulator
   PHA					;and pushes it into the accumulator, now the old 'A' is on the bottom and X is on top of it
   TYA					;this loads whatever is in Y into the accumulator
   PHA					;and pushes the accumulator to the stack, now Y is on top, X is in the middle and A is in the bottom
   
;;do NMI things here
;;transfer sprites to PPU
   LDA #$00				;puts 0 into the accumulator (low byte)
   STA $2003			;sets the low byte of the sprite RAM address
   LDA #$02				;puts 02 into the accumulator (high byte)
   STA $4014			;sets high byte of the RAM address and starts transfer
   ;read backwards: RAM address we loaded 00
   ;then loaded 02 (this has loaded low byte of 00, high byte 02 = it is a 16-bit address)
   ;high+low (02+00 or $0200), this the address for our sprite data
     
   ;load the palettes
   LDA $2002
   LDA #$3F
   STA $2006
   LDA #$00
   STA $2006
   LDX #$00
   
   ;ready to start loop
LoadPaletteLoop:
   LDA MyPalettes,x		;load value from the table that x equals to
   STA $2007			;store it to the address that handles palettes
   INX					;increase x
   CPX #$20				;compare it to hex 20 = 32 decimal (8 banks of 4 colors), so 32-times the loop
   BNE LoadPaletteLoop	;if 32-x is not 0, then start loop again
     
   LDA #%10010000		;turns on NMI, like in RESET = 144 decimal
   STA $2000
   LDA #%00011110		;turns on rendering, like in RESET = 30 decimal   
   STA $2001
 
 ;CONTROLLER   
LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons

ReadA: 
  LDA $4016       ; player 1 - A
  AND #%00000001  ; only look at bit 0
  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  CLC             ; make sure the carry flag is clear
  ADC #$01        ; A = A + 1
  STA $0203       ; save sprite X position
ReadADone:        ; handling this button is done
  

ReadB: 
  LDA $4016       ; player 1 - B
  AND #%00000001  ; only look at bit 0
  BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  LDA $0203       ; load sprite X position
  SEC             ; make sure carry flag is set
  SBC #$01        ; A = A - 1
  STA $0203       ; save sprite X position
ReadBDone:        ; handling this button is done    

   ;PULL registers from the stack and restore them
   ;first thing to pull is the top, then go down
   PLA					;pulls the top stack and put it in the accumulator
   TAY					;puts that value into Y, now Y is restored to what it was before NMI
   PLA					;pulls second stack value and puts it in accumulator
   TAX					;puts that value into X, now X is restored to what it was before NMI
   PLA					;pulls third stack value and puts it in accumulator, now Y, X and A are all restored
   
   RTI					;at the end of NMI, return from the interrupt


;; 6. Sub Routines


;; 7. Includes and data tables
MyPalettes:
   ;backgroud
   .db $0F,$11,$17,$0a, $0F,$31,$01,$1c, $0F,$3c,$01,$14, $0F,$21,$39,$03
   ;sprites
   .db $0F,$27,$07,$30, $0F,$06,$05,$31, $0F,$2a,$2c,$16, $0F,$13,$14,$00


;; 8. The Vectors
   .org $fffa			;sets up at the very end of the code
   .dw NMI				;now the NMI points to our label NMI
   .dw RESET			;now the RESET points to our label RESET
   .dw 00
   
   .incbin "mario.chr"	;this includes the graphics for the sprites in the assembly of the game
I have started NES programming! - Please be kind :D

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Sun Feb 28, 2021 10:13 am

Hum, there are a few problems here, l'll try to cover everything.

There's no need to have a title screen loop if you don't have a title screen, but you can leave it there with the unconditional jmp InitializeGameplay as a placeholder if you want. Once you do implement a title screen, you'll want to jump to the gameplay only when a specific condition is met (e.g. start is pressed).

The gameplay loop must be frame-limited, otherwise you're gonna read the controller and move the sprites several times per frame, and your sprite will move way faster than it should. This is where the wait for the NMI goes. Before JMPing back to the top of the gameplay loop, wait for the next frame using the code I posted before (i.e. wait for FrameCounter to change). This will guarantee that your have logic will run exactly 60 times per second.

Now here's a BIG problem: LatchController should not be in the middle of your NMI handler if it's a subroutine called by the gameplay loop. The way you have it will even crash the program, because the gameplay loop will jump to the middle of the NMI handler, which will then try to pull a bunch of stuff from the stack that was never put there. Just remove all the controller reading and processing from the NMI handler, and make it a separate subroutine. Everything from LatchController to ReadBDone should be a separate subroutine, with an RTS at the end.

For the NMI wait to work, you have to include a inc FrameCounter anywhere in your NMI handler. It can even be at the very end, before the RTI. This will let the main thread know that the NMI fired, and that it can process another frame.

Fixing the points above should get your code working, but there are still a few things to worry about if you plan on expanding this into an actual game. For example, the NMI can fire at any moment, so you need to consider what will happen if it fires while you're initializing a new game mode. During this time we're usually drawing new backgrounds and stuff, and we don't want the NMI handler disrupting our PPU updates by doing other PPU operations. So solve that we normally have a flag indicating whether the NMI thread is allowed to perform PPU operations bat all.

Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

Re: How to get controller working? (reading/writing)

Post by Sebastian_L » Mon Mar 01, 2021 2:51 am

tokumaru wrote:
Sun Feb 28, 2021 10:13 am
Hum, there are a few problems here, l'll try to cover everything.

There's no need to have a title screen loop if you don't have a title screen, but you can leave it there with the unconditional jmp InitializeGameplay as a placeholder if you want. Once you do implement a title screen, you'll want to jump to the gameplay only when a specific condition is met (e.g. start is pressed).

The gameplay loop must be frame-limited, otherwise you're gonna read the controller and move the sprites several times per frame, and your sprite will move way faster than it should. This is where the wait for the NMI goes. Before JMPing back to the top of the gameplay loop, wait for the next frame using the code I posted before (i.e. wait for FrameCounter to change). This will guarantee that your have logic will run exactly 60 times per second.

Now here's a BIG problem: LatchController should not be in the middle of your NMI handler if it's a subroutine called by the gameplay loop. The way you have it will even crash the program, because the gameplay loop will jump to the middle of the NMI handler, which will then try to pull a bunch of stuff from the stack that was never put there. Just remove all the controller reading and processing from the NMI handler, and make it a separate subroutine. Everything from LatchController to ReadBDone should be a separate subroutine, with an RTS at the end.

For the NMI wait to work, you have to include a inc FrameCounter anywhere in your NMI handler. It can even be at the very end, before the RTI. This will let the main thread know that the NMI fired, and that it can process another frame.

Fixing the points above should get your code working, but there are still a few things to worry about if you plan on expanding this into an actual game. For example, the NMI can fire at any moment, so you need to consider what will happen if it fires while you're initializing a new game mode. During this time we're usually drawing new backgrounds and stuff, and we don't want the NMI handler disrupting our PPU updates by doing other PPU operations. So solve that we normally have a flag indicating whether the NMI thread is allowed to perform PPU operations bat all.

Thank you very much for your detailed answer. It is really helpful and detailed, so that I have organized the code the way above.
For me it is now a bit more clear. Especially if I am a beginner, it is important to have a small radius. When I get better things get even more clearer.

Is the FrameCounter a variable? Am i correct?

Could you please review it again, if it is possible?

What is the next thing to think about? Sprite 0 and VRAM cleaning?

Thank you in advance for your time and answer!

So here is the new code:

Code: Select all

;; 1. The iNES Header
   .db "NES", $1a		;INES identifier
   .db $01				;number of PRG-Rom blocks the game will have
   .db $01				;number of CHR-Rom blocks the game will have
   .db $00, $01			;control bytes
   .db $00, $00, $00, $00, $00, $00, $00, $00		;filler


;; 2. Constants and Variables
   .enum $0000			;variables will eventually go here
    FrameCounter:	db 1
   .ende


;; 3. Set the code starting point
   .org $C000			;this starts the code at the address $C000


;; 4. The RESET routine
;;turn things off and initialize
RESET:
   SEI					;SEI tells the code to ignore interrupts for the routine
   LDA #$00				;load 0 into the accumulator
   STA $2000			;disable the NMI
   STA $2001			;disable rendering
   STA $4010
   STA $4015
   LDA #$40				;loads HEX value 40 which is dec value 64
   STA $4017
   CLD					;disable decimal mode
   LDX #$FF				;loads value 255
   TXS					;initialize the stack
   bit $2002
vBlankWait1:
   bit $2002
   BPL vBlankWait1
   
   LDX #$00				;loads zero into x
;;ready to start loop
ClearMemomryLoop:
;;clear out memory etc.
   LDA #$00				;loads zero into the accumulator

   STA $0000,x			;store accumulator 0 into address $0000+x
   STA $0100,x			;store accumulator 0 into address $0100+x
   STA $0300,x			;store accumulator 0 into address $0300+x
   STA $0400,x			;store accumulator 0 into address $0400+x
   STA $0500,x			;store accumulator 0 into address $0500+x
   STA $0600,x			;store accumulator 0 into address $0600+x
   STA $0700,x			;store accumulator 0 into address $0700+x
   LDA #$FF				
   STA $0200,x			;store accumulator 255 into address $0200+x (sprite tiles)
   INX					;x goes up by one, so all of those + x's at the end that were zero the first time through are increased
   BNE ClearMemomryLoop	;loop again until all adresses + x (#$FF = 255) become 0



vBlankWait2:
   bit $2002
   BPL vBlankWait2

;;turn things back on now that we're set up
   LDA #%10010000		;loads this binary number to accumulator = 144 decimal
   STA $2000			;storing it here, turns NMI back on
   LDA #%00011110		;loads this binary number to accumulator = 30 decimal
   STA $2001			;enables rendering

InitializeTitleScreen:
   ;draw title screen

LoopTitleScreen:
   JMP InitializeGameplay

InitializeGameplay:
   LDA #$80				;load decimal value 128 (=8x16)
   STA $0200			;store it to sprite 1's y address
   LDA #$80				;optional, load a different hex value for the x value
   STA $0203			;and store it to sprite 1's x address
   LDA #$00
   STA $0201
   STA $0202

LoopGameplay:
   JSR LatchController
   LDA FrameCounter
WaitForNMI:
   CMP FrameCounter
   BEQ WaitForNMI
   JMP LoopGameplay
   

;; 5. The NMI
NMI:
   ;PUSH registers to the stack to preserve them
   PHA					;this pushes the accumulator to the stack, it is the first thing there.
   TXA					;this loads whatever is in X into the accumulator
   PHA					;and pushes it into the accumulator, now the old 'A' is on the bottom and X is on top of it
   TYA					;this loads whatever is in Y into the accumulator
   PHA					;and pushes the accumulator to the stack, now Y is on top, X is in the middle and A is in the bottom
   
;;do NMI things here
;;transfer sprites to PPU
   LDA #$00				;puts 0 into the accumulator (low byte)
   STA $2003			;sets the low byte of the sprite RAM address
   LDA #$02				;puts 02 into the accumulator (high byte)
   STA $4014			;sets high byte of the RAM address and starts transfer
   ;read backwards: RAM address we loaded 00
   ;then loaded 02 (this has loaded low byte of 00, high byte 02 = it is a 16-bit address)
   ;high+low (02+00 or $0200), this the address for our sprite data
     
   ;load the palettes
   LDA $2002
   LDA #$3F
   STA $2006
   LDA #$00
   STA $2006
   LDX #$00
   
   ;ready to start loop
LoadPaletteLoop:
   LDA MyPalettes,x		;load value from the table that x equals to
   STA $2007			;store it to the address that handles palettes
   INX					;increase x
   CPX #$20				;compare it to hex 20 = 32 decimal (8 banks of 4 colors), so 32-times the loop
   BNE LoadPaletteLoop	;if 32-x is not 0, then start loop again
     
   LDA #%10010000		;turns on NMI, like in RESET = 144 decimal
   STA $2000
   LDA #%00011110		;turns on rendering, like in RESET = 30 decimal   
   STA $2001
 


   ;PULL registers from the stack and restore them
   ;first thing to pull is the top, then go down
   PLA					;pulls the top stack and put it in the accumulator
   TAY					;puts that value into Y, now Y is restored to what it was before NMI
   PLA					;pulls second stack value and puts it in accumulator
   TAX					;puts that value into X, now X is restored to what it was before NMI
   PLA					;pulls third stack value and puts it in accumulator, now Y, X and A are all restored
   INC FrameCounter		;NMI fired and can process another frame
   RTI					;at the end of NMI, return from the interrupt


;; 6. Sub Routines
 ;CONTROLLER   
LatchController:
   LDA #$01
   STA $4016
   LDA #$00
   STA $4016       ; tell both the controllers to latch buttons

ReadA: 
   LDA $4016       ; player 1 - A
   AND #%00000001  ; only look at bit 0
   BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                   ; add instructions here to do something when button IS pressed (1)
   LDA $0203       ; load sprite X position
   CLC             ; make sure the carry flag is clear
   ADC #$01        ; A = A + 1
   STA $0203       ; save sprite X position
ReadADone:        ; handling this button is done
  

ReadB: 
   LDA $4016       ; player 1 - B
   AND #%00000001  ; only look at bit 0
   BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                   ; add instructions here to do something when button IS pressed (1)
   LDA $0203       ; load sprite X position
   SEC             ; make sure carry flag is set
   SBC #$01        ; A = A - 1
   STA $0203       ; save sprite X position
ReadBDone:        ; handling this button is done    

   RTS

;; 7. Includes and data tables
MyPalettes:
   ;backgroud
   .db $0F,$11,$17,$0a, $0F,$31,$01,$1c, $0F,$3c,$01,$14, $0F,$21,$39,$03
   ;sprites
   .db $0F,$27,$07,$30, $0F,$06,$05,$31, $0F,$2a,$2c,$16, $0F,$13,$14,$00


;; 8. The Vectors
   .org $fffa			;sets up at the very end of the code
   .dw NMI				;now the NMI points to our label NMI
   .dw RESET			;now the RESET points to our label RESET
   .dw 00
   
   .incbin "mario.chr"	;this includes the graphics for the sprites in the assembly of the game
I have started NES programming! - Please be kind :D

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Mon Mar 01, 2021 3:09 am

FrameCounter is a variable, yes.

It looks okay to me now. Is it working?

For the next step I think you should implement proper controller reading. The method you're using has several drawbacks, and no games do it like that. What real games do is read all 8 bits of controller state into a variable at the beginning of the frame, and then use that variable whenever a decision has to be made based on input.

This will also take care of the situation you asked about before, about skipping buttons in order to read a specific button.

Sebastian_L
Posts: 17
Joined: Tue Feb 16, 2021 12:31 pm

Re: How to get controller working? (reading/writing)

Post by Sebastian_L » Mon Mar 01, 2021 6:57 am

tokumaru wrote:
Mon Mar 01, 2021 3:09 am
FrameCounter is a variable, yes.

It looks okay to me now. Is it working?

For the next step I think you should implement proper controller reading. The method you're using has several drawbacks, and no games do it like that. What real games do is read all 8 bits of controller state into a variable at the beginning of the frame, and then use that variable whenever a decision has to be made based on input.

This will also take care of the situation you asked about before, about skipping buttons in order to read a specific button.

Thank you, yes it is running good now.

Do you mean that all buttons should be hold in 1 variable?
Now I don't use a variable for the button.

Thank you in advance for your answer!
I have started NES programming! - Please be kind :D

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Mon Mar 01, 2021 6:31 pm

Sebastian_L wrote:
Mon Mar 01, 2021 6:57 am
Do you mean that all buttons should be hold in 1 variable?
Yes, this is what most games do. A byte has 8 bits, exactly what need to store the state of all the buttons in an NES controller. I stead of ANDing and BEQing to test each button, you can just shift the state bit out of the value read from $4016 and into the ControllerState variable (e.g. LSR + ROL). Do this 8 times and your variable will contain the states of all 8 buttons.

Then you can use constants to make the input processing more readable:

Code: Select all

CONTROLLER_A = %10000000
CONTROLLER_B = %01000000
CONTROLLER_SELECT = %00100000
CONTROLLER_START = %00010000
CONTROLLER_UP = %00001000
CONTROLLER_DOWN %00000100
CONTROLLER_LEFT %00000010
CONTROLLER_RIGHT = %00000001
You can then process input like this, in any order you want:

Code: Select all

;accept Start or A to start the game
lda ControllerState
and #CONTROLLER_START|CONTROLLER_A
bne InitializeGameplay 
A LOT of things in NES game programming require buffering, signaling, flagging, etc. in place of immediate processing. Even in this common case of starting the game from the title screen: in the simple example above, the game will start immediately after the button is pressed, but in actual games that rarely happens - usually the text blinks, a sound is played, and then the transition happens. You have to consider all of these little things, the order of events and the state transitions when designing your code. And always keep in mind that all the logic happens in little slices of 1/60th of a second, because that's the time between consecutive iterations of your game loops.
Last edited by tokumaru on Mon Mar 01, 2021 6:41 pm, edited 1 time in total.

User avatar
Quietust
Posts: 1719
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: How to get controller working? (reading/writing)

Post by Quietust » Mon Mar 01, 2021 6:36 pm

tokumaru wrote:
Mon Mar 01, 2021 6:31 pm
Yes, this is what most games do. A byte has 8 bits, exactly what need to store the state of all the buttons in an NES controller. I stead of ANDing and BEQing to test each button, you can just shift the state bit out of the value read from $4016 and into the ControllerState variable (e.g. LSR + ROL). Do this 8 times and your variable will contain the states of all 8 buttons.
Furthermore, you might find it useful to maintain 3 state bytes for each controller: one for the current frame, one for the previous frame, and another for buttons which became pressed during the current frame (calculated as LDA cur; EOR prev; AND cur).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

User avatar
tokumaru
Posts: 12056
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: How to get controller working? (reading/writing)

Post by tokumaru » Mon Mar 01, 2021 6:47 pm

What Quietust said is particularly important when processing things that should happen only once when a button is pressed. For example, shooting. When the player presses the fire button, you don't want to spawn a new bullet for every frame that the button is held down, you only want to do it when the button state transitions from "not pressed" to "pressed". This is in contrast to walking, for example, where you do want the
main character to keep moving in a direction for as long as the directional button is held.

Post Reply