In almost any NES game we can imagine, we used a controller to move one of the sprites around on the screen. Let’s tap into the directional buttons to move our spaceship around.
If you would like to follow along in my git repository, download or clone the following repository:
https://github.com/jonmoody/nes-tutorial
Then to see the code for this tutorial specifically, checkout the branch tutorial-5:
git checkout -b tutorial-5 remotes/origin/tutorial-5
Reading from the Controller
Hooking into the controller is relatively simple. There are two ports that we can use to access the controllers. Port $4016 will be used for player 1, and $4017 will be used for player 2. Let’s start writing a method to read the controller input for player 1:
ReadPlayerOneControls:
LDA #$01
STA $4016
LDA #$00
STA $4016
The code above is used to latch buttons for both controllers. Even though we are writing the values $01 and $00 to the port for controller 1, this initial bit of setup code will prepare both controllers to be read. Once we do this, we are ready to read each button for controller 1:
LDA $4016 ; Player 1 - A
LDA $4016 ; Player 1 - B
LDA $4016 ; Player 1 - Select
LDA $4016 ; Player 1 - Start
LDA $4016 ; Player 1 - Up
LDA $4016 ; Player 1 - Down
LDA $4016 ; Player 1 - Left
LDA $4016 ; Player 1 - Right
You will notice something odd about the code above. The same line of code is repeated eight times. However, the comment next to each line tells us that each load of that memory address is reading a different button. It’s important to know that the buttons are read in this fixed sequence (A, B, Select, Start, Up, Down, Left, Right). This means that if we want to do something when the player presses Start, we will need to load this memory address four times, regardless of whether or not we care about any of the previous buttons in the sequence.
Checking Button Input
That code alone won’t really do anything. We need to actually check the value that came back from the controller to see if the button has been pressed or not. Let’s check the Up button:
ReadUp:
LDA $4016 ; Player 1 - Up
AND #%00000001
BEQ EndReadUp
; Do stuff here if the button is pressed
EndReadUp:
Once we read the Up button, we will perform an AND operation on the value that came back. We only care about the first bit, so by placing a 1 in the first bit of our AND operation, the result will be 1 if the button is pressed, otherwise the value is 0. Then, we will use the BEQ (branch if equal) operation to decide if we need to execute the following code or skip over it. If we don’t use a CMP (compare) operation before using BEQ, it will branch if the value is equal to zero.
Moving Sprites with the Controller
Now we want to actually move the spaceship up when we press the Up button. When we loaded our sprites starting at memory address $0300, remember that each sprite has 4 bytes of data. The first byte of that data is the vertical position. We will have to modify this byte for all six sprites that make up our spaceship. Dealing with memory addresses can quickly become out of control, so let’s create some variables for the vertical positions of all our spaceship sprites. Add these to the top of the file after where we added our background pointers:
shipTile1Y = $0300
shipTile2Y = $0304
shipTile3Y = $0308
shipTile4Y = $030C
shipTile5Y = $0310
shipTile6Y = $0314
Now, back in the code where we read the Up button, if the button is pressed we want to simultaneously move all sprites up one pixel:
ReadUp:
LDA $4016 ; Player 1 - Up
AND #%00000001
BEQ EndReadUp
LDA shipTile1Y
SEC
SBC #$01
STA shipTile1Y
STA shipTile2Y
STA shipTile3Y
LDA shipTile4Y
SEC
SBC #$01
STA shipTile4Y
STA shipTile5Y
STA shipTile6Y
EndReadUp:
In the code above, we are reading in the current vertical position of the first tile of the spaceship. Then, we add an SEC (set carry) operation to set the carry flag to make borrowing possible for subtraction. After that, we use the SBC (subtract with carry) operation to subtract 1 from the current vertical position of the sprite. Once we have this new value, we store that in the memory locations of the first three sprites (since they should all have the same vertical position). This will move the first row of sprites in our spaceship up by one pixel. The next chunk of code does the same thing, except for the second row of sprites in the spaceship.
Now, let’s add the code necessary to be able to move the spaceship down:
ReadDown:
LDA $4016 ; Player 1 - Down
AND #%00000001
BEQ EndReadDown
LDA shipTile1Y
CLC
ADC #$01
STA shipTile1Y
STA shipTile2Y
STA shipTile3Y
LDA shipTile4Y
CLC
ADC #$01
STA shipTile4Y
STA shipTile5Y
STA shipTile6Y
EndReadDown:
The code for the Down button is basically the same as the Up code. The only difference here is that we use the CLC (clear carry) operation instead of the SEC (set carry) operation, since we don’t need it set for addition. Then we use the ADC (add with carry) operation to add 1 to the current vertical position of the sprites.
Moving sprites horizontally works just about the same as moving them vertically, except there is a separate byte for each sprite’s horizontal position. Let’s add more variables for the horizontal positions:
shipTile1X = $0303
shipTile2X = $0307
shipTile3X = $030B
shipTile4X = $030F
shipTile5X = $0313
shipTile6X = $0317
The code for moving the spaceship left has a similar structure:
ReadLeft:
LDA $4016 ; Player 1 - Left
AND #%00000001
BEQ EndReadLeft
LDA shipTile1X
SEC
SBC #$01
STA shipTile1X
STA shipTile4X
LDA shipTile2X
SEC
SBC #$01
STA shipTile2X
STA shipTile5X
LDA shipTile3X
SEC
SBC #$01
STA shipTile3X
STA shipTile6X
EndReadLeft:
There are three chunks of code in there instead of two because there are three columns of sprites in our spaceship. Each chunk of code will move the two sprites in the column to the left by one pixel.
For completeness, let’s add the code for reading the Right button and wrap up this method with an RTS (return from subroutine) operation:
ReadRight:
LDA $4016 ; Player 1 - Right
AND #%00000001
BEQ EndReadRight
LDA shipTile1X
CLC
ADC #$01
STA shipTile1X
STA shipTile4X
LDA shipTile2X
CLC
ADC #$01
STA shipTile2X
STA shipTile5X
LDA shipTile3X
CLC
ADC #$01
STA shipTile3X
STA shipTile6X
EndReadRight:
RTS
Cool! So now all we have to do is call this method from the NMI:
JSR ReadPlayerOneControls
By doing this, the code to read from our controller will get called on each frame.
Player 2
If the game we’re creating happens to be a two-player game, all the stuff we did above is the same except we use port $4017 for player 2 instead of $4016.
The Spaceship Moves!
Once we assemble our game and launch it in the emulator, we should be able to use the directional buttons to move the spaceship around on the screen! The emulator will map buttons on your keyboard to the controller buttons. Additionally, you can plug in a usb controller and map the buttons on an actual controller.
One thing you may notice is that the ship can travel off the sides of the screen and appear on the opposite side. The next post will go over some basic collision detection so we can resolve this issue.