Joypad Problem

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

Louix94
Posts: 18
Joined: Mon May 03, 2010 11:26 am

Joypad Problem

Post by Louix94 »

Hey, sorry it's been a while, I haven't given up on my project, thanks to all those who helped me in the past! I haven't had any spare time to do anything really. Anyway, now I've got a little bit of time, I'd like to continue with this :)

I made a little menu last night and it works fine except for one thing: the joypad is too responsive! I would like to make it so the little arrow that points out the selection only moves down once per-press then ignores any other presses until the button is released. I've seen code that did that before but I can't find it anywhere!

Here's my current joypad code from my NMI routine:

Code: Select all

	.readJoypad:
		lda #$01
		sta $4016
		lda #$00
		sta $4016
		
	.readA:
		lda $4016
		and #%00000001
		beq .readA_done
		
		lda $0200
		adc #$08
		clc
		sta $0200
		
	.readA_done:
Thank you :)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

If you just check the current state of the button it's no surprise that you are going to detect it several times, seeing as the logic runs at 60 frames per second and most people will hold the button down longer than 1 frame.

What you want to do is only detect the buttons that were pressed during this frame, and ignore the ones that were already pressed. This means you have to check the previous frame's button status to make sure the button wasn't pressed before.

The quickest way to this is using bitwise operations, so I hope you're familiar with them. You just have to take the previous state and invert it (EOR #$FF), then AND the result with the new state.

So if the button was pressed during the previous frame, it's previous status is 1. Invert that and it becomes 0. If the button is pressed now, it's current state is 1. 1 AND 0 is 0, which means it was not pressed now, it was already pressed, so don't move the cursor.

But if the button wasn't pressed last frame, it's previous status is 0. 0 inverted becomes 1. 1 AND 1 equals 1, so it was pressed this frame, so the cursor should move.

EDIT: why do you have a CLC after the addition? You're supposed to clear the carry before adding, not after.
User avatar
MetalSlime
Posts: 186
Joined: Tue Aug 19, 2008 11:01 pm
Location: Japan

Re: Joypad Problem

Post by MetalSlime »

The first step would be to separate controller reading and input handling. You'll want to make a subroutine that reads the controller and stores the button states in a variable. See this tutorial to see how to do that.

Next you will want to distinguish between buttons that are pressed and buttons that are newly pressed, ie are pressed this frame but weren't pressed last frame. To do that you will need to compare this frame's button states with last frame's button states:

Code: Select all

read_controller:
    lda joypad1        ; take last frame's button states
    sta joypad1_old    ; and save them in a variable.

    ;... read the controller and store in joypad1

    lda joypad1_old    ; take last frame's button states
    eor #$FF           ; and flip the bits.  Now 1 = not pressed last frame, 0 = pressed last frame
    and joypad1        ; AND with this frame's buttons states.
                       ; We get a 1 only if a button was not pressed last frame, but is pressed this frame.
    sta joypad1_pressed  ; save the results

    rts
Then in your input handling code, check the joypad1_pressed variable instead of the joypad1 variable.
Wave
Posts: 110
Joined: Mon Nov 16, 2009 5:59 am

Post by Wave »

I do something like tokumaru says, I have 3 bytes per joypad:
-Pressed buttons (this frame are pressed)
-Mantained buttons (pressed last frame and this frame)
-Released buttons (pressed last frame, not pressed this frame)
Louix94
Posts: 18
Joined: Mon May 03, 2010 11:26 am

Post by Louix94 »

Got it, thanks again! :) Here's my new code for those interested:

Code: Select all

	.readA:
		lda joypad_new
		sta joypad_old
		
		lda $4016
		sta joypad_new

		and #%00000001
		beq .readA_done
		
		lda joypad_old
		eor #$FF
		and joypad_new
		beq .readA_done
		
		lda $0200
		clc
		adc #$08
		sta $0200
		
	.readA_done:
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I cringe to think what that code will look like once it handles all the buttons. :)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

The problem is that a lot of tutorials teach newbies to constantly interact with the hardware like that, instead of teaching them to use an abstraction layer.

