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:
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:
By doing this, the code to read from our controller will get called on each frame.
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.