Tuesday, November 29, 2016

NESDev - Overdue Update

 The end of the semester is approximately 3 weeks away, and I realized that I am well overdue for a blog post.

I have opted out of the "Nerdy Nights" tutorial series for the time being, as they have taken on a rather steep learning curve near the last few lessons.  I noticed that the last lesson I covered observed how sprites are loaded into the PPU and onto the screen, so I will try to summarize everything from that point to where the project sits now.


Controller Input

Controller inputs are read through register $4016 for Player 1 and $4017 for Player 2.Each button value is read in a particular order:
    1. A
    2. B
    3. Select
    4. Start
    5. Up
    6. Down
    7. Left
    8. Right
The standard method of reading these values is through a basic loop that is constantly stored into a single byte, as seen from the Nerdy Nights Tutorial here:

ReadController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016
  LDX #$08
ReadControllerLoop:
  LDA $4016
  LSR A           ; bit0 -> Carry
  ROL buttons     ; bit0 <- Carry
  DEX
  BNE ReadControllerLoop
  RTS

This code uses the LSR and ROL commands in an interesting manner.  LSR (Logical Shift Right) moves the rightmost bit into the 'carry' position of Accumulator A, then the ROL (Rotate Left) command moves the value in the carry position back into the first bit of the "buttons" directive. Every time a new value read into the "buttons" directive, it scoots the previous value over one bit, until each of the 8 button values fill a whole byte.  As the controller is read in that order with every refresh, those values will be consistently changing.  Then the program can compare each individual bit to jump to a subroutine designated for each button's functionality.

A somewhat uglier way of programming this functionality is by simply laying out each step of the controller reading cycle, like so:

ReadA: 
LDA $4016      
        * code here*
ReadB: 
LDA $4016        ;
        * code here*
ReadSelect: 
LDA $4016       
        * code here*
ReadStart: 
LDA $4016       
        * code here*
ReadUp: 
LDA $4016       
        * code here*
ReadDown: 
LDA $4016       
        * code here*
ReadLeft: 
LDA $4016       
        * code here*
ReadRight: 
LDA $4016       
        * code here*

Order matters.

For the sake of moving forward with the little time I have remaining, I chose to stick to the latter method of controller reading and moved on to adding movement to my own sprites.



While I had hoped to create my sprites in a more "raw" fashion, it seems the most common way to go about creating a chr file is through the use of third-party software.  In my case, I opted to use yy-chr.  This tool simplified the process, creating an environment similar to microsoft paint that featured a grid to match each 8x8 pixel tile.  My wife asked to do the pixel art for my project, below is some of the work she did:


As I am looking to create a very basic 2 player game, I will be using only 
two of the characters she drew up for me.


Sprite Movement

A concept that concerned me was how to make multiple sprites move together as one entity.  It turns out, it's not quite so difficult.  To demonstrate this example I'll use some code snippets from my WIP game.

You of course need to start off by loading the sprites into the PPU registers.  Using a directive for each sprite and a looping structure to load them is the simplest and most straightforward:

sprites:
     ;vert tile attr horiz
; Y - TILE - ATTR - X
.db $80, $00, $00, $80   ;headleft $0200 - $0203
.db $80, $01, $00, $88 ;headright $0204 - $0207
.db $88, $10, $00, $80   ;spine $0208 - $020B
.db $88, $11, $00, $88   ;front $020C - $020F
.db $90, $20, $00, $80 ;back leg $0210 - $0213
.db $90, $21, $00, $88   ;front leg $0214 - $0217

---------------------------------------

LoadSprites:
LDX #$00              ; start at 0
LoadSpritesLoop:
LDA sprites, x        ; load data from address (sprites +  x)
STA $0200, x          ; store into RAM address ($0200 + x)
INX                   ; X = X + 1
CPX #$28              ; Compare X to hex $20, decimal 32
BNE LoadSpritesLoop   ;
LDA #%10000000   ; enable NMI, sprites from Pattern Table 1
STA $2000

LDA #010000   ; enable sprites
STA $2001

The upper snippet of code is how my directives are constructed, while the bottom snippet is the looping structure to load the sprites into memory, which was broken down in a previous post.  The more sprites I add to the directive will naturally result in an expansion of this looping structure. The directive places everything in the appropriate order, and as you can see, the inital X and Y values are put into place.  From here, it is a simple matter of adding or subtracting the value of '1' to those values whenever the left or right button is pushed, making the sprite move left and right.
;;;;;;;;;; HEAD LEFT ;;;;;;;;;;
LDA #%01000000   ; Flip Horizon
STA $0202 ; Data Sprite 1
LDA $0203       ; load sprite X position
CLC ;
SBC #$01 ;
STA $0203         ; Save Sprite 1 X position
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

I have commented this code to reflect what part of the character it will move, in this case the left side of the head.  In this case I am trying to "flip" the sprite by loading the value 01000000 into the data register of that particular sprite.  From there, I have just loaded the current X value, used CLC to ensure the carry bit is clear, and subtracted 1, as moving left is considered a negative movement on the screen.



Up Next

The current issue I am trying to fix is using the PPU's mirroring capability to force the character to face the direction it is heading.  Unfortunately, this is a process I have to figure out on my own.  What I have been doing thus far is checking the position of the front-facing sprite in relation to the back sprites when the left or right buttons are pressed.  If the "back" sprite is in front of the "front" sprite, then the program should jump to a subroutine immediately adding a value of 8 to the front sprite's current X coordinate.  This is my current result:


It appears that the code is acting as it should, but only when the character scrolls off screen and appears on the opposite end (I have left scrolling on as I thought it might add some fun to the game).

Once I fix this issue, here is my potential "TO DO" list:
  • Higher Priority
    • "Jump" code
    • Adding 2nd player
  • If I Have Time
    • Attacking projectiles
    • Registering hits
    • Health
I will probably be ignoring the idea of start screens or even sounds, as I am running low on time for this project.

Wish me luck.




No comments:

Post a Comment