The same goes for the sprites in this case. A lot of newbies use hardcoded sprites in the OAM page, and use the sprite data (coordinates) as object data, instead separating the model from the view, as is the professional way of doing it.
User avatar
Dwedit
Posts: 4470
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Here's the code I'm using for joystick input:

Code: Select all

JOY1      = $4016
JOY2      = $4017

ReadJOY1_8:
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	cpx JOY1
	ror a
	eor #$FF
	rts


Strobe:
	ldx #1
	stx JOY1
	lda JOY1
	dex
	stx JOY1
	and #$FC
	tax
	rts

ReadJoypad1:
	lda joy1
	sta joy1last
	
	jsr Strobe
	jsr ReadJOY1_8
	sta joy1
ReadJoy1Again:	
	jsr Strobe
	jsr ReadJOY1_8
	cmp joy1
	sta joy1
	bne ReadJoy1Again
	
	eor joy1last
	and joy1
	sta joy1Pressed
	lda joy1
	rts
I have the strobe and controller read parts separate because I also them in different ways for the 4-player joystick code.
This code doesn't consider the famicom expansion port joysticks.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Louix94
Posts: 18
Joined: Mon May 03, 2010 11:26 am

Post by Louix94 »

@blargg:
I just discovered what happens when I try to handle multiple buttons, it doesn't work too well!


@tokumaru:
I'm very used to writing code with the whole model-view concept, but I just can't bring myself to think like that when I write code in an assembly language!

EDIT: All fixed up now, will attempt to tidy up and post back here later, thank you all again :)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Louix94 wrote:I just discovered what happens when I try to handle multiple buttons, it doesn't work too well!
You should read the state of all buttons beforehand (thus interacting with the joypad only once) and buffer them in a byte, and then use that byte for checking any buttons you wish during the rest of the logic frame.
I'm very used to writing code with the whole model-view concept, but I just can't bring myself to think like that when I write code in an assembly language!
That's OK. If you already know about it you're on the right track. As long as you try to progressively abstract the hardware aspects in your code you'll be fine.

The sprites, for example, are just a way the NES offers to display objects, so the sprite data shouldn't be used for logic... There "professional" way would be to have a cursor object which would check for input and react accordingly, and finally draw itself by using an entry in the OAM, which is nothing more than a graphical representation of the cursor object.

It may seem pointless to make an apparently complex separation when you can just move the damn sprite, but that's the difference between a simple demo and a game. A simple demo can get away with doing things in the dirtiest way possible as long as the graphical results are as expected, but a game must have a solid framework underneath if it is supposed to get anywhere.

I know you are just learning, and whatever works is good for learning. What I wrote above is just a tip for the future. Keep that in mind.
Celius
Posts: 2159
Joined: Sun Jun 05, 2005 2:04 pm
Location: Minneapolis, Minnesota, United States
Contact:

Post by Celius »

Wave wrote:I do something like tokumaru says, I have 3 bytes per joypad:
-Pressed buttons (this frame are pressed)
-Mantained buttons (pressed last frame and this frame)
-Released buttons (pressed last frame, not pressed this frame)
That's interesting! I also have 3 bytes, but they are as follows:

-ControlCurrent (What is read as currently being pressed this frame)
-ControlPrevious (The status of ControlCurrent from last frame)
-ControlTrigger (Buttons newly pressed = 1, buttons not newly pressed = 0)

When I don't care if a button was newly pressed, and I just need to see if it was pressed, I just look at ControlCurrent. I guess if I need to see if a button is held, I just look at (ControlCurrent AND ControlPrevious). I guess a Maintained Buttons variable would be a good thing though... I don't think I use ControlPrevious for anything other than calculating the result of ControlTrigger.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Or feel free to use my working-on-hardware, audio-glitch-tolerant, permissively licensed controller reading subroutine, which can be found in pads.s of Concentration Room. It updates these variables for both players:
  • cur_keys: 1 if held
  • new_keys: 1 if newly pressed since the previous read; this corresponds to "joypad_pressed" or "joy1Pressed" or "ControlTrigger"
  • das_keys: 1 if newly pressed since the previous frame or if the key has been held for autorepeat (DAS stands for delayed auto-shift)
