How to Register a Button Press vs a Button Hold?

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

How to Register a Button Press vs a Button Hold?

Post by eskayelle » Mon Aug 10, 2020 7:00 pm

Code like this

Code: Select all

  CMP #%00000001
  BEQ jumpright1
  CMP #%00000010
  BEQ jumpleft1
  CMP #%00000100
  BEQ jumpdown1
  CMP #%00001000
  BEQ jumpup1
  CMP #%00010000
  BEQ jumpstart1
  CMP #%00100000
  BEQ jumpselect1
  CMP #%01000000
  BEQ jumpB1
  CMP #%10000000
  BEQ jumpA1
without much more context seems to presume an operation will continue to happen (like walking left or right) as a certain button is held. What if I wanted the Start or Select button to perform an action, but only do so upon a single button press (e.g., move from a selection screen to the resulting gameplay screen)? How might I code such that only the first press is registered, even if I continue to hold down the intended button?

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Mon Aug 10, 2020 7:50 pm

Sup buddy. You want to save the previous frame button press byte in another byte (say, before reading the controller) and compare them after reading the controller. If the previous frame is not pressed and the current frame is pressed, you have your button press.
Image

User avatar
dougeff
Posts: 2735
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by dougeff » Mon Aug 10, 2020 8:12 pm

look at the neslib code...near the end, after it gets the current buttons.

Code: Select all

	sta <PAD_STATE,y
	tax
	eor <PAD_STATEP,y
	and <PAD_STATE ,y
	sta <PAD_STATET,y
	txa
	sta <PAD_STATEP,y
y is used to select the 2 ports. 0 or 1.

state is the current frame's buttons.
state p is the Previous buttons.
state t is NEW buttons. A bit is only 1 if it wasn't held down.

Edit, also your compare code is a bit lacking. I would do, like

LDA PAD_STATET
and #LEFT_BUTTON
BEQ @skip
JSR Handle_Left
@skip
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Tue Aug 11, 2020 8:40 am

dougeff wrote:
Mon Aug 10, 2020 8:12 pm
your compare code is a bit lacking.
He's right. This is a ticking time bomb, and it is (likely) only a matter of time until your code fails. Why?

Branch instructions (BEQ, BPL, BCC, etc...) can only move the program counter 127 bytes forward or 128 bytes backward, as they are encoded with a single (signed) byte, as opposed to a actual 16bit memory address (like JMP or JSR). They can only move the PC relative to their own position, and not very far. As your code grows, you may find your button routines out of range of your BEQ soon...

From what i understand, NESASM3 will not even warn you about this, so be aware. What a wonderful feature :roll:
Image

User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by eskayelle » Tue Aug 11, 2020 1:05 pm

Ha! Oh yeah, my code had failed a bunch of times when I first implemented this. NESASM had given me enough "BEQ out of range!" warnings to make my head spin, so I had worked around this one with a BEQ to a label that then JSRs to my actual subroutine. Efficient...

I did some cleanup on the code this morning to better fit standard practice, but I more or less hate what it's doing. My original code kills the animation and movement entirely when you hit a wall. Using best practices, I get wiggling feet trying to go though the wall (thankfully unsuccessfully), some kind of stutter in the movement at the wall where ADC and SBC > #$02 are used, AND caused animation conflicts when pressing two buttons, and CMP seemed to have reversed my buttons, so I had to reverse labels to make up go up, etc.

If programming and romhacking has taught me anything these past few years, it's that 2 wrongs (or 17, in my case) CAN make a right. :beer:
Last edited by eskayelle on Tue Aug 11, 2020 1:22 pm, edited 1 time in total.

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Tue Aug 11, 2020 1:15 pm

eskayelle wrote:
Tue Aug 11, 2020 1:05 pm
I had worked around this one with a BEQ to a label that then JSRs to my actual subroutine.
What happens if you push more than one button?
Image

User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by eskayelle » Tue Aug 11, 2020 1:25 pm

In my original code? No movement or animation is allowed; the sprite screeches to a halt. I like that effect. Only minor nit is that diagonals will halt movement to, so you gotta be a tad more precise with joystick use (versus gamepad use).

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Tue Aug 11, 2020 1:48 pm

