From 475b45d206da33544c63c570465f6811c706eb32 Mon Sep 17 00:00:00 2001 From: Rhett Date: Sun, 21 Oct 2018 13:21:57 -0400 Subject: [PATCH] Animating sprites This demo uses the existing "walk cycle animation" available in the loaded pattern table to make a sprite animate in place. To achieve this, we do a few different things: * To keep track of what cell/frame to animate and where to put the sprite, we bring in some RAM in the "zero page". * To organize pattern table references into coherent frames of animations, we declare an "array" of them called "anim" * We move the loading of sprite tiles into OAM into a subroutine, making this the first case of using a subroutine * We implement a drawing process in the handler for the non-maskable interrupt (NMI). This not only tells us when it's time to draw but also lets us have a sense of how much time has elapsed so we can decide when to change frames of animation. That's a lot to digest, but the good news is that this is rapidly progressing towards being an actual animation engine! --- Makefile | 2 +- src/.sprite.asm.swp | Bin 0 -> 32768 bytes src/sprite.asm | 302 +++++++++++++++++++++++++++----------------- 3 files changed, 187 insertions(+), 117 deletions(-) create mode 100644 src/.sprite.asm.swp diff --git a/Makefile b/Makefile index 39b6bf3..6a1f91f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ SRCDIR = src OBJDIR = obj BINDIR = bin -TARGET = sprite.nes +TARGET = anim.nes SOURCES := $(wildcard $(SRCDIR)/*.asm) OBJECTS := $(SOURCES:$(SRCDIR)/%.asm=$(OBJDIR)/%.o) diff --git a/src/.sprite.asm.swp b/src/.sprite.asm.swp new file mode 100644 index 0000000000000000000000000000000000000000..fa723414093ae1bf290ddd1c05204b72adccd8b5 GIT binary patch literal 32768 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 D0f#QD literal 0 HcmV?d00001 diff --git a/src/sprite.asm b/src/sprite.asm index ef9a4e2..efe9317 100644 --- a/src/sprite.asm +++ b/src/sprite.asm @@ -1,24 +1,58 @@ -; 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. +; Drawing sprite animation ; -; 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). +; Since we previously drew a sprite by allocating its 6 tiles in the Object +; Attribute Memory (OAM), this time we get fancy and make it animate a little. +; Specifically, I'm using the 4-cell walk animation for the little person +; that was already laying around in the pattern table. ; -; 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. +; There are, assurredly, more efficient ways to implement this, but this one +; was coded up with the layout of the pattern table in mind. The pattern +; table layout is really more of the "easy to reason about" sort rather than +; one chosen for tighter code. +; +; In order to load the sprite, we're now using a subroutine called +; "load_sprite" which knows how to convert the current frame number and an +; initial x and y coordinate and load a 2x3 tile sprite from them. You'll +; notice that this information is basically "passed" using "global variables." +; The 6502 has limited registers making the use of the stack for parameter +; passing pretty tricky. Effective parameter passing is probably a lab on +; its own, so I skipped it. +; +; Because the pattern table entries needed to draw the sprite correctly are +; not arranged in a linear way, I created a little array called "anim" which +; describes each frame of the animation in a way that's coherent to the +; algorithm used in load_sprite. It takes 6 pattern table entries to draw +; the character, so advancing to the next step in the animation is a matter +; of adding different multiples of 6 to "anim". +; +; To animate, you must change the image as time progresses, meaning you also +; need a timer. This is where the other major point of this lab comes in-- +; the non-maskable interrupt (NMI). The NMI fires every time the PPU starts +; drawing another frame on the TV screen. This gives us a "heartbeat" for our +; code and also serves as a general sense of time. NTSC refreshes 60 fields +; (half-frames) per second, so we know that each trigger of the NMI is 1/60th +; of a second, and we can therefore decide how long to devote to each part +; of an animation. +; +; Also kindly note that we go ahead and do the OAM DMA immediately at the +; beginning of the NMI hander. This is because the NMI signals the beginning +; of something called "vertical blanking" in the NTSC and PAL standards. The +; OAM must be ready to go at end of vertical blanking so the image can be put +; on the screen, so we do it first and make sure we don't delay. After that, +; we set up the next frame of animation. +; +; Finally...this is the first lab where we need RAM in order to track changing +; variables! Astute observers might have noticed that all the memory we've +; declared up to this point has been RAM. Since the variables we need are few, +; I've declared them in the "zero page", which is a RAM region already +; made available by the cc65 default NES configuration. This is all a fancy +; way of saying that the first 256 bytes of addressable space are RAM, and +; because they're the first 256 bytes, the 6502 can fetch them very quickly. + .define SPRITE_PAGE $0200 +.define PPUCTRL $2000 .define PPUMASK $2001 .define PPUSTATUS $2002 .define PPUADDR $2006 @@ -47,6 +81,20 @@ ; ; Note the header is 16 bytes but the nes.cfg will zero-pad for us. +; "zero page" RAM +; This is where we're storing our mutable state. +.segment "ZEROPAGE" +current_frame: + .byte 0 +sprite_x: + .byte 0 +sprite_y: + .byte 0 +frame_count: + .byte 0 + + + ; code ROM segment ; all code and on-ROM program data goes here @@ -62,28 +110,24 @@ 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 +; 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 +; load the palettes lda #BGPALETTE_HI sta PPUADDR lda #BGPALETTE_LO sta PPUADDR - ; prep the loop +; 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 @@ -111,14 +155,6 @@ attrloop: 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 @@ -127,104 +163,119 @@ zero_oam: dex bne zero_oam -; refresh our index register...we're going to make heavy use of it -; now... +; Set up the sprite's base x and y coordinates +; and frame index (i.e. where in the animation we are) +; and then call load_sprite to do the hard work - ldx #0 + lda #$7F + sta sprite_x + sta sprite_y + lda #0 + sta current_frame + lda #0 + sta frame_count +; Load the sprite + jsr load_sprite -; 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 +; Enable background and sprite rendering. + lda #$1e + sta PPUMASK + +; generate NMI + lda #$80 + sta PPUCTRL + +forever: + jmp forever + +nmi: +; 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 + +; Here we keep a count of the NMIs as they come in. Until we count 15 +; of them, which is roughly a quarter second, just keep holding on the +; existing sprite. + inc frame_count + lda frame_count + cmp #15 + bne done + lda #0 + sta frame_count + +; We counted 15 NMIs, so let's update the animation frame and +; make the little character walk in place + lda current_frame + clc ; NEVER forget to clear the carry flag before adding + adc #6 ; Each frame is an offset of a multiple of 6 + cmp #24 ; After 4 frames, wrap around (4*6 = 24) + bne dont_cycle_anim + lda #0 +dont_cycle_anim: + sta current_frame +done: + jsr load_sprite + rti ; Return from the NMI (NTSC refresh interrupt) + + + +; load_sprite consults current_frame to determine the offset into anim +; and then draws the data in that row of anim into a 2x3 rectangle +.proc load_sprite + ldx #0 + ldy current_frame + lda #$7F + sta sprite_x + lda #$7F + sta sprite_y +load_loop: +; First of two cells + lda sprite_y sta SPRITE_PAGE, X inx -; feet - lda #$8F ; Y coordinate + lda anim, Y + iny sta SPRITE_PAGE, X inx - lda #$C0 ; Pattern bank 0, tile A0 (A1 is bottom) + lda #$00 sta SPRITE_PAGE, X inx - lda #$00 ; No flipping, in front of background, palette 0 + lda sprite_x sta SPRITE_PAGE, X + clc + adc #7 ; move to right cell + sta sprite_x inx - lda #$7F ; X coordinate +; Second of two cells + lda sprite_y sta SPRITE_PAGE, X + clc + adc #7 + sta sprite_y inx - lda #$8F ; Y coordinate + lda anim, Y + iny sta SPRITE_PAGE, X inx - lda #$C1 ; Pattern bank 0, tile A0 (A1 is bottom) + lda #$00 sta SPRITE_PAGE, X inx - lda #$00 ; No flipping, in front of background, palette 0 + lda sprite_x sta SPRITE_PAGE, X + sbc #7 ; return to the left cell + sta sprite_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) +;; Loop if we haven't loaded the full sprite + cpx #24 + bne load_loop + lda sprite_y + sbc #14 + sta sprite_y + rts +.endproc ; The background colors are, in order: @@ -238,12 +289,31 @@ bgpalette: .byte $0F, $15, $22, $20 ; palette 1 .byte $0F, $15, $22, $20 ; palette 2 .byte $0F, $15, $22, $20 ; palette 3 + +; The foreground/sprite colors are: +; $0F: black +; $07: dark brown +; $19: drab green +; $20: white + 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 +; This describes each "frame" or "cell" of the walk animation. I decided to +; write out an animation table rather than alter the pattern table. This +; means that load_sprite is a little less efficient than it probably ought be, +; but makes the pattern table easier to visually think about in a debugger +; like fceux. Each byte here is an address in the pattern table; you'll +; recognize them from the previous lab. + +anim: + .byte $A0, $A1, $B0, $B1, $C0, $C1 ; frame 1 + .byte $A2, $A3, $B2, $B3, $C2, $C3 ; frame 2 + .byte $A4, $A5, $B4, $B5, $C4, $C5 ; frame 3 + .byte $A6, $A7, $B6, $B7, $C6, $C7 ; frame 4 ; vectors declaration .segment "VECTORS" -- 2.34.1