This was very large and I had to write more sections in different parts of the tutorial, so please tell me if anything is confusing. I have not proofread any of this.

Sprites are important to every game. They are coined after the objects that move in a game, and there are ALOT of moving objects in a game! So... sprites are very important to any demo or game, capiche?

I want you to read the Sprites tutorial in Qwertie's doc. He explains a lot of important things. However, he made 1 mistake which I would like to correct for you in advance. Bits 0,2,4,6 in the 2nd sprite table are the X coord-MSB, and bits 1,3,5,7 are the Size Toggle bits. He mixed that up. Also, if you don't understand some parts, don't worry, I am going to go over most of these concepts later in the tutorial. Ok go read it!

Back yet? I didn't think so. Now? nope.. How about now? Ok good! Wait.. no

Screw this I'm starting. (that was all to waste time while I thought about what to say next)

Ok, one of the important and possibly confusing parts of that section was the explanation of how sprites are stored in VRAM. Well, if you're lazy, you don't have to worry about it, because Neviksti's pcx2snes program automatically organizes the tiles in such a way that you only need to upload 1 large block of tile data. But, if you wanted to do it manually, you could create the 16x16 or larger tile(s), then arrange the tiles all into 1 row of 8x8 tiles, and DMA each row, transferring in 512 byte sections. I'll take the easy way :).

Ok, we're going to create our graphics in an editor simply because we can, and someone nice made programs that do the conversions. However, I still want to teach you the graphics format of sprites. If you don't care, skip this part.

SNES Sprites are 4bpp, giving a total of 16 possible colors for each pixel. Remember the explanation of 2bpp graphics, Learning the GFX Format - 2BPP Gameboy and SNES? You can think of a 4bpp tile as 2bpp tiles one after the other. Bitplanes 0 and 1 are in the first 2bpp tile, and bitplants 2 and 3 are in the second 2bpp tile. Here's an example from FDwR's snesgfx doc:

Byte 0| Row 0| Bpl 0|  00011000    0 /-- <=====Bitplane 0
     1|     0| Bpl 1|  00000000      | --\ 0 <=Bitplane 1
     2|     1|     0|  00111100    1 |-- |
     3|     1|     1|  00000000      | --| 1
     4|     2|     0|  01100110    2 |-- |
     5|     2|     1|  00000000      | --| 2
     6|     3|     0|  11000011    3 |-- |
     7|     3|     1|  00000000      | --| 3
     8|     4|     0|  11111111    4 |-- |
     9|     4|     1|  00000000      | --| 4
    10|     5|     0|  11000011    5 |-- |
    11|     5|     1|  00000000      | --| 5
    12|     6|     0|  11000011    6 |-- |
    13|     6|     1|  00000000      | --| 6
    14|     7|     0|  11000011    7 \-- |
    15|     7|     1|  00000000        --/ 7
Beginning of the second 2bpl tile
    16|     0|     2|  00000000    0 /-- <=====Bitplane 2
    17|     0|     3|  00000000      | --\ 0 <=Bitplane 3
    18|     1|     2|  00000000    1 |-- |
    ...                ...
    29|     6|     3|  00000000      | --| 6
    30|     7|     2|  00000000    7 \-- |
    31|     7|     3|  00000000        --/ 7

Understand? If you didn't it's not all that bad because you still have the programs that do the conversions for you. In this case, it's not all that important to reinvent the wheel.

Ok now it's time to....hm.. Well you read Qwertie's doc on snes sprites. Ok, I got it, first we'll MAKE the sprite.

Use a good graphics editor that supports pcx files. I found a nice free one called Ultimate Paint. It does the job great for my SNES work. Make a 32x32 image, fill it with a color you want to act as transparent (this color will not show up when the sprite is displayed), and then draw an image with up to 16 colors in the picture. Save it as a 256 color pcx file. Now the important part (and I should have done this for the BG tutorial, had I known better):

You need to align the palette so color 0 is the color you chose for transparency. Otherwise, whatever color is color 0 will then be transparent, and the color you wanted to be transparent will show!

Good / Bad

bike-good.gif bike-bad.gif

To make it 'good' we will use this cool program I found called PalExChaos. Here's what we do to swap whatever is in color 0 with what we really want for color 0. We'll say that we want pink as our transparent color, and blue is in color 0.

Open up PalExChaos. Click Load Pic, and select your pcx, a window pops up showing your image. Just move that aside, we don't care about it. In the main window, the boxes representing the palette should fill up. If your transparent color (pink) is not already color 0, click and hold the mouse on it, drag the mouse to color 0's box, and release. The colors will swap, and the job is done. Click Save Pic and save.