What's "audio-glitch-tolerant"? Memory accesses while playing sampled sound occasionally cause the CPU to miss a bit while reading the controller ports. Standard procedure if your game uses sampled sound is to read the controllers more than once and compare them to make sure that such a glitch hasn't occurred.
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I added a Controller Reading page to the Wiki.
Wave
Posts: 110
Joined: Mon Nov 16, 2009 5:59 am

Post by Wave »

Celius wrote:
Wave wrote:I do something like tokumaru says, I have 3 bytes per joypad:
-Pressed buttons (this frame are pressed)
-Mantained buttons (pressed last frame and this frame)
-Released buttons (pressed last frame, not pressed this frame)
That's interesting! I also have 3 bytes, but they are as follows:

-ControlCurrent (What is read as currently being pressed this frame)
-ControlPrevious (The status of ControlCurrent from last frame)
-ControlTrigger (Buttons newly pressed = 1, buttons not newly pressed = 0)

When I don't care if a button was newly pressed, and I just need to see if it was pressed, I just look at ControlCurrent. I guess if I need to see if a button is held, I just look at (ControlCurrent AND ControlPrevious). I guess a Maintained Buttons variable would be a good thing though... I don't think I use ControlPrevious for anything other than calculating the result of ControlTrigger.
This is my function to do this right now:

Code: Select all

inline inp_updatePlayer(player) {
    lda pads[player].pressed
    ora pads[player].mantained
    sta pads[player].released
	tax
    lda tmpPort
    eor #0xFF
    and pads[player].released
    sta pads[player].released
    txa
    and tmpPort
    sta pads[player].mantained
    txa
    eor #0xFF
    and tmpPort
    sta pads[player].pressed
}
tmpPort contains the pad readed this frame
Louix94
Posts: 18
Joined: Mon May 03, 2010 11:26 am

Post by Louix94 »

Okay I've re-written my input code to make it a bit more readable, made better use of constants and variables and added better structure to it (as much as I could). Input basically works the same way as it did before, thank you for your help :)

EDIT: I just realised I think I could do all the button handling in a loop, I'll try that later on :)

Code: Select all

.GetButtons:
		.Strobe:
			lda #$01
			sta .joypad1Port
			lda #$00
			sta .joypad1Port

		.ReadInput:
			; Info:		Read # |    1      2      3      4      5      6      7      8
			;			-------+---------------------------------------------------------
			;			Button |    A      B   SELECT   START   UP    DOWN   LEFT  RIGHT
			
			; Loop until we reach the start button
			
			ldx #$00
			.ReadInput_readLoop:
				lda .joypad1Port
				inx 
				cpx #$03
				bne .ReadInput_readLoop
			
			.ReadInput_StartButton:
				lda JPNewValue
				sta JPOldValue
				
				lda $4016
				sta JPNewValue
				
				and #$01
				beq .ReadInput_UpButton
				
				lda JPOldValue
				eor #$FF
				and JPNewValue
				beq .ReadInput_Done
				
				
				jsr .ChooseCurrentOption
				
				rts
				
			.ReadInput_UpButton:	
				lda $4016
				sta JPNewValue
				
				and #$01
				beq .ReadInput_DownButton
				
				lda JPOldValue
				eor #$FF
				and JPNewValue
				beq .ReadInput_Done
				
				ldx Counter1
				cpx #$00
				beq .ReadInput_Done
				
				lda #.selectionArrow
				sta .selectionArrowSprite
				
				jsr .SelectionPreviousOption
				
				rts
				
			.ReadInput_DownButton:
				lda $4016
				sta JPNewValue
				
				and #$01
				beq .ReadInput_Done
				
				lda JPOldValue
				eor #$FF
				and JPNewValue
				beq .ReadInput_Done
				
				ldx Counter1
				cpx #.totalMenuOptions-1
				beq .ReadInput_Done
				
				lda #.selectionArrow
				sta .selectionArrowSprite
				
				jsr .SelectionNextOption
				
				rts
				
				
			.ReadInput_Done:
				rts
				
; Variables (listed in a different file):
JPNewValue .rs 1
JPOldValue .rs 1

Counter1 .rs 1

; Constants
.totalMenuOptions = $02

.selectionArrow = $02
.selectionArrowSprite = $0202

[/code]
Post Reply