eskayelle wrote:
Tue Aug 11, 2020 1:25 pm
In my original code? No movement or animation is allowed; the sprite screeches to a halt. I like that effect. Only minor nit is that diagonals will halt movement to, so you gotta be a tad more precise with joystick use (versus gamepad use).
That's an... interesting design choice...

The point i was trying to make is that your code in its current state will not handle more than one button or directional input, as it will jump away after the first input detected, and never finish detecting the rest of the controller. Doug's method (...and also mine that i suggested in your original thread) will. I don't want to sound harsh, but, it is bad practice to have a controller input detection routine that may fail at detecting controller input.

I will also say that on original hardware, dPads can be a bit flaky, especially worn in square controller ones, and can easily put in more than one direction by accident if your thumb isn't super careful. This could lead to ...user issues and potential frustration.

...I have "fond" memories of losing a few seconds during Kirby's Adventure speedruns when my old controller would register an up input when i was "clearly" only holding right, putting him in the slow "flying" state from the ground... good times...
Image

User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by eskayelle » Wed Aug 12, 2020 8:40 am

Nope, nothing harsh interpreted here... I definitely appreciate all the guidance from everyone to date.
your code in its current state will not handle more than one button or directional input, as it will jump away after the first input detected, and never finish detecting the rest of the controller.
I'm not sure I follow that. My understanding is that the NES looks for inputs in a certain sequence (right, left, down, up, start, select, B, A), which my code follows. The button press (hold at this point, really) is loaded and compared to each bit in that order. When there's a match, it branches to two subroutines to process the opcodes associated with holding that button. Each subroutine has an RTS, so the code jumps right back to the original set of compare opcodes. So if a button isn't pressed, the code checks the next. If a button IS pressed, the code does something, then returns and checks for that next bit.

Running the game and viewing RAM while it plays, I can see all the button holds happening. It's detecting everything I'm throwing at it.

I did figure out how to get diagonals working; to your point, it would be nice to have some output happening there.

So I currently have this as my rough code now:

Code: Select all

  LDX #$08		 ; 8 increments, one per button
ReadController1Loop:
  LDA $4016
  LSR A    	         ; bit0 -> Carry
  ROL buttons1   	 ; bit0 <- Carry
  DEX
  BNE ReadController1Loop
  LDA buttons1		 ; NES always goes in this order: rlduSsBA
  CMP #RIGHT_BUTTON	 ; look for right button bit from $4016
  BEQ jumpright1	 ; if found, jump to right button label and run that subroutine
  CMP #LEFT_BUTTON       ; if not found (or upon return from that subroutine), move on to left...
  BEQ jumpleft1
  CMP #DOWN_BUTTON	 ; then down, etc.
  BEQ jumpdown1
  CMP #UP_BUTTON
  BEQ jumpup1
  CMP #START_BUTTON
  BEQ jumpstart1
  CMP #SELECT_BUTTON
  BEQ jumpselect1
  CMP #B_BUTTON
  BEQ jumpB1
  CMP #A_BUTTON
  BEQ jumpA1
  CMP #RIGHT_UP_BUTTON
  BEQ jumpup1
  CMP #LEFT_UP_BUTTON
  BEQ jumpup1
  CMP #RIGHT_DOWN_BUTTON
  BEQ jumpdown1
  CMP #LEFT_DOWN_BUTTON
  BEQ jumpdown1
  RTS			 ; use of AND instead would be a best practice, but it creates unwanted animations

jumpright1:		 ; create these labels as subroutines per button so the BEQs aren't out of range
  JSR right1_button_subroutine
  RTS

jumpleft1:
  JSR left1_button_subroutine
  RTS

jumpdown1:
  JSR down1_button_subroutine
  RTS

jumpup1:
  JSR up1_button_subroutine
  RTS

jumpstart1:
  JSR start1_button_subroutine
  RTS

jumpselect1:
  JSR select1_button_subroutine
  RTS

jumpB1:
  JSR B1_button_subroutine
  RTS

jumpA1:
  JSR A1_button_subroutine
  RTS
Example button press (hold...) subroutine:

Code: Select all