Now we have to convert the pcx to SNES format. We'll use Neviksti's pcx2snes. It's one of the best pcx converters for SNES out there since it has so many features. Type the following command in a cmd prompt in the directory where your pcx is (biker.pcx is the filename for me. It is 32x32 in size):

pcx2snes -s32 -c16 -o16 -n biker
           |    |    |   |   |
           |    |    |   |   |
           |    |    |   |    \--> Name of pcx file (without extension)
           |    |    |   |
           |    |    |    \------> No border surrounding tile(s)
           |    |    |   
           |    |     \----------> Number of colors in palette to output
           |    |
           |     \---------------> Color Depth of tiles(s)
           |
            \--------------------> Size of tile(s)

This will produce 2 binary files. 1 is named biker.pic, which contains the character data. The other is biker.clr, which contains the palette entries. Both are ready to be uploaded to VRAM and CGRAM, and can be modified in a hex editor if need-be.

Now we have our sprite graphic ready, so now it's time to do a little coding. Preparing the sprites is mostly similar to how we setup a BG, so this won't be too painful to learn.

We will load our palette and character data in same way as before. But, sprite palettes start at entry 128, so you'll need to load from 128 instead of 0. That's simple enough..

Now you need to learn about OAM - Object Attribute Memory. OAM contains all the properties of your sprites, such as the X coordinate, Y coordinate, tile #, vertical flip, etc (All will be listed shortly). OAM can only hold properties for up to 128 sprites at a time. Also, OAM addresses are a word in size, in case you did not know. Now let's learn how the OAM memory is organized.

OAM consists of 544 ($220) bytes of data (or $110 words), split (unevenly) into 2 tables. The first table is $200 bytes long ($100 words), ranging from $0000-$00FF. The 2nd table is just $20 bytes long ($10 words), and ranges from $0100-010F. Here is what is contained in this tables:

Sprite Table 1 (4-bytes per sprite)         
Byte 1:    xxxxxxxx    x: X coordinate
Byte 2:    yyyyyyyy    y: Y coordinate
Byte 3:    cccccccc    c: Starting tile #
Byte 4:    vhoopppc    v: vertical flip h: horizontal flip  o: priority bits
                       p: palette # c:sprite size (e.g. 8x8 or 16x16 pixel)

The x and y coordinates are self-explanatory, but there's one twist. There's an extra 9th bit for the x coord in the 2nd sprite table. The primary reason for this is to place unused sprites off the screen. With all 9 bits, the x coordinate can range from -256-255 ($100-$FF). $100 = -256, -1 = $1FF, etc. The starting tile # is the same thing as from the BG tutorial, same with vertical and horizontal flips.. Same with the priority bits.. and same with the palette #.

Sprite Table 2 (2 bits per sprite)
bits 0,2,4,6 - Enable or disable the X coordinate's 9th bit.
bits 1,3,5,7 - Toggle Sprite size: 0 - small size   1 - large size

The lower bits in each byte are properties for the lower sprite numbers. For example, bit 1 toggles the sprite size for sprite poop. bit 3 toggles the sprite size for sprite poop+1, and so on.

Are you understanding the whole sprite table thing? Talk to me if you don't, I'd be glad to help you.

The next thing to do is initialize the 2 sprite tables. What we will do is have a whole section of RAM act as a replica of OAM. This way, instead of loading different values a zillion times into a register and manually uploading to OAM, we can just change parts of this RAM section and DMA the block to OAM. This is also good because you can change the RAM when you're not in VBLANK, and then dma the data later. Otherwise you could only alter the OAM during VBLANK, and have to somehow store all those values with RAM!

Let's take a look at how we'll do this. We'll have a sprite initialization routine that will put all sprites off the screen. We could just set the X coord's MSB and that would put the sprite off the screen, but I am told that only setting the msb causes the sprite to be counted as being displayed, even though it's not. Because of this, we will just add by 1. So we will set the X coord's msb and also write a #$01 to the X coord byte, giving us #$101 as the X position (-255).

We will do this for every sprite to put them all off the screen. Take a look:

SpriteInit:
    php             ; preserve P reg

    rep #$30        ; 16bit A/X/Y

    ldx #$0000
    lda #$01        ; Prepare Loop 1
_offscreen:
    sta $0000, X
    inx
    inx
    inx
    inx
    cpx #$0200
    bne _offscreen
