Welcome back, Adventurer! Last time, we learned more about direct memory access and moving sprites.
This time, we’ll read the joypad and move the sprites accordingly. So this time we will
- read and process input from the joypad
- make the sprites move according to input
- implement some simple collision detection to make the sprites stay on screen
Without further ado, let’s get started!
Preparing The Demo
For this demo, we’re going to reuse a lot of the code from the bouncing sprite demo from last time.
Copy all the code, sprite, and color data from that demo as we’ll be reusing them here. You can find all source code and other files in the SNES Assembly Adventure repository on Github.
Reading The Joypad
As always, I’ll show you the code first, then we’ll discuss it line by line:
Boy, has our little demo grown. Packing 500+ lines of code into a single file is usually not a good idea. We’ll alleviate this [next time][2] when we talk about how to modularize our code and make it easier to reuse and navigate. For now, let’s look at the sections that changed.
Lines 1 through 204: There’s little to no change here. We added a handful of new symbols, HVBJOY
, STDCNTRL1L
/H
, JOYPAD
, JOYTRIGGER1
, and JOYHELD1
are new symbols/addresses we’ll use to read the joypad. The *_BUTTON
constants are bit masks we’ll use to detect which button was pressed. Next, we initialize all data and place our sprites at the center of the screen. Lines 199 and 200 are important. Polling the joypads can be turned off if necessary, we must make sure that it is turned on. Otherwise, the SNES won’t poll the controllers/joypads for new input.
With initialization out of the way, let’s look at the updated main game loop.
Lines 214 through 230: Here’s where the magic happens. The first thing we need to do is to make sure that the SNES has finished polling the joypad(s) for input. This is what lines 215 through 217 do. The SNES polls the joypads once each frame (at the beginning of V-Blank) and it takes a few cycles to finish (as the communication between console and joypad is serial, similar to the NES). Once the console has polled the joypads, we switch the size of register A to 16 bits. We read the input just polled from STDCNTRL1
. We then use the Y register to load the input we polled last frame from JOYPAD1
. We store the newly polled input in A in JOYPAD1
(so it becomes the last frame’s input on the next game loop iteration/V-Blank).
So after line 222, we have this frame’s input in A, and the last frame’s input in Y, and stored this frame’s input in WRAM at address JOYPAD1
. We need this to be able to differentiate which button has been pressed since the last frame and which has been held (if any). We do this by transferring the last frame’s input from Y to A and exclusive OR
‘ing with the current frame’s input. After that, A only holds the buttons that were pressed during this frame, but not last. Aka, these are the buttons the player has just pressed. We stored this information in JOYTRIGGER1
.
We repeat this process in lines 228 through 230 to find all the buttons that have been held since the last frame and store that information in JOYHELD1
.
Now we know which buttons the players have either newly pressed or held since the last frame. We are now ready to check whether the dpad was used and move the sprites accordingly. We’ll check the dpad in the order of up, down, left, right.
Lines 234 through 258: Here’s where even more of the magic happens. First, we reset the accumulator to zero. We want the sprites to move up whenever we pressed or held the up button on the dpad. To do this, we OR
in all triggered and held buttons. Then we use a constant bit mask to check whether the bit of the up button set (aka, was pressed or held). If it wasn’t pressed, we just jump ahead and check the next button.
If the up button was pressed or held, we next need to move the four sprites up. This code shouldn’t hold any surprises by now. We use Y as the loop counter and X as the offset. We load the current vertical position from the OAM mirror and subtract the vertical movement speed. Then we check whether the sprites would move the upper boundary. If it does, we jump to CorrectVerticalPositionDown
to move all sprites to the upper boundary.
Lines 260 through 290: This is almost completely similar to the previous section. Only one part is slightly different: We check whether the sprites could cross the lower boundary before we move. This is necessary because the lower two sprites could cross the boundary before the upper two. If that is the case, we jump to CorrectVerticalPositionUp
.
Lines 292 through 310: These sections of code simply set the position of the sprites of the upper and lower boundary respectively. We use the constants defined at the start of the code for this.
Lines 312 through 386: Again, very similar to what we did for the up and down button, expect that we change or correct the horizontal position of the sprites.
And that’s it for handling input. The rest of the code is an exact copy from the bounding sprite example.
Challenges
Here are a few ideas (and a few hints) for you to experiment with and test your understanding of the code:
- Can you change the speed with which the sprites move using the A and B buttons? You’ll need to add bit masks for the A and B buttons.
- Accelerate the sprites if a dpad button is held long enough? A minimum and maximum speed and a frame counter may prove useful here.
- Deaccelerate the sprites if the dpad button is let go? A frame counter can also count down.
Conclusion
To correctly handle input on a SNES, we poll the joypad (the mouse is handled differently; that may become its own article in the future) and check with the help of logical operators whether a certain button was pressed or not. This hasn’t changed a lot 30+ years later, as you will still encounter code along the lines of if( Gamepad.XButton.pressed )
in modern game engines. The logic we employ here is very similar.
As mentioned in the beginning, our code example has grown quite a bit. If you feel that there’s a lot of code repetition here, you’re correct. The code here is purposefully kept very verbose to make it easy to follow. But that makes coding harder than it needs to be.
So next time, we’ll be breaking this code into several files to make it easier to handle and reuse for future projects. Additionally, we’ll upgrade the makefile found in the SNES Assembly Adventure repository to a CMake project for more convenient development.
Thank you for reading and see you next time!
Links and References
- This great post on the SFC Development Wiki covers the same ground as this article.
- nesdoug also has a great article on controller handling as well.
- Find a lot of detailed information on automatic and manual input poll in nocash’s (author of several excellent emulators!) great SNES reference. Bookmark this site.
- All sources and files for this series can be found in the SNES Assembly Adventure repository.