start1_button_subroutine:
  LDA gamestate
  CMP #STATE_GAMEOVER    ; if it's not game over, check for title screen
  BNE checktitle
checktitle:
  LDA gamestate
  CMP #STATE_TITLE  	 ; if it's not title screen, do nothing
  BNE ReadStart1Done
  LDA #ONEUP
  STA twopgame
  LDA #STATE_PLAYING	 ; if it is title screen, begin play
  STA gamestate
  LDA #%00000000         
  STA $2000
  STA $2001
  STA $4015		 ; stop the title screen sound when pressed!
  JSR LoadField
ReadStart1Done
  RTS
  

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Wed Aug 12, 2020 10:28 am

eskayelle wrote:
Wed Aug 12, 2020 8:40 am
Each subroutine has an RTS, so the code jumps right back to the original set of compare opcodes.
Ah! You may think it does, but it does not.
It is returning to where you called (the label before ReadController1Loop) from, presumably.
eskayelle wrote:
Wed Aug 12, 2020 8:40 am
I'm not sure I follow that.
Right, so, its not an NES thing, it's the fact that your code BEQ's away from the first part of controller reading routine and never returns to your detection logic after the first button it detects is a hit. RTS does not return to BEQ or any branch instruction. Your RTS will never return to the point where it detects the other buttons. Do you understand?

The code should look something like this:

Code: Select all

readController1Start:
; LDX #$08 

; Also you should store your previous buttons here, 
; so you can detect the first button press frame, like:

; LDA buttons1
; STA previousButtons1

ReadController1Loop:
  LDA $4016
  LSR A    	         ; bit0 -> Carry
  ROL buttons1   	 ; bit0 <- Carry
  DEX
  BNE ReadController1Loop
  
  ; Yes, you need to do LDA buttons1 every time 
  ; If it jumps into the subroutine, your accumulator will probably change.
  
  ; *(Unless you LDA buttons1 after each button_subroutine is done and before RTS) 
  ; *(that is probably a better and quicker solution.) 
  
  LDA buttons1		 ; NES always goes in this order: rlduSsBA
  AND #RIGHT_BUTTON
  BEQ +
    JSR right1_button_subroutine	 ; if found, jump to right button label and run that subroutine
  +
  
  LDA buttons1 ; *
  AND #LEFT_BUTTON       ; if not found (or upon return from that subroutine), move on to left...
  BEQ +
    JSR left1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #DOWN_BUTTON	 ; then down, etc.
  BEQ +
    JSR down1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #UP_BUTTON
  BEQ +
    JSR up1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #START_BUTTON
  BEQ +
    JSR  start1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #SELECT_BUTTON
  BEQ +
    JSR select1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #B_BUTTON
  BEQ +
    JSR B1_button_subroutine
  +
  
  LDA buttons1 ; *
  AND #A_BUTTON
  BEQ +
    JSR A1_button_subroutine
  +
  
 ; You honestly shouldn't do this next part. 
 ; Each direction should be tested for individually, and handled accordingly...
 
 ; But, if you Insist on having "multi direction behavior" subroutines, (which you should NOT)
 ; handle it in the beginning of the first called direction subroutine.
 ; In the 2nd called direction subroutine, if the other direction is pressed, just RTS, i guess...
 
 ; CMP #RIGHT_UP_BUTTON
 ; BEQ jumpup1
  
 ; CMP #LEFT_UP_BUTTON
 ; BEQ jumpup1
  
 ; CMP #RIGHT_DOWN_BUTTON
 ; BEQ jumpdown1
  
 ; CMP #LEFT_DOWN_BUTTON
 ; BEQ jumpdown1
  
  RTS			 ; use of AND instead would be a best practice, but it creates unwanted animations
  
Fig 1: Do you see the difference how this returns to the button subroutine detection after each button is done detecting?

Also, CMP will yield a result if ONLY that button/direction is being pressed, as opposed to AND, which will isolate the bit and ignore the other inputs. This is proper technique. You want to detect and handle all inputs in all cases. ...What you do with them after that it up to you.

Anyway, if having your code this way makes your animations wrong, well, don't blame the controller reading code =p
You will just have to tweak how you are doing your animations. Use the step through debugger and figure it out!
Image