;------------------
    lda #$5555
_xmsb:
    sta $0000, X
    inx
    inx
    cpx #$0220
    bne _xmsb
;------------------

    plp
    RTS

Ok, now all the sprites are initialized to be off the screen. After calling your sprite init routine, it's time to personally set the settings for our sprite. Sprite 0's table goes from $0000-$0003, and $0200. We'll set our sprite to be in the middle of the screen.

lda #(256/2 - 16)
sta $0000           ; Sprite X-Coordinate

lda #(224/2 - 16)   ; Sprite Y-Coordinate
sta $0001

; $0002 and $0003 are already set to 0, which we want,
; (use palette 0, no sprite flip, and no priority - we're only displaying
; one thing on the screen anyways..

lda #%01010100  ; clear X-MSB
sta $0200

If you are curious of how I set the sprite to be in the middle of the screen, here's what the formula is, where 'screen'is equal to the width or height of the screen, and half_sprite is equal to half of the sprite's width of height:

(screen/2 - half_sprite)

Lastly, we will set some further sprite properties in our SetupVideo routine. But first, let's learn about some OAM registers.

Register $2101: OAM Size (1B/W)
sssnnbbb    s: Object Size (see table below)  n: name selection bits
            b: base selection bits      
                                               small    large   
            Object size table:          000     8x8     16x16
                                        001     8x8     32x32
                                        010     8x8     64x64
                                        011     16x16   32x32
                                        100     16x16   64x64
                                        101     32x32   64x64

This register has multiple uses. Let me explain.

  • The base location bits set the base location (lower $1000 words of $2000) in VRAM where tile data is stored.
  • The name selection bits page the upper $1000 words (of $2000) area in VRAM where tile data is stored. You don't really need to mess with this yet, so just leave these bits to 0 in order to have a linear 16 Kilobytes of tile data.
  • Set the 2 possible sprite sizes that can be displayed on the screen with the Object Size bits.

We will also have to upload our RAM copy of OAM to OAM. We need to set the OAM address registers.

Register $2102/$2103: Address for accessing OAM (2B/W)
aaaaaaaa r------m   a: Low word of OAM address
                    r: OAM priority rotation    m: OAM address MSB

This register selects the word (I think Qwertie was incorrect saying byte) address to begin uploading to OAM. Setting the OAM address MSB allows you to access the second sprite table, and not having it set gives access to first sprite table. We'll usually just set the address to 0, since we are uploading a whole copy of OAM.

If you set the OAM priority rotation bit, the sprite(s?) for the address set you set for the OAM address will have the highest priority.

Also, whatever you set to these registers will be automatically set again during VBLANK (I have not tested this yet though).

You will write data to the OAM through register $2104

Register $2104: Data Write to OAM (1B/W)
dddddddd    d: byte to write to OAM

This register writes a byte to OAM. You must write the bytes in the order of Low and High. Once the low and high bytes have been written, the OAM address will be incremented.

Ok, let's utilize these registers. What we will do is set register $2101 to our liking, and then dma our copy of OAM to OAM. Here is how that will look:

SetupVideo:
    rep #$10
    sep #$20            ; 8bit A, 16bit X/Y

    ; DMA sprite data
    stz $2102
    stz $2103           ; Set OAM address to 0

    ldy #$0400          ; Writes #$00 to $4300, #$04 to $4301
    sty $4300           ; CPU -> PPU, auto inc, $2104 (OAM write)
    stz $4302
    stz $4303
    lda #$7E
    sta $4304           ; CPU address 7E:0000 - Work RAM
    ldy #$0220
    sty $4305           ; #$220 bytes to transfer
    lda #$01
    sta $420B

    lda #%10100000      ; 32x32 and 64x64 size sprites (we are using a 32x32)
    sta $2101

    lda #%00010000      ; Enable Sprites
    sta $212C

    lda #$0F
    sta $2100           ; Turn on screen, full brightness

    RTS

That's the FULL SetupVideo routine. I suppose I did not need to give the full version, but... whatever. Ok, is that it?

That's it people. All that needs to be done is put all of this code together.

Note: There is one small error in the source code below: The source mentions "Enable BG1", while it really means "Enable OBJ" <=> enable sprites. Please see documentation for register $212C.

Complete Source Code: snes-sprite-tutorial.7z

(Note: I couldn't get this running on the latest WLA so I modified some things: snes-sprite-tutorial-jeffy.7z - jeffythedragonslayer)

On to Polling Controller Input!

Tutorial by bazz