Drawing sprites lab_4
authorRhett Aultman <roadriverrail@gmail.com>
Mon, 1 Oct 2018 15:21:24 +0000 (11:21 -0400)
committerRhett Aultman <roadriverrail@gmail.com>
Mon, 1 Oct 2018 15:27:11 +0000 (11:27 -0400)
This demo shows how to use the Object Attribute Memory (OAM), a part of
the PPU memory space dedicated to working with sprites.  Additionally,
this demo uses the OAM DMA technique for trasferring a full OAM table
from RAM into the PPU memory space, since this is the most common
technique used by NES game programmers.

Makefile
src/background.asm [deleted file]
src/sprite.asm [new file with mode: 0644]

index a8a75eaf0871892405553ffd918cf612d06a2857..39b6bf3ecdd499c26f4d73cd75fee864e5c29b01 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 SRCDIR = src
 OBJDIR = obj
 BINDIR = bin
-TARGET = background.nes
+TARGET = sprite.nes
 
 SOURCES := $(wildcard $(SRCDIR)/*.asm)
 OBJECTS := $(SOURCES:$(SRCDIR)/%.asm=$(OBJDIR)/%.o)
diff --git a/src/background.asm b/src/background.asm
deleted file mode 100644 (file)
index bf6afc3..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-; Nametables and attribute tables Demo for NES
-; Following on from the background palette program, this program looks at how
-; to load a pattern table, which contains the actual graphic tiles, a nametable,
-; which contains the layout of graphic tiles for the background, and the
-; attribute table, which specifies what palette to paint everything with.
-;
-; I am not an artist, and even then, converting bitmaps to pattern tables can be
-; a chore.  So, I used the "Generitiles" from the NESDev wiki
-; (https://wiki.nesdev.com/w/index.php/Placeholder_graphics).  I then ran these
-; through a Python script supplied by Damian Yerrick 
-; (https://github.com/pinobatch/nesbgeditor) which converts a 2-bit PNG into
-; pattern tables pretty darn effectively.  Thanks, Damian!
-; 
-; So, we're now *finally* using the CHARS section, which gets directly mapped
-; into the PPU's memory at power on.  This section really should more
-; be named "CHR-ROM", as this is the more common name for it.  You'll notice
-; that I can directly include the file produced by Damian's tools, which keeps
-; the code tidy.
-;
-; With the patterns directly mapped in, the next step is to load up some data
-; in the name table.  Since we're not doing anything fancy, I've restricted
-; things to the first of the two name tables.  Much like we did for loading the
-; palette colors, we load this through the use of PPUADDR and PPUDATA.
-;
-; Finally, we load the attribute table, which says which palette to use for
-; each 32x32 pixel region on the screen.  In a more advanced demo, this would
-; raise the number of effective colors I was using on the screen.  For now,
-; though, I just want to keep things simple and easy-to-explain.
-;
-; Note that the annyoing "hello world" sound is now gone.  The graphics show
-; that everything is working.
-
-.define PPUMASK      $2001
-.define PPUSTATUS    $2002
-.define PPUADDR      $2006
-.define PPUSCROLL    $2005
-.define PPUDATA      $2007
-
-.define NAMETABLE_0_HI $20
-.define NAMETABLE_0_LO $00
-.define ATTRTABLE_0_HI $23
-.define ATTRTABLE_0_LO $C0
-.define BGPALETTE_HI   $3F
-.define BGPALETTE_LO   $00
-
-; Mandatory iNES header.
-.segment "HEADER"
-
-.byte "NES", $1A ; "NES" magic value
-.byte 2          ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2)
-.byte 1          ; number of 8KB "char" data pages
-.byte $00        ; "mapper" and bank-switching type (0 for "none")
-.byte $00        ; background mirroring flats
-                 ;
-                 ; Note the header is 16 bytes but the nes.cfg will zero-pad for us.
-
-; code ROM segment
-; all code and on-ROM program data goes here
-
-.segment "STARTUP"
-
-; reset vector
-reset:
-  bit PPUSTATUS  ; clear the VBL flag if it was set at reset time
-vwait1:
-  bit PPUSTATUS
-  bpl vwait1     ; at this point, about 27384 cycles have passed
-vwait2:
-  bit PPUSTATUS
-  bpl vwait2     ; at this point, about 57165 cycles have passed
-
-  ; Interesting little fact I learned along the way.  Because it takes two
-  ; stores on PPUADDR to move its pointer, it's good practice to start all of
-  ; your PPUADDR use with a peek at PPUSTATUS since this resets its "latch"
-  ; and ensures you're addressing the address you expect.
-  ; Technically, we don't need this because we did it in the reset code, but
-  ; it's a neat little thing to mention here
-
-  bit PPUSTATUS
-
-  ; load the background palette
-  lda #BGPALETTE_HI
-  sta PPUADDR
-  lda #BGPALETTE_LO
-  sta PPUADDR
-
-  ; prep the loop
-  ldx #0
-
-paletteloop:
-  lda bgpalette, X ; load from the bgpalette array
-  sta PPUDATA      ; store in PPUDATA, PPU will auto-increment
-  inx              ; increment the X (index) register
-  cpx #16
-  bne paletteloop  ; run the loop until X=16 (size of the palettes)
-
-; move PPUADDR over to nametable 0. 
-  lda #NAMETABLE_0_HI
-  sta PPUADDR
-  lda #NAMETABLE_0_LO
-  sta PPUADDR
-
-; This loop iterates over the pattern table, outputting it in lines of 16
-; The other 16 are just padded out with a pattern that's blank.  This lets
-; me easily show you some simple graphics that are made up of multiple
-; stacked tiles without getting too fancy.  In reality, you'd probably have
-; complete nametables that you'd load in from files and simply run in a loop.
-
-  ldx #$00
-  ldy #16
-nametableloop:
-  stx PPUDATA
-  inx
-  dey
-  bne nametableloop
-  ldy #16
-padright:
-  sta PPUDATA
-  dey
-  bne padright
-  cpx $D0
-  beq done
-  ldy #16
-  jmp nametableloop
-done:
-  
-; set up Palette 0 for everything
-  bit PPUSTATUS
-  lda #ATTRTABLE_0_HI
-  sta PPUADDR
-  lda #ATTRTABLE_0_LO
-  sta PPUADDR
-  ldx #64 ; 64 tiles in the attribute table
-  lda #0
-
-attrloop:
-  sta PPUDATA
-  dex
-  bne attrloop
-
-; Enable background and sprite rendering.  This is suuuuuper important to
-; remember.  I didn't remember to put this in and probably blew a whole day
-; trying to figure out why my emulator hated me.
-  lda #$1e
-  sta PPUMASK
-
-forever:
-  jmp forever
-
-nmi:
-  rti ; Return from the NMI (NTSC refresh interrupt)
-
-
-; The background colors are, in order:
-; $0F: black
-; $15: red
-; $22: blue
-; $20: white
-
-bgpalette:
-  .byte $0F, $15, $22, $20 ; palette 0
-  .byte $0F, $15, $22, $20 ; palette 1
-  .byte $0F, $15, $22, $20 ; palette 2
-  .byte $0F, $15, $22, $20 ; palette 3
-
-
-; vectors declaration
-.segment "VECTORS"
-.word nmi
-.word reset
-.word 0
-
-; As mentioned above, this is the place where you put your pattern table data
-; so that it can automatically be mapped into the PPU's memory at $0000-$1FFF.
-; Note the use of .incbin so I can just import a binary file.  Neato!
-
-.segment "CHARS"
-.incbin "generitiles.pat"
diff --git a/src/sprite.asm b/src/sprite.asm
new file mode 100644 (file)
index 0000000..ef9a4e2
--- /dev/null
@@ -0,0 +1,259 @@
+; Drawing static sprites
+; Now that we can confirm the palettes, pattern table, and nametable loading
+; are all working, we can work with the next part of the NES PPU-- the sprite
+; subsystem.
+;
+; Sprites are controlled by the Object Attribute Memory (OAM).  There are 64
+; available entries in the OAM, with each specifying the coordinates and
+; pattern for the sprite.  Sprites may be layered in front of or behind the
+; background, too (see Super Mario Bros 3 for a classic example of behind-the-
+; background sprite use).
+;
+; Technically, you can write to the OAM using addresses $2003 and $2004, called
+; OAMADDR and OAMDATA, but this is not commonly done because address $4014
+; manages a direct memory access (DMA) controller that can copy a complete OAM
+; table from the page of your choice in memory.  By convention, most NES coders
+; use $0200, so writing 2 to $4014 triggers a dump into the PPU's OMA table.
+; The CPU is stalled during this transfer, so you need only make the write and
+; then carry on with the code.
+
+.define SPRITE_PAGE  $0200
+
+.define PPUMASK      $2001
+.define PPUSTATUS    $2002
+.define PPUADDR      $2006
+.define PPUSCROLL    $2005
+.define PPUDATA      $2007
+
+.define OAM_DMA      $4014
+
+.define OAM_PAGE     2
+
+.define NAMETABLE_0_HI $20
+.define NAMETABLE_0_LO $00
+.define ATTRTABLE_0_HI $23
+.define ATTRTABLE_0_LO $C0
+.define BGPALETTE_HI   $3F
+.define BGPALETTE_LO   $00
+
+; Mandatory iNES header.
+.segment "HEADER"
+
+.byte "NES", $1A ; "NES" magic value
+.byte 2          ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2)
+.byte 1          ; number of 8KB "char" data pages
+.byte $00        ; "mapper" and bank-switching type (0 for "none")
+.byte $00        ; background mirroring flats
+                 ;
+                 ; Note the header is 16 bytes but the nes.cfg will zero-pad for us.
+
+; code ROM segment
+; all code and on-ROM program data goes here
+
+.segment "STARTUP"
+
+; reset vector
+reset:
+  bit PPUSTATUS  ; clear the VBL flag if it was set at reset time
+vwait1:
+  bit PPUSTATUS
+  bpl vwait1     ; at this point, about 27384 cycles have passed
+vwait2:
+  bit PPUSTATUS
+  bpl vwait2     ; at this point, about 57165 cycles have passed
+
+  ; Interesting little fact I learned along the way.  Because it takes two
+  ; stores on PPUADDR to move its pointer, it's good practice to start all of
+  ; your PPUADDR use with a peek at PPUSTATUS since this resets its "latch"
+  ; and ensures you're addressing the address you expect.
+  ; Technically, we don't need this because we did it in the reset code, but
+  ; it's a neat little thing to mention here
+
+  bit PPUSTATUS
+
+  ; load the palettes
+  lda #BGPALETTE_HI
+  sta PPUADDR
+  lda #BGPALETTE_LO
+  sta PPUADDR
+
+  ; prep the loop
+  ldx #0
+
+; the palette loop now loads 8 palettes; 4 are for the background
+; tiles and 4 are for sprites.  Before working with sprites, you
+; must set sprite palette colors, or you'll have a bad time
+
+paletteloop:
+  lda bgpalette, X ; load from the bgpalette array
+  sta PPUDATA      ; store in PPUDATA, PPU will auto-increment
+  inx              ; increment the X (index) register
+  cpx #32
+  bne paletteloop  ; run the loop until X=32 (size of the palettes)
+
+; move PPUADDR over to nametable 0. 
+  lda #NAMETABLE_0_HI
+  sta PPUADDR
+  lda #NAMETABLE_0_LO
+  sta PPUADDR
+
+; set up Palette 0 for everything
+  bit PPUSTATUS
+  lda #ATTRTABLE_0_HI
+  sta PPUADDR
+ lda #ATTRTABLE_0_LO
+  sta PPUADDR
+  ldx #64 ; 64 tiles in the attribute table
+  lda #0
+
+attrloop:
+  sta PPUDATA
+  dex
+  bne attrloop
+
+; Now for the meat of this lab-- making a sprite.  This makes a
+; basic character using tiles we already have at hand from last lab's
+; pattern table.  Note that the character is made up of six 8x8 tiles
+; and thus it takes six entries in the OAM to draw him.  It's common
+; to think of "sprite" as "character," but on this early hardware,
+; all sprites are fixed sizes expressed in either 8x8 or 8x16 tiles.
+; So those 64 sprites don't go as far as it might seem!
+
+; zero out the OAM DMA shadow page
+  ldx #$FF
+  lda $0
+zero_oam:
+  sta SPRITE_PAGE, X
+  dex
+  bne zero_oam
+
+; refresh our index register...we're going to make heavy use of it
+; now...
+
+  ldx #0
+
+; head
+  lda #$7F             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$A0             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$7F             ; X coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$7F             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$A1             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$87             ; X coordinate
+  sta SPRITE_PAGE, X
+  inx
+; torso
+  lda #$87             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$B0             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$7F             ; X coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$87             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$B1             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$87             ; X coordinate
+  sta SPRITE_PAGE, X
+  inx
+; feet
+  lda #$8F             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$C0             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$7F             ; X coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$8F             ; Y coordinate
+  sta SPRITE_PAGE, X
+  inx
+  lda #$C1             ; Pattern bank 0, tile A0 (A1 is bottom)
+  sta SPRITE_PAGE, X
+  inx
+  lda #$00             ; No flipping, in front of background, palette 0  
+  sta SPRITE_PAGE, X
+  inx
+  lda #$87             ; X coordinate
+  sta SPRITE_PAGE, X
+
+; OAM DMA must always be a transfer from address XX00-XXFF, so we write
+; the value of XX (in this case, 2) to OAM_DMA ($4014) to trigger the
+; transfer
+
+  lda #OAM_PAGE
+  sta OAM_DMA 
+
+; Enable background and sprite rendering.
+  lda #$1e
+  sta PPUMASK
+
+
+
+forever:
+  jmp forever
+
+nmi:
+  rti ; Return from the NMI (NTSC refresh interrupt)
+
+
+; The background colors are, in order:
+; $0F: black
+; $15: red
+; $22: blue
+; $20: white
+
+bgpalette:
+  .byte $0F, $15, $22, $20 ; palette 0
+  .byte $0F, $15, $22, $20 ; palette 1
+  .byte $0F, $15, $22, $20 ; palette 2
+  .byte $0F, $15, $22, $20 ; palette 3
+spritepalette:
+  .byte $0F, $07, $19, $20 ; palette 0
+  .byte $0F, $07, $19, $20 ; palette 1
+  .byte $0F, $07, $19, $20 ; palette 2
+  .byte $0F, $07, $19, $20 ; palette 3
+
+
+; vectors declaration
+.segment "VECTORS"
+.word nmi
+.word reset
+.word 0
+
+; As mentioned above, this is the place where you put your pattern table data
+; so that it can automatically be mapped into the PPU's memory at $0000-$1FFF.
+; Note the use of .incbin so I can just import a binary file.  Neato!
+
+.segment "CHARS"
+.incbin "generitiles.pat"