User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by eskayelle » Wed Aug 12, 2020 2:50 pm

your code BEQ's away from the first part of controller reading routine and never returns to your detection logic after the first button it detects is a hit. RTS does not return to BEQ or any branch instruction.
I think this is the piece I needed to follow why your example code does something different from mine. I still find it funny that the buttons appear detected, but mine's also a simpler game, so I assume with more complex inputs, the difference is there.

I ended up with something like this. The diagonal subroutine is just a clean-up sub I'm using to take care of those extra bits I don't care for.

Code: Select all

ReadController1:	 	; Controller 1 latch is elsewhere (the hits to $4016)
   LDX #$08

; Can store your previous buttons here, so you can detect the first button press frame, like:
  LDA buttons1
  STA prevbuttons1

ReadController1Loop:
  LDA $4016
  LSR A    	         	; bit0 -> Carry
  ROL buttons1   	 	; bit0 <- Carry
  DEX
  BNE ReadController1Loop
   
  LDA buttons1			; * need to do LDA buttons1 each time, since jumping into subroutines could change the accumulator
  JSR diagonals		 	; subroutine to clean up diagonals and button mashing
  AND #RIGHT_BUTTON	 	; NES always goes in this order: rlduSsBA
  BEQ .skip1
  JSR right1_button_subroutine	; if found, jump to right button label and run that subroutine
