From a development perspective, the title screen of the game is not that important. From a marketing perspective, it is vital as it is the first impression of the game that a player will receive. My personal opinion is somewhere between the two points of view. You want a title screen to be attractive but you do not want to spend too much time on creating the title screen. Some decent artwork and perhaps some simple animation will probably suffice for smaller projects. For larger projects with big budgets, some type of CGI may be what the project backer insists on.
For trivia, out title screen simply shows a graphical title while waiting for a button to be pressed. More elaborate games may have a menu of options. Different modes of play, options, credits, and instructions are possibilities. Handling a menu would be done similarly to how we handled answer selection in the main game. The different options simply jumping to a separate routine for handling the particular screen.
The only hard part of the title screen is displaying the graphical title. Once you realize that the graphics are just a bunch of tiles, it is not much of a leap before you realize that you can simply print graphics. The only real restrictions being that you are not able to use character 0 as that is the character that is reserved for indicating the end of a string, and that the length of the string be under 256 characters. In our case, most of the title image is empty space so we conserve ROM space by breaking the string into individual lines. It would certainly be possible to take advantage of the consecutive nature of screen memory to have multiple lines of graphical information in a string. Still, here is the code used for printing the N.E.S. part of the title.
CallClearScreen ' ',0,0
CallPrintStringAt titleNES1, 5,2,0
CallPrintStringAt titleNES2, 5,3,0
CallPrintStringAt titleNES3, 5,4,0
CallPrintStringAt titleNES4, 5,5,0
CallPrintStringAt titleNES5, 5,6,0
And here is the data for the print statements.
titleNES1 .db 30,10,32,32,32,30,32,32,32,30,30,30,30,32,32,32, 9,30,30,10,0
titleNES2 .db 30,11,10,32,32,30,32,32,32,30,32,32,32,32,32,32,11,10,32,11,0
titleNES3 .db 30,32,11,10,32,30,32,32,32,30,30,30,32,32,32,32,32,11,10,0
titleNES4 .db 30,32,32,11,10,30,32,32,32,30,32,32,32,32,32,32,10,32,11,10,0
titleNES5 .db 30,32,32,32,11,30,32,30,32,30,30,30,30,32,30,32,11,30,30, 8,32,30,0
The rest of the title screen is pretty much more of the same, though the press start and copyright messages are ASCII strings. Once the display is generated, we simply use the WaitForButtonPress and WaitForButtonPressEnd functions to wait to start the game.
The results screen is rendered pretty much the same way though has the interesting problem of having to display the score. Thankfully the score is a single digit number. This means that to display it, we only need to add 48 to the number to get the proper ASCII value for the number. For numbers greater than 9 we would need multiple digits which adds the problem of no BCD or division functions for the NES. We will be covering software multiplication and division shortly, but it is a complex topic so it is nice that we didn't need to get into it before creating this game. Here is the code for displaying the score.
LDA SCORE
CLC
ADC #48
; the cursor is already at the appropriate position in the PPU so
; simply need to send the character index to the PPU
STA $2007
And that is all there is to trivia. Of course, having randomly selected questions and mixing up the answers so they were not always in the same order would be nice. This requires quite a bit of work as we need to delve into the topics of entropy, pseudo-random numbers, and software multiplication. Software division will be covered as well as we will need it for displaying multi-digit numbers which is something the target RPG will need for sure. There is a lot to cover before we can do the full version of trivia, though I may have a couple of other simple games before we get to trivia 2. But…that will not be done on this blog as the Blazing Games Development blog is being shut down. More on that, and where future home-brew articles will appear will be covered next.
Showing posts with label NES. Show all posts
Showing posts with label NES. Show all posts
Friday, August 22, 2014
Friday, August 15, 2014
NES Trivia Gameplay
As covered in the NES Trivia Screen article, the code for this game is at http://blazinggames.com/books/NES/NESTrivia/ . We are only covering the most relevant snippets of code. If you wish to look at the main game loop in its entirety search the code for the MainGameLoop label.
With the Questions displayed we now need to monitor the user input. To allow for the player to hold the up or down buttons down there is a delay between reads of the joystick that is initiated when the player performs an action. This works well for holding down the directions but rapidly clicking on a button doesn't work as well as it should as some of the clicks can happen between the delay. This will be fixed in the second version of trivia.
DEC INPUT_DELAY
BNE MainGameLoop_adjustScroll
INC INPUT_DELAY ; Make sure if no input we update next frame
JSR ReadController1
TAY
Moving up or down is simply a matter of adjusting the CURRENT_ANSWER variable. Because there are only 4 possible answers (0..3) we can simplify the range checking logic by simply using the AND operation to force the value of the adjusted CURRENT_ANSWER to within the acceptable range. This technique saves us from having to have a conditional branch which, at least in my opinion, makes for more readable code. While people who are not familiar with boolean operations may disagree with this, if you are programming in assembly language a lot you will quickly get use to working with boolean operations.
MainLoop_checkButtonUp:
TYA
AND #BUTTON_UP
BEQ MainLoop_checkButtonA
LDX CURRENT_ANSWER
DEX
TXA
AND #3
STA CURRENT_ANSWER
LDA #DELAY_TIME
STA INPUT_DELAY
JMP MainGameLoop_adjustScroll
The heart of the trivia game logic comes when the A button is pressed. The handling code waits for the button to be released and then calls DisplayQuestionResults. This function potentially prints a lot to the screen so we temporarily turn off the display so we can write to the PPU without any issues. It then sets up the print location before checking to see if the answer the player selected is the correct answer. The appropriate result string is printed, and if the answer is correct the score is increased. Finally, this function prints the explanation before returning to the main game loop.
DisplayQuestionResults:
LDA #$00
STA $2001 ; disable rendering
; set up print coordinates
TAX ; accumulator already 0
LDY ANSWER_ROWS+4
JSR SetScreenXY
; See if selected answer is correct
LDY #14 ; Offset to correct answer
LDA [QUESTION_INFO_PTR],Y
CMP CURRENT_ANSWER
BEQ DisplayQuestionResults_correct
CallPrintString incorrectText
JMP DisplayQuestionResults_explain
DisplayQuestionResults_correct:
INC SCORE
CallPrintString correctText
DisplayQuestionResults_explain:
; set up coordinates for explanation text
LDY ANSWER_ROWS+4
INY
INY
LDA #0
TAX
JSR SetScreenXY
; print explanation
LDY #12
JSR PrintQuestionInfoString
LDA #011110 ; enable background + sprites
STA $2001
RTS
When we return to the main loop we wait for the press and then the release of a button so that the player has time to acknowledge the explanation. After this, we need to know if the question is the last question or not. If it is the last question, we jump to the summary screen loop. If it is not the last question, the pointer to the current question info structure is incremented by 16 (the length of the structure) and we call DisplayQuestion to get the next question displayed.
LDX CURRENT_QUESTION
INX
STX CURRENT_QUESTION
CPX #9
BEQ SummaryScreen
LDA QUESTION_INFO_PTR
CLC
ADC #16
BCC MainLoop_showNextQuestion
INC QUESTION_INFO_PTR+1
MainLoop_showNextQuestion:
STA QUESTION_INFO_PTR
JSR DisplayQuestion
We are now finished with the game's core logic. Essentially we have a game. The only thing missing from having a complete game at this point is the title and summary screens. Normally, these are not things that I would bother to cover as they are very straightforward, but as this is our first NES game, it is probably a good idea to cover them. This is what we will be doing next.
With the Questions displayed we now need to monitor the user input. To allow for the player to hold the up or down buttons down there is a delay between reads of the joystick that is initiated when the player performs an action. This works well for holding down the directions but rapidly clicking on a button doesn't work as well as it should as some of the clicks can happen between the delay. This will be fixed in the second version of trivia.
DEC INPUT_DELAY
BNE MainGameLoop_adjustScroll
INC INPUT_DELAY ; Make sure if no input we update next frame
JSR ReadController1
TAY
Moving up or down is simply a matter of adjusting the CURRENT_ANSWER variable. Because there are only 4 possible answers (0..3) we can simplify the range checking logic by simply using the AND operation to force the value of the adjusted CURRENT_ANSWER to within the acceptable range. This technique saves us from having to have a conditional branch which, at least in my opinion, makes for more readable code. While people who are not familiar with boolean operations may disagree with this, if you are programming in assembly language a lot you will quickly get use to working with boolean operations.
MainLoop_checkButtonUp:
TYA
AND #BUTTON_UP
BEQ MainLoop_checkButtonA
LDX CURRENT_ANSWER
DEX
TXA
AND #3
STA CURRENT_ANSWER
LDA #DELAY_TIME
STA INPUT_DELAY
JMP MainGameLoop_adjustScroll
The heart of the trivia game logic comes when the A button is pressed. The handling code waits for the button to be released and then calls DisplayQuestionResults. This function potentially prints a lot to the screen so we temporarily turn off the display so we can write to the PPU without any issues. It then sets up the print location before checking to see if the answer the player selected is the correct answer. The appropriate result string is printed, and if the answer is correct the score is increased. Finally, this function prints the explanation before returning to the main game loop.
DisplayQuestionResults:
LDA #$00
STA $2001 ; disable rendering
; set up print coordinates
TAX ; accumulator already 0
LDY ANSWER_ROWS+4
JSR SetScreenXY
; See if selected answer is correct
LDY #14 ; Offset to correct answer
LDA [QUESTION_INFO_PTR],Y
CMP CURRENT_ANSWER
BEQ DisplayQuestionResults_correct
CallPrintString incorrectText
JMP DisplayQuestionResults_explain
DisplayQuestionResults_correct:
INC SCORE
CallPrintString correctText
DisplayQuestionResults_explain:
; set up coordinates for explanation text
LDY ANSWER_ROWS+4
INY
INY
LDA #0
TAX
JSR SetScreenXY
; print explanation
LDY #12
JSR PrintQuestionInfoString
LDA #011110 ; enable background + sprites
STA $2001
RTS
When we return to the main loop we wait for the press and then the release of a button so that the player has time to acknowledge the explanation. After this, we need to know if the question is the last question or not. If it is the last question, we jump to the summary screen loop. If it is not the last question, the pointer to the current question info structure is incremented by 16 (the length of the structure) and we call DisplayQuestion to get the next question displayed.
LDX CURRENT_QUESTION
INX
STX CURRENT_QUESTION
CPX #9
BEQ SummaryScreen
LDA QUESTION_INFO_PTR
CLC
ADC #16
BCC MainLoop_showNextQuestion
INC QUESTION_INFO_PTR+1
MainLoop_showNextQuestion:
STA QUESTION_INFO_PTR
JSR DisplayQuestion
We are now finished with the game's core logic. Essentially we have a game. The only thing missing from having a complete game at this point is the title and summary screens. Normally, these are not things that I would bother to cover as they are very straightforward, but as this is our first NES game, it is probably a good idea to cover them. This is what we will be doing next.
Friday, August 8, 2014
NES Trivia Screen
As usual I am only going to cover the snippets of code I consider most relevant for the discussion. For those of you who want to see all the code, it is located at @link to source code here@.
The core of the game is fairly simple. We start by clearing the screen and displaying the label for the current question. As the label always takes the form of “Question ???” we save a bit of memory by having the label string only create the ??? part of the string generating the full string on the fly. The label is underlined. This is done by taking advantage of the fact that the PrintString function returns the length of the printed string in the Y register. Notice that the use of macros makes the code quite a bit more human-readable.
CallClearScreen ' ',0,0
CallStrNCpy baseLabel, SCRATCH_PAGE, 0
LDY #0
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR
INY
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR+1
JSR AppendString
; once created we print it
CallSetScreenXY 2,1,0
CallPrintString SCRATCH_PAGE
; underline Title
TYA
PHA ; save length of string to underline
CallSetScreenXY 2,2,0
PLA ; retrieve length of label string
TAX ; transfer to appropriate parameter for call
LDA #'='
JSR PrintRepeatedChar
Printing the question, answers, and later the explanation is all pretty much the same code but with different pointers being used, this is ideal code for a function. The desired string to be printed is part of the question info structure. We set Y to the offset within this structure to the pointer for the string we want printed. In case you are wondering, the term for a pointer to a pointer is called a handle. We copy this pointer to the zero-page SOURCE_PTR variable then call the PrintString function.
PrintQuestionInfoString:
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR
INY
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR+1
JSR PrintString
Now, as we did for underlining, we take advantage of the returned length value to determine how many rows were used for printing the string. I should point out that this is not always correct as the answers are indented by a couple of characters. This generally is not a problem but is something that has to be kept in mind when creating the data for the game. Divide the length printed by 32 to determine how many lines were printed. It is important to remember that the remainder from the divide is dropped. This means we need to add 1 to the result. As we are adding a blank line between rows this means we need to add 2 to get the appropriate next row.
TYA ; divide printed length by 32
LSR A
LSR A
LSR A
LSR A
LSR A
CLC ; add it to current printed row so we know what line we are on
ADC CURRENT_ROW
CLC ; add 2 so we know appropriate row for next print
ADC #2
STA CURRENT_ROW
RTS
While tracking the row for printing is important, we also need this information for handling the player choice. After calling the above function to print the question, we store the returned row number which will be the location of the first answer. We then call the function again for each of the answers storing the rows in subsequent memory locations. This is the equivalent of creating an array of rows. You may have noticed that there is an extra row stored. This will be used to display the results and explanation.
The players choice is represented by a right arrow sprite. The column the sprite appears on is set to 4 when the sprite is set up and never changes. The vertical placement of this sprite is based on the current choice that is selected which is stored in the CURRENT_ANSWER variable. The value in this variable is used to index the rows table we stored the row in above to determine the Y coordinate of the sprite.
LDY CURRENT_ANSWER
LDA ANSWER_ROWS,Y
ASL A
ASL A
ASL A
STA SPRITE_PAGE
At this point we have the question displayed with an arrow to the currently selected answer. Getting player input and determining if the selected question is our next task.
The core of the game is fairly simple. We start by clearing the screen and displaying the label for the current question. As the label always takes the form of “Question ???” we save a bit of memory by having the label string only create the ??? part of the string generating the full string on the fly. The label is underlined. This is done by taking advantage of the fact that the PrintString function returns the length of the printed string in the Y register. Notice that the use of macros makes the code quite a bit more human-readable.
CallClearScreen ' ',0,0
CallStrNCpy baseLabel, SCRATCH_PAGE, 0
LDY #0
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR
INY
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR+1
JSR AppendString
; once created we print it
CallSetScreenXY 2,1,0
CallPrintString SCRATCH_PAGE
; underline Title
TYA
PHA ; save length of string to underline
CallSetScreenXY 2,2,0
PLA ; retrieve length of label string
TAX ; transfer to appropriate parameter for call
LDA #'='
JSR PrintRepeatedChar
Printing the question, answers, and later the explanation is all pretty much the same code but with different pointers being used, this is ideal code for a function. The desired string to be printed is part of the question info structure. We set Y to the offset within this structure to the pointer for the string we want printed. In case you are wondering, the term for a pointer to a pointer is called a handle. We copy this pointer to the zero-page SOURCE_PTR variable then call the PrintString function.
PrintQuestionInfoString:
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR
INY
LDA [QUESTION_INFO_PTR],Y
STA SOURCE_PTR+1
JSR PrintString
Now, as we did for underlining, we take advantage of the returned length value to determine how many rows were used for printing the string. I should point out that this is not always correct as the answers are indented by a couple of characters. This generally is not a problem but is something that has to be kept in mind when creating the data for the game. Divide the length printed by 32 to determine how many lines were printed. It is important to remember that the remainder from the divide is dropped. This means we need to add 1 to the result. As we are adding a blank line between rows this means we need to add 2 to get the appropriate next row.
TYA ; divide printed length by 32
LSR A
LSR A
LSR A
LSR A
LSR A
CLC ; add it to current printed row so we know what line we are on
ADC CURRENT_ROW
CLC ; add 2 so we know appropriate row for next print
ADC #2
STA CURRENT_ROW
RTS
While tracking the row for printing is important, we also need this information for handling the player choice. After calling the above function to print the question, we store the returned row number which will be the location of the first answer. We then call the function again for each of the answers storing the rows in subsequent memory locations. This is the equivalent of creating an array of rows. You may have noticed that there is an extra row stored. This will be used to display the results and explanation.
The players choice is represented by a right arrow sprite. The column the sprite appears on is set to 4 when the sprite is set up and never changes. The vertical placement of this sprite is based on the current choice that is selected which is stored in the CURRENT_ANSWER variable. The value in this variable is used to index the rows table we stored the row in above to determine the Y coordinate of the sprite.
LDY CURRENT_ANSWER
LDA ANSWER_ROWS,Y
ASL A
ASL A
ASL A
STA SPRITE_PAGE
At this point we have the question displayed with an arrow to the currently selected answer. Getting player input and determining if the selected question is our next task.
Friday, July 25, 2014
Designing NES Trivia
As I have said before, and have heard other people say as well, trivia is a trivial game to create. It consists of a set number of rounds. Each round has a question presented to the player with the player choosing the answer from one of a set number of answers. Once the player selects his or her choice, they are told if they are correct and an explanation of the correct answer is given.
This can be represented by a fairly simple data structure:
0..1 Pointer to label for the question string
2..3 Pointer to question string
4..5 Pointer to first (0) answer
6..7 Pointer to second (1) answer
8..9 Pointer to third (2) answer
10..11 Pointer to fourth (3) answer
12..13 Pointer to explanation
14..15 Correct answer number
The correct answer number only needs a single byte but uses a pair of bytes for the simple reason that this makes the structure 16 bytes. Powers of 2 are always nice to have as that allows you to take advantage of the bit-shifting operations instead of using more costly multiplication. When you consider that the 6502 does not have multiplication instructions, meaning that software multiplication needs to be done, this is a very important consideration. The actual implementation of a question looks like this:
questionInfo:
.dw q1_label, q1_question
.dw q1_answer0, q1_answer1, q1_answer2, q1_answer3
.dw q1_explanation, 2
; additional question info structures follow this...
; then we have the actual string data
q1_label: .db "One",0
q1_question: .db "The original Japanese version "
.db "of the NES was called?",0
q1_answer0: .db "Nintendo Entertainment System",0
q1_answer1: .db "Adam",0
q1_answer2: .db "Nintendo Family Computer",0
q1_answer3: .db "Nintendo Advanced Video System",0
q1_explanation: .db "The Nintendo Family Computer "
.db "became better known by the short"
.db "name: Famicom.",0
You will notice that strings are specially formatted to take advantage of the fact that the lines are 32 characters long. Extra space characters are added to fill out lines when necessary. While it would certainly be possible to write code to do the formatting for us, for this first version it is not necessary. The decision to write formatting code will depend on how much storage space is wasted by extra characters for formatting. When we get to the point where code to format text will take significantly less memory than will be saved by embedding formatting into the strings then it will be worth writing the code.
The game itself consists of three separate screens. The title screen and results screen are simple text displays with nothing special about them. The main game screen is fairly simple as well but has three separate phases of operation. The first phase displays the question and answers. The second phase has the user moving a pointer between the four different answers and determining which answer the user has selected. The final phase is determining the results of the user selection and displaying them as well as the explanation. Development starts with getting the game screen to work. Once it is working the game can be completed by adding the title and results screens. That is what we will be focusing on after next month's postmortem.
This can be represented by a fairly simple data structure:
0..1 Pointer to label for the question string
2..3 Pointer to question string
4..5 Pointer to first (0) answer
6..7 Pointer to second (1) answer
8..9 Pointer to third (2) answer
10..11 Pointer to fourth (3) answer
12..13 Pointer to explanation
14..15 Correct answer number
questionInfo:
.dw q1_label, q1_question
.dw q1_answer0, q1_answer1, q1_answer2, q1_answer3
.dw q1_explanation, 2
; additional question info structures follow this...
; then we have the actual string data
q1_label: .db "One",0
q1_question: .db "The original Japanese version "
.db "of the NES was called?",0
q1_answer0: .db "Nintendo Entertainment System",0
q1_answer1: .db "Adam",0
q1_answer2: .db "Nintendo Family Computer",0
q1_answer3: .db "Nintendo Advanced Video System",0
q1_explanation: .db "The Nintendo Family Computer "
.db "became better known by the short"
.db "name: Famicom.",0
The game itself consists of three separate screens. The title screen and results screen are simple text displays with nothing special about them. The main game screen is fairly simple as well but has three separate phases of operation. The first phase displays the question and answers. The second phase has the user moving a pointer between the four different answers and determining which answer the user has selected. The final phase is determining the results of the user selection and displaying them as well as the explanation. Development starts with getting the game screen to work. Once it is working the game can be completed by adding the title and results screens. That is what we will be focusing on after next month's postmortem.
Friday, June 27, 2014
NES Controllers
The NES has serial ports for the controllers. The advantage of this is that it is possible to have different controllers and other peripherals. The downside is that you need to read the input one bit at a time. The serial ports are located at memory addresses $4016 and $4017. The order that the bits are read is dependent on the controller that is plugged in, but as most games use the normal NES controller, the bit order is A, B, Select, Start, Up, Down, Left, Right.
The controllers need to be told to send their state to the serial port. This is done by a method known as strobing a port. Essentially this is simply writing a 1 to the port you wish to read followed by a 0 to the port you want to read. You then read the port 8 times for a standard controller, possibly more for other controllers. The low order bit (1) is used to tell you the state of the button being read while the next bit (2) indicates if a controller is attached. Other bits may also have significance, but I have not been able to find any information on this.
It is possible to have controller reading routines that incorporate the logic as the bits are tested, but I prefer having the bits combined into a single byte that can then be tested using boolean operations. This is the code for the first controller, the second controller works the same except you use $4017 instead of $4016.
ReadController1:
; strobe the controllers so will send button bits
LDA #$01
STA $4016
LDA #$00
STA $4016
; now loop through buttons to build joystick value
; a,b,select,start,up,down,left,right
LDY #$00
LDX #$08
ReadController1_pollJoy1:
LDA $4016 ; read bit
AND #$01 ; check if set (button down)
BEQ ReadController1_adjustJoyBits
INY ; as bit 0 is 0 will set bit!
ReadController1_adjustJoyBits:
TYA
DEX ; loop through button bits
; done here so won't over-shift flagset
BEQ ReadController1_done
ASL A ; shift button flag bits over
TAY
JMP ReadController1_pollJoy1
ReadController1_done:
RTS
One potential problem with the controller is that it is possible for the sound chip to cause interference with the serial bits resulting in the wrong value. This is a fairly easy to solve problem. Simply read the controller until the value returned is the same as the previous read.
A couple of convenience functions that I like to have, which are handy to have for things such as title screens and other times where you need to wait for button presses, are WaitForButtonPress and WaitForButtonPressEnd. These simply wait for a button press or the end of a button press.
WaitForButtonPress:
JSR ReadController1
AND #$F0
BEQ WaitForButtonPress
RTS
WaitForButtonPressEnd:
JSR ReadController1
AND #$F0
BNE WaitForButtonPressEnd
RTS
I also wrote a function that displays what controller buttons are pressed but as this is fairly simple code, I am not including it here. If you are interested in the code, it is still part of the Trivia source code (NES Trivia included in the game zip file.) even though it is not actually used.
The next couple of weeks will take a look at my first RPG Maker VXAce game. First a postmortem of the game and then a look at my thoughts on RPG Maker and what I am hoping the next version of this tool would have to make it perfect for my needs (not that my wishes are likely to happen but you never know).
The controllers need to be told to send their state to the serial port. This is done by a method known as strobing a port. Essentially this is simply writing a 1 to the port you wish to read followed by a 0 to the port you want to read. You then read the port 8 times for a standard controller, possibly more for other controllers. The low order bit (1) is used to tell you the state of the button being read while the next bit (2) indicates if a controller is attached. Other bits may also have significance, but I have not been able to find any information on this.
It is possible to have controller reading routines that incorporate the logic as the bits are tested, but I prefer having the bits combined into a single byte that can then be tested using boolean operations. This is the code for the first controller, the second controller works the same except you use $4017 instead of $4016.
ReadController1:
; strobe the controllers so will send button bits
LDA #$01
STA $4016
LDA #$00
STA $4016
; now loop through buttons to build joystick value
; a,b,select,start,up,down,left,right
LDY #$00
LDX #$08
ReadController1_pollJoy1:
LDA $4016 ; read bit
AND #$01 ; check if set (button down)
BEQ ReadController1_adjustJoyBits
INY ; as bit 0 is 0 will set bit!
ReadController1_adjustJoyBits:
TYA
DEX ; loop through button bits
; done here so won't over-shift flagset
BEQ ReadController1_done
ASL A ; shift button flag bits over
TAY
JMP ReadController1_pollJoy1
ReadController1_done:
RTS
One potential problem with the controller is that it is possible for the sound chip to cause interference with the serial bits resulting in the wrong value. This is a fairly easy to solve problem. Simply read the controller until the value returned is the same as the previous read.
A couple of convenience functions that I like to have, which are handy to have for things such as title screens and other times where you need to wait for button presses, are WaitForButtonPress and WaitForButtonPressEnd. These simply wait for a button press or the end of a button press.
WaitForButtonPress:
JSR ReadController1
AND #$F0
BEQ WaitForButtonPress
RTS
WaitForButtonPressEnd:
JSR ReadController1
AND #$F0
BNE WaitForButtonPressEnd
RTS
I also wrote a function that displays what controller buttons are pressed but as this is fairly simple code, I am not including it here. If you are interested in the code, it is still part of the Trivia source code (NES Trivia included in the game zip file.) even though it is not actually used.
The next couple of weeks will take a look at my first RPG Maker VXAce game. First a postmortem of the game and then a look at my thoughts on RPG Maker and what I am hoping the next version of this tool would have to make it perfect for my needs (not that my wishes are likely to happen but you never know).
Saturday, June 21, 2014
NES String Utilities
There are a number of additional functions that may not be necessary but are nice to have if you are creating a text heavy game. One print-related function that I love to have, even if it is a very simple function, is a way of repeatedly printing an indicated character an indicated number of times. On the NES it is simply a matter of a loop sending the desired character to the PPU. Still, having it as a separate function will save a few bytes per use so it doesn't hurt to have as its own function.
PrintRepeatedChar:
STA $2007
DEX
BNE PrintRepeatedChar
RTS
If you are manipulating strings on the fly, something that will be used in the RPG that is my ultimate goal, appending characters and strings is handy. Appending a character simply requires that the terminating zero be replaced with the character to be appended and the byte after that character be replaced with a terminating zero. As this code is fairly similar to the AppendString function I will not bother showing the code here but feel free to look it up in the source file.
The AppendString function adds string pointed to by SOURCE_PTR to the end of the string pointed to by DEST_PTR. Note that DEST_PTR is modified in the process to set up a call to StrNCpy. Our first goal is to find end index of DEST_PTR which points to the string being appended.
AppendString:
LDY #0
AppendString_loop:
LDA [DEST_PTR],Y
BEQ AppendString_doAppend
INY
CPY #$FF
BEQ AppendString_done
JMP AppendString_loop
Once we know the length of the string we are appending we adjust the DEST_PTR to point to the end of the string. We can then simply call the StrNCpy function to finish. It is possible to save a few cycles by jumping to StrNCpy and taking advantage of its RTS but this is considered a bad practice that should only be used for optimizing.
AppendString_doAppend:
; index in TEMP so it can be used with math instructions
STY TEMP
; Subtract index from 0 so we know maximum number of characters that
; can be copied
LDA #0
SEC SBC TEMP
; This is placed in X for subsequent StrNCpy call
TAX
; Add index to DEST_PTR
LDA DEST_PTR
CLC
ADC TEMP
BCC AppendString_StrNCpy
INC DEST_PTR+1
AppendString_StrNCpy:
STA DEST_PTR ; now do the append via StrNCpy
JSR StrNCpy
AppendString_done:
RTS
SubStr copies the middle portion of an existing string into a new string. Creating a new string from the middle portion of an existing string is another handy function but is really just a call to StrNCpy with an adjusted SOURCE_PTR. This is fairly straightforward code so will not be included here but is in the source file.
Finally the library includes a StrLength function. As we have done something like this in the Append String function we really don't need to show the code here. The big difference is we use SOURCE_PTR instead of DEST_PTR.
Now we have an adequate string library for our upcoming projects. Before we can get to creating the trivia game we still have one NES topic to cover. Controller input.
PrintRepeatedChar:
STA $2007
DEX
BNE PrintRepeatedChar
RTS
If you are manipulating strings on the fly, something that will be used in the RPG that is my ultimate goal, appending characters and strings is handy. Appending a character simply requires that the terminating zero be replaced with the character to be appended and the byte after that character be replaced with a terminating zero. As this code is fairly similar to the AppendString function I will not bother showing the code here but feel free to look it up in the source file.
The AppendString function adds string pointed to by SOURCE_PTR to the end of the string pointed to by DEST_PTR. Note that DEST_PTR is modified in the process to set up a call to StrNCpy. Our first goal is to find end index of DEST_PTR which points to the string being appended.
AppendString:
LDY #0
AppendString_loop:
LDA [DEST_PTR],Y
BEQ AppendString_doAppend
INY
CPY #$FF
BEQ AppendString_done
JMP AppendString_loop
Once we know the length of the string we are appending we adjust the DEST_PTR to point to the end of the string. We can then simply call the StrNCpy function to finish. It is possible to save a few cycles by jumping to StrNCpy and taking advantage of its RTS but this is considered a bad practice that should only be used for optimizing.
AppendString_doAppend:
; index in TEMP so it can be used with math instructions
STY TEMP
; Subtract index from 0 so we know maximum number of characters that
; can be copied
LDA #0
SEC SBC TEMP
; This is placed in X for subsequent StrNCpy call
TAX
; Add index to DEST_PTR
LDA DEST_PTR
CLC
ADC TEMP
BCC AppendString_StrNCpy
INC DEST_PTR+1
AppendString_StrNCpy:
STA DEST_PTR ; now do the append via StrNCpy
JSR StrNCpy
AppendString_done:
RTS
SubStr copies the middle portion of an existing string into a new string. Creating a new string from the middle portion of an existing string is another handy function but is really just a call to StrNCpy with an adjusted SOURCE_PTR. This is fairly straightforward code so will not be included here but is in the source file.
Finally the library includes a StrLength function. As we have done something like this in the Append String function we really don't need to show the code here. The big difference is we use SOURCE_PTR instead of DEST_PTR.
Now we have an adequate string library for our upcoming projects. Before we can get to creating the trivia game we still have one NES topic to cover. Controller input.
Friday, June 13, 2014
StrNCpy and StrNCmp
Copying strings is pretty much just your regular memory copy operation with a couple of twists. First, we have no idea how many bytes need to be copied. Second, we need to make sure to write a zero at the end of the string so that the copied string will always be a valid string.
This function requires two pointers be set up. The SOURCE_PTR points to string to be copied while the DEST_PTR pointer where string is copied to. We also use the X Register to hold the maximum length of the string (including null terminator) to be copied, with a length of 0 being an entire page.
First we set the character to copy to zero. We then use zero page indirect addressing to load a character from the source string. If this character is a zero then the copying is done. Because our last action is always to write an ending zero, we can branch right to our ending code. If not a zero then we write the character to the destination location.
StrNCpy:
LDY #0
StrNCpy_loop:
LDA [SOURCE_PTR],Y
BEQ StrNCpy_done
STA [DEST_PTR],Y
The X Register is deducted to make sure the maximum copy length had not been reached. If it has been then we force an overwrite of the last character with a zero.
DEX
BEQ StrNCpy_lengthReached
INY
JMP StrNCpy_loop
StrNCpy_lengthReached:
LDA #0 ; Make sure last character a zero
StrNCpy_done:
STA [DEST_PTR],Y
RTS
String comparison is fairly similar which makes it a fairly costly operation. The longer the strings being compared the longer the operation will take. This is why it irks me when I see programmers use strings when integer constants could be used. This is thankfully not as bad a situation as it could be. Many string class libraries use hash values when comparing strings. This technique has the cost of computing a hash value, but if strings are immutable as they are in many modern languages then the hash value will only be computed once. While hash values result in a simple and fast integer comparison for non-matching strings, hashes are not unique so if a match is found then you still need to check each character to make sure the strings match.
We are not computing or checking hash values, but are checking if the source and destination pointers are the same. This takes a few cycles but can save a lot of work. This also lets us cheat by using string pointers as our integer constants for prompts and other text-based menus.
StrNCmp:
LDY #0
LDA SOURCE_PTR
CMP DEST_PTR
BNE StrNCmp_compareLoop
LDA SOURCE_PTR+1
CMP DEST_PTR+1
BNE StrNCmp_compareLoop
; If we reach here, same pointer so must match
LDA #0
RTS
The second phase is a byte by byte comparison of the strings. The logic here is pretty similar to the string copy except we are comparing instead of copying.
StrNCmp_compareLoop:
LDA [SOURCE_PTR],Y
BEQ StrNCmp_done
CMP [DEST_PTR],Y
BNE StrNCmp_done
DEX ; Make sure not comparing beyond limit
BEQ StrNCmp_done
INY ; Now set next byte and continue
JMP StrNCmp_compareLoop
Finally we determine the comparison result. We do this the same way the CMP instruction does by simply subtracting the compared byte from the source byte.
StrNCmp_done:
SEC
SBC [DEST_PTR],Y
RTS
That covers the most important parts of our string library but there are still a few functions that I want to write which will be covered next.
This function requires two pointers be set up. The SOURCE_PTR points to string to be copied while the DEST_PTR pointer where string is copied to. We also use the X Register to hold the maximum length of the string (including null terminator) to be copied, with a length of 0 being an entire page.
First we set the character to copy to zero. We then use zero page indirect addressing to load a character from the source string. If this character is a zero then the copying is done. Because our last action is always to write an ending zero, we can branch right to our ending code. If not a zero then we write the character to the destination location.
StrNCpy:
LDY #0
StrNCpy_loop:
LDA [SOURCE_PTR],Y
BEQ StrNCpy_done
STA [DEST_PTR],Y
The X Register is deducted to make sure the maximum copy length had not been reached. If it has been then we force an overwrite of the last character with a zero.
DEX
BEQ StrNCpy_lengthReached
INY
JMP StrNCpy_loop
StrNCpy_lengthReached:
LDA #0 ; Make sure last character a zero
StrNCpy_done:
STA [DEST_PTR],Y
RTS
String comparison is fairly similar which makes it a fairly costly operation. The longer the strings being compared the longer the operation will take. This is why it irks me when I see programmers use strings when integer constants could be used. This is thankfully not as bad a situation as it could be. Many string class libraries use hash values when comparing strings. This technique has the cost of computing a hash value, but if strings are immutable as they are in many modern languages then the hash value will only be computed once. While hash values result in a simple and fast integer comparison for non-matching strings, hashes are not unique so if a match is found then you still need to check each character to make sure the strings match.
We are not computing or checking hash values, but are checking if the source and destination pointers are the same. This takes a few cycles but can save a lot of work. This also lets us cheat by using string pointers as our integer constants for prompts and other text-based menus.
StrNCmp:
LDY #0
LDA SOURCE_PTR
CMP DEST_PTR
BNE StrNCmp_compareLoop
LDA SOURCE_PTR+1
CMP DEST_PTR+1
BNE StrNCmp_compareLoop
; If we reach here, same pointer so must match
LDA #0
RTS
The second phase is a byte by byte comparison of the strings. The logic here is pretty similar to the string copy except we are comparing instead of copying.
StrNCmp_compareLoop:
LDA [SOURCE_PTR],Y
BEQ StrNCmp_done
CMP [DEST_PTR],Y
BNE StrNCmp_done
DEX ; Make sure not comparing beyond limit
BEQ StrNCmp_done
INY ; Now set next byte and continue
JMP StrNCmp_compareLoop
Finally we determine the comparison result. We do this the same way the CMP instruction does by simply subtracting the compared byte from the source byte.
StrNCmp_done:
SEC
SBC [DEST_PTR],Y
RTS
That covers the most important parts of our string library but there are still a few functions that I want to write which will be covered next.
Friday, June 6, 2014
NES Printing Strings
The whole purpose of having strings is to be able to display them. The print string function should support multiple screens and allow the location of the text to be specified. Because setting the PPU address to a screen location is handy to have even if we aren't writing a string, the SetScreenXY function will be created first. It will be called using the x / y registers to hold the x / y coordinates and the accumulator holding the quadrant of the screen to draw the text to.
The first thing the function does is multiplies the quadrant by 4 then adding the high byte of quadrant 0 to this. This is the equivalent of base_address = 1024 + quadrant_base. Next we want to multiply Y by 32 and add it to this address. I take advantage of the fact that the result of this multiplication would result in the upper 5 bits of Y being added to the high order byte so a tiny bit of bit manipulation saves a lot of looping logic. Finally, as we know the lower 5 bits of the low-order byte are clear, we can simply OR the X value with the PPU address. This may seem complicated, but looking at the code should help.
SetScreenXY:
; Calculate PPU Address for screen quadrant
ASL A
ASL A
CLC
ADC #$20
STA PPU_HIGH
; Add high portion of 32*y to high PPU address
TYA
LSR A
LSR A
LSR A
CLC
ADC PPU_HIGH
STA PPU_HIGH
; Calculate low byte of y * 32
TYA
; AND #$07 not necessary as 0 is always shifted in
ASL A
ASL A
ASL A
ASL A
ASL A
; add X to it
STA PPU_LOW
TXA
ORA PPU_LOW
STA PPU_LOW
LDA $2002 ; read PPU status to reset the high/low latch
LDA PPU_HIGH
STA $2006 ; write the high byte of screen address
LDA PPU_LOW
STA $2006 ; write the low byte of screen address
RTS
The PrintString function uses the SOURCE_PTR to hold the string to print, which it prints at the current PPU address. This means that SetScreenXY should be called before printing. Concurrent calls to PrintString will continue printing where the last call left off. The cursor position is not tracked, though if you needed this functionality it would be fairly easy to add. This is not something I will need so the printing functions are being kept to their simplest form. This function is a loop that prints characters until either 256 characters are printed or a zero character is reached. The length-checking code is free as a JMP would be needed if the BNE wasn't used.
PrintString:
LDY #0
PrintString_loop:
LDA [SOURCE_PTR],Y
BEQ PrintString_done
STA $2007
INY
BEQ PrintString_done
JMP PrintString_loop
PrintString_done:
RTS
The problem with these functions is that there a bunch of code required to set up the parameters. Wouldn't it be great if we could call a function like this: "CallPrintStringAt labelOfString, x,y,screenQuardrant"? This is what macros are for. Different assemblers will have different ways of setting up macros. This is the way it is done in NESAsm:
CallPrintStringAt .macro
LDA #LOW(\1)
STA SOURCE_PTR
LDA #HIGH(\1)
STA SOURCE_PTR+1
LDX #\2
LDY #\3
LDA #\4
JSR SetScreenXY
JSR PrintString
.endm
The \# sections are the parameters. The code is inserted as shown with the parameters being pasted as they are into the macro. For complicated functions that require setting up multiple registers and variables, macros can save a lot of time. They can also make code easier to read. It is important to remember that the code for the macro is inserted into the program every time the macro is used so it is not a substitute for functions. But they certainly make calling functions easier. The downside to macros is that the code is just pasted as is so some optimizations that could be done (such as if you know a register or memory address is already correct) are not.
Printing works great except for one problem. Color. As was explained in earlier posts, setting attributes is kind of complicated and colors are set for 2x2 blocks. The ClearScreen function sets the attributes. We will not cover the ClearScreen function as we have written similar code for HelloNES and HelloSprites already. Next we will be looking at string copying and comparisons which will include a bit of a rant.
The first thing the function does is multiplies the quadrant by 4 then adding the high byte of quadrant 0 to this. This is the equivalent of base_address = 1024 + quadrant_base. Next we want to multiply Y by 32 and add it to this address. I take advantage of the fact that the result of this multiplication would result in the upper 5 bits of Y being added to the high order byte so a tiny bit of bit manipulation saves a lot of looping logic. Finally, as we know the lower 5 bits of the low-order byte are clear, we can simply OR the X value with the PPU address. This may seem complicated, but looking at the code should help.
SetScreenXY:
; Calculate PPU Address for screen quadrant
ASL A
ASL A
CLC
ADC #$20
STA PPU_HIGH
; Add high portion of 32*y to high PPU address
TYA
LSR A
LSR A
LSR A
CLC
ADC PPU_HIGH
STA PPU_HIGH
; Calculate low byte of y * 32
TYA
; AND #$07 not necessary as 0 is always shifted in
ASL A
ASL A
ASL A
ASL A
ASL A
; add X to it
STA PPU_LOW
TXA
ORA PPU_LOW
STA PPU_LOW
LDA $2002 ; read PPU status to reset the high/low latch
LDA PPU_HIGH
STA $2006 ; write the high byte of screen address
LDA PPU_LOW
STA $2006 ; write the low byte of screen address
RTS
The PrintString function uses the SOURCE_PTR to hold the string to print, which it prints at the current PPU address. This means that SetScreenXY should be called before printing. Concurrent calls to PrintString will continue printing where the last call left off. The cursor position is not tracked, though if you needed this functionality it would be fairly easy to add. This is not something I will need so the printing functions are being kept to their simplest form. This function is a loop that prints characters until either 256 characters are printed or a zero character is reached. The length-checking code is free as a JMP would be needed if the BNE wasn't used.
PrintString:
LDY #0
PrintString_loop:
LDA [SOURCE_PTR],Y
BEQ PrintString_done
STA $2007
INY
BEQ PrintString_done
JMP PrintString_loop
PrintString_done:
RTS
The problem with these functions is that there a bunch of code required to set up the parameters. Wouldn't it be great if we could call a function like this: "CallPrintStringAt labelOfString, x,y,screenQuardrant"? This is what macros are for. Different assemblers will have different ways of setting up macros. This is the way it is done in NESAsm:
CallPrintStringAt .macro
LDA #LOW(\1)
STA SOURCE_PTR
LDA #HIGH(\1)
STA SOURCE_PTR+1
LDX #\2
LDY #\3
LDA #\4
JSR SetScreenXY
JSR PrintString
.endm
The \# sections are the parameters. The code is inserted as shown with the parameters being pasted as they are into the macro. For complicated functions that require setting up multiple registers and variables, macros can save a lot of time. They can also make code easier to read. It is important to remember that the code for the macro is inserted into the program every time the macro is used so it is not a substitute for functions. But they certainly make calling functions easier. The downside to macros is that the code is just pasted as is so some optimizations that could be done (such as if you know a register or memory address is already correct) are not.
Printing works great except for one problem. Color. As was explained in earlier posts, setting attributes is kind of complicated and colors are set for 2x2 blocks. The ClearScreen function sets the attributes. We will not cover the ClearScreen function as we have written similar code for HelloNES and HelloSprites already. Next we will be looking at string copying and comparisons which will include a bit of a rant.
Saturday, May 31, 2014
NES Trivia Postmortem
For my Blazing Games June game I am releasing my NES Trivia game even though I have not yet quite got to it in the blog. See the what went wrong section for the reason for this. Needless to say, here is the postmortem.
What Went Right
The string library that was developed for general purpose use worked really well. Especially when the macros were used. Lots of the display-related code is very easy to understand thanks to the use of the PrintStringAt macro. While the StrNCmp function was never used in the game, it will be used in the sequel so the code was not wasted, just slightly premature.
Mixed Blessings
The whole reason for this game was to actually create a simple game fairly early in the series. As it turned out, there was a lot more foundation work that I had to write about before I could get to the meat and have a real game. Even at this point there are things that could be improved. The biggest being randomness. Right after that (before that for many people) is sound. Filling up the extra ROM space with questions and touching up the display finish off my list.
What Went Wrong
This was not the game I was planning on releasing for June. I was going to hold off releasing the game until we actually started creating the game in this blog. Unfortunately, the game I was hoping to release for June simply was too large for the amount of time I had available (and my Dad going into the hospital for a hernia operation didn't help). Using this game as filler material was my only option. Sadly the game I was developing is going to be delayed by at least a month as there is a big RPG Maker contest in June. While I suspect there are people who are far more knowledgable about RPG Maker that will probably win, there are real prizes for this challenge and I did want to try out the tool so this will be my June project. If the resulting game got enough interest, and especially if it won money, I would probably shift my focus to developing a full version of that game.
What Went Right
The string library that was developed for general purpose use worked really well. Especially when the macros were used. Lots of the display-related code is very easy to understand thanks to the use of the PrintStringAt macro. While the StrNCmp function was never used in the game, it will be used in the sequel so the code was not wasted, just slightly premature.
Mixed Blessings
The whole reason for this game was to actually create a simple game fairly early in the series. As it turned out, there was a lot more foundation work that I had to write about before I could get to the meat and have a real game. Even at this point there are things that could be improved. The biggest being randomness. Right after that (before that for many people) is sound. Filling up the extra ROM space with questions and touching up the display finish off my list.
What Went Wrong
This was not the game I was planning on releasing for June. I was going to hold off releasing the game until we actually started creating the game in this blog. Unfortunately, the game I was hoping to release for June simply was too large for the amount of time I had available (and my Dad going into the hospital for a hernia operation didn't help). Using this game as filler material was my only option. Sadly the game I was developing is going to be delayed by at least a month as there is a big RPG Maker contest in June. While I suspect there are people who are far more knowledgable about RPG Maker that will probably win, there are real prizes for this challenge and I did want to try out the tool so this will be my June project. If the resulting game got enough interest, and especially if it won money, I would probably shift my focus to developing a full version of that game.
Friday, May 16, 2014
6502 Pointers
Pointers are one of those programming subjects that tend to confuse people yet once understood is really fairly simple. If you have been following my NES series of articles, the following code is probably easy to understand:
LDY #0
CopySpriteData:
LDA SpriteData,Y
STA $200,Y
INY
BEQ CopySpriteData
RTS
SpriteData:
; 256 bytes of sprite data go here
This code simply copies 256 bytes of data from SpriteData to the second page of 6502 memory. The fact that when a byte overflows it wraps around and becomes 0 again is used to terminate the loop. This code seems easy enough. The LDA instruction is using the address SpriteData is at, then adding the Y register to that value to come up with the memory address to grab the byte being copied from. Likewise, the STA is using the memory address $200 and adding the Y register to it to find the address to store the byte.
Pointers work pretty much the same way but instead of directly using an address, the address is stored in a pair of zero page addresses. Why would you want to do this? By having pointers to addresses instead of hard-coding the address you can create general-purpose functions. The caller would simply fill in the pointer with the address the function is suppose to use then call the function. The 6502 expects addresses to be written in a low to high format meaning the low-order byte is followed by the high-order byte. This is known as little-endian format. For NES developers this can be confusing as the PPU uses high/low (big-endian) for specifying addresses. To demonstrate pointers, here is a simple MemCpy function.
MemCpy:
LDY #0
MemCpy_loop:
LDA [SOURCE_PTR],Y
STA [DEST_PTR],Y
INY
CPY COPY_LENGTH
BNE MemCpy_loop
RTS
The Y register is zeroed out before the loop. The byte at the address pointed to in SOURCE_PTR offset by Y is loaded into the accumulator. This value is then stored at the address pointed to by DEST_PTR offset by Y. The Y offset is increased and the loop continues until Y is equal to the value in COPY_LENGTH.
This works, but the amount of effort required to set up the zero-page pointer variables requires more instructions than the actual function. A more complex function, such as a MemCpy function that supported 16-bit copy-lengths would be worth doing. Likewise, when you are dealing with addresses that you may not know beforehand, such as will be the case with the general-purpose prompt routines we will be developing, using pointers to data is incredibly handy.
The use of pointers is something that will be used quite a bit as we progress. Our Trivia game will be needing strings and some type of way of selecting a choice so that is what we will be focusing on next.
LDY #0
CopySpriteData:
LDA SpriteData,Y
STA $200,Y
INY
BEQ CopySpriteData
RTS
SpriteData:
; 256 bytes of sprite data go here
This code simply copies 256 bytes of data from SpriteData to the second page of 6502 memory. The fact that when a byte overflows it wraps around and becomes 0 again is used to terminate the loop. This code seems easy enough. The LDA instruction is using the address SpriteData is at, then adding the Y register to that value to come up with the memory address to grab the byte being copied from. Likewise, the STA is using the memory address $200 and adding the Y register to it to find the address to store the byte.
Pointers work pretty much the same way but instead of directly using an address, the address is stored in a pair of zero page addresses. Why would you want to do this? By having pointers to addresses instead of hard-coding the address you can create general-purpose functions. The caller would simply fill in the pointer with the address the function is suppose to use then call the function. The 6502 expects addresses to be written in a low to high format meaning the low-order byte is followed by the high-order byte. This is known as little-endian format. For NES developers this can be confusing as the PPU uses high/low (big-endian) for specifying addresses. To demonstrate pointers, here is a simple MemCpy function.
MemCpy:
LDY #0
MemCpy_loop:
LDA [SOURCE_PTR],Y
STA [DEST_PTR],Y
INY
CPY COPY_LENGTH
BNE MemCpy_loop
RTS
The Y register is zeroed out before the loop. The byte at the address pointed to in SOURCE_PTR offset by Y is loaded into the accumulator. This value is then stored at the address pointed to by DEST_PTR offset by Y. The Y offset is increased and the loop continues until Y is equal to the value in COPY_LENGTH.
This works, but the amount of effort required to set up the zero-page pointer variables requires more instructions than the actual function. A more complex function, such as a MemCpy function that supported 16-bit copy-lengths would be worth doing. Likewise, when you are dealing with addresses that you may not know beforehand, such as will be the case with the general-purpose prompt routines we will be developing, using pointers to data is incredibly handy.
The use of pointers is something that will be used quite a bit as we progress. Our Trivia game will be needing strings and some type of way of selecting a choice so that is what we will be focusing on next.
Saturday, May 10, 2014
NES Hello Sprites 2
NES programs will often clone sprite memory in RAM to allow for DMA transfers. DMA, which stands for Direct Memory Access, is a rapid way of transferring blocks of memory to hardware. When a DMA transfer happens, the processor is temporarily halted while the memory is rapidly copied into PPU memory. While this still takes time, it is much faster than manually copying sprites. If you have a large number of sprites, it is far better to use DMA than to manually copy sprites.
Another convenience that the NES provides programmers is a VBlank Non-Maskable Interrupt (NMI). Interrupts cause the processor to stop what it is doing and immediately start running the subroutine pointed to by the interrupt vector. There are two types of interrupts. Your regular interrupts, or IRQs, can be disabled by the SEI instruction and enabled by the CLI instruction. NMIs are not affected by those instructions so will always occur. The PPU control register, however, does allow the VBlank to be disabled.
The advantage of using interrupts is that you do not have to worry about polling for the VBlank so you can process other game logic while the PPU is drawing the screen without having to worry about missing this important period. In this second sprite demo, all of our logic will be done in the interrupt handler, though this certainly does not have to be the case and in later projects we will be developing a state machine to handle complex processing.
The code for the second version of our sprite demo is very similar to the original version with only a few changes needed to support interrupts and DMA. Instead of copying the sprite data directly to the PPUs sprite memory, it is copied to a page of RAM. VBlank interrupts are enabled just before the radically changed main loop as follows:
LDA #%10000000 ; enable NMI
STA $2000
MainGameLoop:
NOP ; Right now we do nothing in the main loop.
JMP MainGameLoop
NMI:
LDA #2
STA $4014
JSR MoveSprites
RTI
Another convenience that the NES provides programmers is a VBlank Non-Maskable Interrupt (NMI). Interrupts cause the processor to stop what it is doing and immediately start running the subroutine pointed to by the interrupt vector. There are two types of interrupts. Your regular interrupts, or IRQs, can be disabled by the SEI instruction and enabled by the CLI instruction. NMIs are not affected by those instructions so will always occur. The PPU control register, however, does allow the VBlank to be disabled.
The advantage of using interrupts is that you do not have to worry about polling for the VBlank so you can process other game logic while the PPU is drawing the screen without having to worry about missing this important period. In this second sprite demo, all of our logic will be done in the interrupt handler, though this certainly does not have to be the case and in later projects we will be developing a state machine to handle complex processing.
The code for the second version of our sprite demo is very similar to the original version with only a few changes needed to support interrupts and DMA. Instead of copying the sprite data directly to the PPUs sprite memory, it is copied to a page of RAM. VBlank interrupts are enabled just before the radically changed main loop as follows:
LDA #%10000000 ; enable NMI
STA $2000
MainGameLoop:
NOP ; Right now we do nothing in the main loop.
JMP MainGameLoop
Yes, absolutely nothing is done in the main loop. Instead all work is done in the Interrupt. While the NOP instruction is not necessary, I have heard urban legends about loops causing hardware to catch fire. I find this highly suspect but spreading the loop over an extra instruction does give me an excuse to use my favorite 6502 instruction. The real logic happens in the Non-maskable interrupt routine. Though thanks to DMA hardware this is much simpler than the manual method of transferring sprite information.
LDA #2
STA $4014
JSR MoveSprites
RTI
As you can see, DMA is very simple to use. You just store the page of memory you want to transfer to the PPU into $4014. The move sprites function is almost identical except it stores sprite positions to RAM instead of writing directly to the PPUs sprite memory which has the added benefit of being more compatible with emulators (always a benefit for home-brew games).
Thursday, April 24, 2014
NES Hello Sprites 1
For my first demonstration of sprites I decided to go with your typical bouncing sprite. Instead of using a ball, the text message "Hello Sprites!" Is used. Due to the NES limitation of only allowing 8 sprites on a line, the message is broken into two lines of text. This version manually manipulates sprite memory instead of using the more common DMA method that will be covered next. To my surprise, this actually lead to issues with the emulator if ran with the default fast PPU emulation though worked fine when ran with the slower PPU emulator. This is a good reminder that emulators are not always accurate, though developing with them is much faster than developing on real hardware.
The complete source code for this project is at http://blazinggames.com/books/NES/. Only the most relevant sections of code are included.
The program starts out with pretty standard initialization code. The screen is then filled with a checkerboard pattern instead of blank spaces so it will be clear that the sprites are appearing above the screen image (background). Next we have the task of setting up the sprites. In our case we are just copying this info from a table in ROM into the PPU Sprite Memory. Variables for holding the top corner of the block of sprites and the movement directions are then set up. The main loop simply waits for the VBlank flag to be set and then calls the moveSprite function which has all the movement logic.
; initialize the sprites
LDA #0
TAY
STA $2003
; fill sprite memory with data from ROM
InitSprites_dataLoop:
LDA hello_sprite_data,Y
STA $2004
INY
CPY #52
BNE InitSprites_dataLoop
; fill rest of sprite memory with 255 so that it will not be visible
LDA #255
InitSprites_fillLoop:
STA $2004
INY
BNE InitSprites_fillLoop
; set sprite variables
LDA #0
STA SPRITE_X
STA SPRITE_Y
LDA #1
STA H_ADJUST
STA V_ADJUST
The MoveSprites function is fairly large but also simple. The first chunk of code shows how the coordinates are adjusted. Each frame the block's top corner is adjusted by the current direction variables. Hits against the screen bounds are then performed with hits resulting in the direction being changed. What may be confusing, and something I will definitely be writing about in the future, 255 is used for -1.
MoveSprites:
; First adjust top corner of sprite block
LDA SPRITE_X
CLC
ADC H_ADJUST
STA SPRITE_X
; Has hit left edge?
BNE MoveSprite_checkRightEdge
; if so set horizontal adjust to +1
LDA #1
STA H_ADJUST
MoveSprite_checkRightEdge:
; if has hit right edge?
CMP #192
BNE MoveSprite_vertical
; if hit right edge, set horizontal adjust to -1
LDA #$FF ; -1
STA H_ADJUST
; vertical tests similar to above and not included here, see source file for full code.
Once we know the coordinates for the top of the block of sprites, we can calculate the position of the sprites. As the bottom calculations are the more complex ones, here is the code for setting the bottom line of sprites.
; we reach here when top row done, so prepare bottom row
LDA SPRITE_Y
CLC
ADC #8
STA TEMP_Y ; Y coordinate to use for bottom row of sprites
LDA SPRITE_X
STA TEMP_X ; X coordinate starts at block x coordinate
; Y register already correct so no need to set
LDX #8 ; 8 sprites in bottom row need to be counted
MoveSprite_bottomRowAdjust:
STY $2003
LDA TEMP_Y
STA $2004 ; write adjusted sprite Y coordinate
INY ; Adjust index to point to sprite X data
INY
INY
STY $2003 ; tell PPU we want to write sprite X
LDA TEMP_X
STA $2004 ; Write sprite X coordinate
CLC
ADC #8 ; add 8 to this coordinate for next sprite
STA TEMP_X
INY ; set up index for next sprite
DEX ; and adjust countdown
BNE MoveSprite_bottomRowAdjust
As you can see it is fairly simple. Most NES programs, however, do not manipulate sprites directly as we are doing so we will rewrite this program to take advantage of interrupts and DMA,as well as explain what interrupts and DMA shortly but next week will be a postmortem possibly followed by something else.
The complete source code for this project is at http://blazinggames.com/books/NES/. Only the most relevant sections of code are included.
The program starts out with pretty standard initialization code. The screen is then filled with a checkerboard pattern instead of blank spaces so it will be clear that the sprites are appearing above the screen image (background). Next we have the task of setting up the sprites. In our case we are just copying this info from a table in ROM into the PPU Sprite Memory. Variables for holding the top corner of the block of sprites and the movement directions are then set up. The main loop simply waits for the VBlank flag to be set and then calls the moveSprite function which has all the movement logic.
; initialize the sprites
LDA #0
TAY
STA $2003
; fill sprite memory with data from ROM
InitSprites_dataLoop:
LDA hello_sprite_data,Y
STA $2004
INY
CPY #52
BNE InitSprites_dataLoop
; fill rest of sprite memory with 255 so that it will not be visible
LDA #255
InitSprites_fillLoop:
STA $2004
INY
BNE InitSprites_fillLoop
; set sprite variables
LDA #0
STA SPRITE_X
STA SPRITE_Y
LDA #1
STA H_ADJUST
STA V_ADJUST
The MoveSprites function is fairly large but also simple. The first chunk of code shows how the coordinates are adjusted. Each frame the block's top corner is adjusted by the current direction variables. Hits against the screen bounds are then performed with hits resulting in the direction being changed. What may be confusing, and something I will definitely be writing about in the future, 255 is used for -1.
MoveSprites:
; First adjust top corner of sprite block
LDA SPRITE_X
CLC
ADC H_ADJUST
STA SPRITE_X
; Has hit left edge?
BNE MoveSprite_checkRightEdge
; if so set horizontal adjust to +1
LDA #1
STA H_ADJUST
MoveSprite_checkRightEdge:
; if has hit right edge?
CMP #192
BNE MoveSprite_vertical
; if hit right edge, set horizontal adjust to -1
LDA #$FF ; -1
STA H_ADJUST
; vertical tests similar to above and not included here, see source file for full code.
Once we know the coordinates for the top of the block of sprites, we can calculate the position of the sprites. As the bottom calculations are the more complex ones, here is the code for setting the bottom line of sprites.
; we reach here when top row done, so prepare bottom row
LDA SPRITE_Y
CLC
ADC #8
STA TEMP_Y ; Y coordinate to use for bottom row of sprites
LDA SPRITE_X
STA TEMP_X ; X coordinate starts at block x coordinate
; Y register already correct so no need to set
LDX #8 ; 8 sprites in bottom row need to be counted
MoveSprite_bottomRowAdjust:
STY $2003
LDA TEMP_Y
STA $2004 ; write adjusted sprite Y coordinate
INY ; Adjust index to point to sprite X data
INY
INY
STY $2003 ; tell PPU we want to write sprite X
LDA TEMP_X
STA $2004 ; Write sprite X coordinate
CLC
ADC #8 ; add 8 to this coordinate for next sprite
STA TEMP_X
INY ; set up index for next sprite
DEX ; and adjust countdown
BNE MoveSprite_bottomRowAdjust
As you can see it is fairly simple. Most NES programs, however, do not manipulate sprites directly as we are doing so we will rewrite this program to take advantage of interrupts and DMA,as well as explain what interrupts and DMA shortly but next week will be a postmortem possibly followed by something else.
Thursday, April 17, 2014
NES Sprites
When programmers talk about sprites they are not usually talking about the mythical variant. They are referring to a special graphical object that has transparent parts and floats above the display without altering what is behind it. This seemingly magical behaviour is likely why programmers started referring to these objects as sprites. In fact, on the 2600, they were referred to as Player Missile Graphics. Sprite Graphics clearly rolls off the tongue better.
Not all graphics hardware supports sprites, and even when they do the specifications for the sprite can vary wildly. When emulating sprites in software, the software needs to be able to preserve what is behind the sprites and then draw the sprites over the display. With multiple overlapping sprites this can be complex, especially if speed is important. Thankfully the NES supports hardware sprites so we don't need to get into display lists and dirty rectangles here.
The NES supports 64 sprites on the screen at the same time, though the hardware is limited to displaying 8 sprites on a single scan line. To get around these limitations games that need more sprites or more than 8 sprites on a scan line can shuffle sprites around so the visible sprites change from frame to frame. This has the downside of causing a noticeable flickering effect. Some emulators do not emulate the 8 sprites per scan line limitation so will only flicker if you use more than 64 sprites. I prefer emulators that keep hardware limitations and bugs as that makes it much more likely that whatever I create using the emulator will run fine on real hardware.
Each of the 64 NES sprites take 4 bytes of memory. The layout of this data may seem rather haphazard but is actually very logical from a hardware perspective. Sprite data is set up as follows:
Byte 0 holds the Y coordinate. To know if it needs to draw a sprite on the current scan line this value is used making it the first piece of sprite information needed.
Byte 1 is the character to draw. Hardware needs to know this to grab the data from the pattern table.
Byte 2 is the display flags that alter how the sprite is displayed so is obviously the next piece of information the hardware needs. The bits are set up as follows:
Bit 0 and 1 indicate the palette to use
Bits 2 though 4 are not used
Bit 5 When set puts the sprite behind the background image.
Bit 6 Flips sprite horizontally when set
Bit 7 Flips sprite vertically when set
Byte 3 holds the X coordinate. Once the hardware knows what it is drawing it needs to know where on the line it is going to be drawn.
We now know enough about sprites to create a demo, which will be next.
Not all graphics hardware supports sprites, and even when they do the specifications for the sprite can vary wildly. When emulating sprites in software, the software needs to be able to preserve what is behind the sprites and then draw the sprites over the display. With multiple overlapping sprites this can be complex, especially if speed is important. Thankfully the NES supports hardware sprites so we don't need to get into display lists and dirty rectangles here.
The NES supports 64 sprites on the screen at the same time, though the hardware is limited to displaying 8 sprites on a single scan line. To get around these limitations games that need more sprites or more than 8 sprites on a scan line can shuffle sprites around so the visible sprites change from frame to frame. This has the downside of causing a noticeable flickering effect. Some emulators do not emulate the 8 sprites per scan line limitation so will only flicker if you use more than 64 sprites. I prefer emulators that keep hardware limitations and bugs as that makes it much more likely that whatever I create using the emulator will run fine on real hardware.
Each of the 64 NES sprites take 4 bytes of memory. The layout of this data may seem rather haphazard but is actually very logical from a hardware perspective. Sprite data is set up as follows:
Byte 0 holds the Y coordinate. To know if it needs to draw a sprite on the current scan line this value is used making it the first piece of sprite information needed.
Byte 1 is the character to draw. Hardware needs to know this to grab the data from the pattern table.
Byte 2 is the display flags that alter how the sprite is displayed so is obviously the next piece of information the hardware needs. The bits are set up as follows:
Bit 0 and 1 indicate the palette to use
Bits 2 though 4 are not used
Bit 5 When set puts the sprite behind the background image.
Bit 6 Flips sprite horizontally when set
Bit 7 Flips sprite vertically when set
Byte 3 holds the X coordinate. Once the hardware knows what it is drawing it needs to know where on the line it is going to be drawn.
We now know enough about sprites to create a demo, which will be next.
Subscribe to:
Posts (Atom)

