Friday, October 7, 2016

NESDev - Sprites

I'm really overdue for this post.

Week 4 of the Nerdy Nights tutorial covered the NES color palettes as well as a brief introduction to sprites.  Week 5 followed up with more elaboration on sprites and introducing controls.  It was a fair amount of information to take in and understand,and seeing as how I started the lesson 2 weeks ago, I may be scrambling together it all back together again.

For now, I'll just go off the notes I had taken at the time...

Sprites/PPU Review

  • Sprites - non-static graphics that are separate from the background.
    • made up of 8x8 tiles of pixels
    • PPU can hold up to 64 sprites
  • Sprite DMA - "Direct Memory Access"
    • This is apparently a method for transfer sprites into memory, and the way it works is described as "a block of RAM is copied from CPU memory to the PPU sprite memory". 
    • Typically uses addresses $0200-$02FF 
    • I'll be coming back to this one...

Let's break down the code... "sprites.asm"

We left off discussing how the color palette gets loaded into the NES PPU.  Following this in the code structure of sprites.asm is the loading and placement of a single sprite.  The way sprites are loaded in to the PPU is brokem down into 4 specific bytes of data:
  1. Y-Position
  2. Tile number - referencing the pattern table position
  3. Attributes - color and display info
  4. X-Position
The x and y position seems to work on a grid, similar to below:


The tile number is in reference to the placement of tiles within the .chr file.  Using FCEUX SP, we can see those values quite clearly:

Lastly, the attributes are adjusted in a similar fashion to the PPUCTRL register discussed previously.  The program sends a string of 8 buts to the last register reserved for this sprite.  Those bits are organized as such (from Nerdy Nights):

  |||   ||
  |||   ++- Color Palette of sprite.  Choose which set of 4 from the 16 colors to use
  ||+------ Priority (0: in front of background; 1: behind background)
  |+------- Flip sprite horizontally
  +-------- Flip sprite vertically

There are essentially 4 steps to loading a sprite into the PPU.  The code snippet included in this weeks tutorial paints this quite simply using 1 byte to center the sprite (#$80), and 1 byte to determine which sprite to place and how it will look (#$00):

  LDA #$80
  STA $0200        ;put sprite 0 in center ($80) of screen vertically
  STA $0203        ;put sprite 0 in center ($80) of screen horizontally
  LDA #$00
  STA $0201        ;tile number = 0
  STA $0202        ;color palette = 0, no flipping

  LDA #%10000000   ; enable NMI, sprites from Pattern Table 0 (PPUCNTRL)
  STA $2000

  LDA #010000   ; no intensify (black background), enable sprites (PPUMASK)
  STA $2001

Notice where the data is being loaded.  As mentioned previously, the Sprite DMA typically uses addresses $0200 - $02FF.  Each sprite that is loaded will use 4 adresses to cover the 4 bytes of data we have mentioned here, hence why this particular sprite is loaded into registers $0200 - $0203.

It is worth noting that this code snippet also loads to PPUCTRL and PPUMASK (last 4 lines).  The comments should speak to themselves as to what the values are controlling.  However, I am finding it useful to keep a reference sheet on hand.

Multiple Sprites

In the following week's lesson, bunnyboy goes into the process of placing multiple sprites into the PPU.  There isn't too much to this, we can apply the same logic used when we loaded color palette data previously.  Storing those 4 values into a .db directive helps keep everything organized:

     ;vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3

Using the X index, we can apply these bytes of data into the appropriate addresses within $0200-$02FF using a simple loop.

  LDX #$00              ; start at 0
  LDA sprites, x        ; load data from address (sprites + x)
  STA $0200, x          ; store into RAM address ($0200 + x)
  INX                   ; X = X + 1
  CPX #$10              ; Compare X to hex $10, decimal 16
  BNE LoadSpritesLoop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero
                        ; if compare was equal to 16, continue down

I've given up on formatting.

This logic should be fairly easy to follow, but just in case, I'll break it down:
  1. Load #$00 into Index X.  As we will be using this register to keep count as we load each individual byte of sprite data, we may as well be starting at 0.
  2. Load the first byte from the sprites .db directive.  As we are starting at 0, value 0 is loaded into Accumulator A ($80, in this case).
  3. Store the loaded byte into $0200.  The reference to accumulator X will result in a step up to the next register once the loop completes and starts again.
  4. Increment X.  This adds #$01 to the number currently stored in index X.  This allows us to step up to the next byte of data in the sprite directory and the next PPU address to store the data in.
  5. Compare X to #$10 and branch back to loop start if they are not equal.  This compares the current index count to the decimal number 16, which is the number of bytes we have stored in the sprite directory.  If we have hit this number, then we have loaded all of our sprites and can move on in the code.

For the next post I will be going into the use on controllers and how they control the movement of these sprites.  I will probably be referring a lot to the information covered in this post.


Yes, I have a lot of holes to fill in this post.

No comments:

Post a Comment