.skip1:
  
  LDA buttons1 			; *
  JSR diagonals			; (in other games, likely don't need this cleanup, but Pong is only using 2 directions (up and down))
  AND #LEFT_BUTTON       	; if not found (or upon return from that subroutine), move on to left...
  BEQ .skip2
  JSR left1_button_subroutine
.skip2:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #DOWN_BUTTON	 	; then down, etc.
  BEQ .skip3
  JSR down1_button_subroutine
.skip3:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #UP_BUTTON
  BEQ .skip4
  JSR up1_button_subroutine
.skip4:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #START_BUTTON
  BEQ .skip5
  JSR start1_button_subroutine
.skip5:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #SELECT_BUTTON
  BEQ .skip6
  JSR select1_button_subroutine
.skip6:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #B_BUTTON
  BEQ .skip7
  JSR B1_button_subroutine
.skip7:
  
  LDA buttons1 			; *
  JSR diagonals
  AND #A_BUTTON
  BEQ .skip8
  JSR A1_button_subroutine
.skip8:
  RTS			 	; use of AND vs CMP is a best practice, but in this code it creates unwanted animations, unless...
I think this gets me closer to best practices, should I want to steal this code for future games, and I just need to remove that diagonals jump.

I'm next moving to those button presses versus holds notes, but I'm struggling a bit. dougeff's code seems to have 3 bytes being transferred between: a current pad state, a previous pad state, and a new pad state. There also appear to be stores and bitwise operations on low bytes, so I'm not sure why each would need to be a 16-bit number or why 3 states are needed versus 2 (current and previous). Assuming I'm interpreting that code wrong (as I'm not quite getting what it's trying to accomplish just yet, and I don't know ca65 or much C; I'm only working with NESASM right now), when I look at the other suggestion to make 2 bytes, a current and a previous, and move the value from current to previous when the controller read begins, I'm not quite following when the two bytes will differ such that a bitwise operation can we coded to "lock" the value (in another byte???) and force the controller to stop reading the hold and only read a press. How does that work?

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Wed Aug 12, 2020 3:10 pm

eskayelle wrote:
Wed Aug 12, 2020 2:50 pm
I still find it funny that the buttons appear detected
They are detected in the controller read loop, but your subroutines would never run after one button subroutine ran, because after you BEQ'd the JSR would never return to where you BEQ'd from to test/run the rest of the button subroutines in your previous code. Get it?
eskayelle wrote:
Wed Aug 12, 2020 2:50 pm
I'm next moving to those button presses versus holds notes, but I'm struggling a bit.
It's easy man. Ignore all that over-complication...

All you have to do is check prevbuttons1 at the beginning of your button/direction subroutines.
You can do it like this:

Code: Select all

A_button_subroutine:
LDA prevButtons1
AND #A_BUTTON
BNE + 
  ; This will be 0 only if the button was not pressed last frame, hence, your onPress
  JSR A_button_onPress_subroutine
  ; RTS ; You can RTS here if you dont want to run the "a button held" subroutine
+

; proceed with your A button held subroutine...
Do that in your button / direction subroutines, and you're golden!

...I would still factor out that multidirectional stuff. The only reason you needed it (probably) was because you were using CMP instead of AND. Your code (should) just work fine now without it. You're welcome =p
Image

User avatar
eskayelle
Posts: 22
Joined: Wed Jul 29, 2020 5:07 pm
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by eskayelle » Wed Aug 12, 2020 6:44 pm

Thanks!

NESASM is not liking the use of "+" or "@" for loops, so I've been working around it. I gave this a test drive for my Start button routine, the Start button being used at either game over or at the title screen.

Code: Select all

start1_button_subroutine:
  LDA prevbuttons1	 ; check for previous button
  AND #START_BUTTON      ; AND with START; same will equal 1
  BNE pressstart	 ; will be 0 only if button was not pressed last frame
  RTS			 ; RTS here because don't want a button held routine

pressstart:
  LDA gamestate
  CMP #STATE_GAMEOVER    ; compare gamestate to game over state
  BNE checktitle	 ; if it's not game over, check for title screen state
  JSR RESET	 	 ; if it is game over, reset to title screen
  RTS

checktitle:		 ; a press above is going here, while a hold goes here and then starts the game
  LDA gamestate
  CMP #STATE_TITLE  	 ; after looking for game over state, next look for title screen state
  BNE ReadStart1Done	 ; if it's not title screen, do nothing
  LDA #ONEUP
  STA twopgame
  LDA #STATE_PLAYING	 ; if it is title screen, begin play
  STA gamestate
  LDA #%00000000         
  STA $2000
  STA $2001
  STA $4015		 ; stop the title screen sound when pressed!
  JSR LoadField
ReadStart1Done
  RTS
Is the RTS you commented on (below BNE pressstart) supposed to negate a button held subroutine entirely? Currently, it allows a button press to get to the title screen on game over, but if Start is held at game over, it'll jump to title screen and right into gameplay rather than force two button presses (one to get to title screen and one to start play). Is it supposed to do that? Since each frame would have the same button press recorded during a hold, I'm not sure how this would be prevented without some sort of button release code that zeroed out the prevbuttons1 byte, no?

User avatar
dougeff
Posts: 2735
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by dougeff » Wed Aug 12, 2020 6:50 pm

Some games have a timer that counts down on the title screen, that rejects button presses until a certain amount of time has passed. 256 frames is 4 seconds. Seems enough to me.
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
Controllerhead
Posts: 147
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: How to Register a Button Press vs a Button Hold?

Post by Controllerhead » Wed Aug 12, 2020 7:22 pm

eskayelle wrote:
Wed Aug 12, 2020 6:44 pm
Currently, it allows a button press to get to the title screen on game over, but if Start is held at game over, it'll jump to title screen and right into gameplay rather than force two button presses (one to get to title screen and one to start play). Is it supposed to do that?
No. Sorry, maybe i wasn't clear enough. There are two parts to this method. The first part is to check if the button is being held. If that is true, THEN you check if it was not pressed last frame. That will deliver your onPress. It will only happen one time when you press and hold a button on the first frame it registers. The routine i wrote last post was supposed to happen FROM your current controller reading routine.

So, the whole thing (from scratch) would work like this:

Code: Select all

LDA buttons1
AND #START_BUTTON
BEQ startWasNotJustPressed
  ; Now you check whether it was not pressed last frame
  LDA prevButtons1
  AND #START_BUTTON
  BNE startWasNotJustPressed
    ; Ok NOW start was just pressed this frame. 
    ; This JSR will not happen again until the button is released and pressed again.
    JSR startWasJustPressed

startWasNotJustPressed:
; ...carry on
Is that more clear now?

EDIT: Wait, unless, are you using the RESET vector label to go back to the title screen? If you had this implemented correctly before, that will (probably) wipe your RAM, including your prevButtons byte, and cause a double press... If that is the case, well, don't jump to the RESET vector label after game over...
Image

Post Reply