diff options
Diffstat (limited to 'scroll/mmc3.s')
| -rw-r--r-- | scroll/mmc3.s | 382 |
1 files changed, 381 insertions, 1 deletions
diff --git a/scroll/mmc3.s b/scroll/mmc3.s index 3f97a52..a498977 100644 --- a/scroll/mmc3.s +++ b/scroll/mmc3.s @@ -1 +1,381 @@ -;; TODO: Through MMC3 +;;; +;; The same as in `level.s` but at the bottom of the screen we have a "This is a +;; message" being shown. This message is part of the background but it does not +;; scroll like the rest of the screen, but it stays at the same coordinates all +;; the time. This is done via the MMC3 chip, and the same technique is further +;; developed in `roulette.s`. In short, this mapper chip implements a bunch of +;; features, and one of them is the ability to instruct the chip to send an IRQ +;; on a given scanline. This is then done to mess with the scroll value and +;; obtain different effects. +;; +;; This example shows the most basic usage of it, and it's what games like Super +;; Mario Bros. 3 and Kirby's Adventure did: implement a status bar at the bottom +;; of the screen in a way that is reliable and less CPU consuming than sprite 0 +;; hit detection as it's done in `sprite0.s`. That is, instead of wasting CPU +;; cycles waiting for a sprite 0 hit, during VBlank we configure the chip to +;; send us an IRQ for a given scanline. Once the PPU arrives at this scanline, +;; then it sends us an IRQ in which we reset the scroll value. This scroll value +;; will then be configured again during VBlank. +;; +;; The code is a mix between `level.s` and `fx/blink.s`, so consider these two +;; examples as previous work and get to know them before jumping into this one. +;; In here I have just written comments which are specific to this example. + +;; Just like `fx/blink.s`. +.segment "HEADER" + .byte 'N', 'E', 'S', $1A + .byte $10, $10 + .byte $42, $08 + .res 8, 0 + +.segment "VECTORS" + .addr nmi, reset, irq + +;; Just like `fx/blink.s`: empty on purpose as this is a simple example. + +.segment "PRG0_00" +.byte $FF +.segment "PRG0_01" +.byte $FF +.segment "PRG0_02" +.byte $FF +.segment "PRG0_03" +.byte $FF +.segment "PRG0_04" +.byte $FF +.segment "PRG0_05" +.byte $FF +.segment "PRG0_06" +.byte $FF +.segment "PRG0_07" +.byte $FF +.segment "PRG0_08" +.byte $FF +.segment "PRG0_09" +.byte $FF +.segment "PRG0_0A" +.byte $FF +.segment "PRG0_0B" +.byte $FF +.segment "PRG0_0C" +.byte $FF +.segment "PRG0_0D" +.byte $FF +.segment "PRG0_0E" +.byte $FF +.segment "PRG1_00" +.byte $FF +.segment "PRG1_01" +.byte $FF +.segment "PRG1_02" +.byte $FF +.segment "PRG1_03" +.byte $FF +.segment "PRG1_04" +.byte $FF +.segment "PRG1_05" +.byte $FF +.segment "PRG1_06" +.byte $FF +.segment "PRG1_07" +.byte $FF +.segment "PRG1_08" +.byte $FF +.segment "PRG1_09" +.byte $FF +.segment "PRG1_0A" +.byte $FF +.segment "PRG1_0B" +.byte $FF +.segment "PRG1_0C" +.byte $FF +.segment "PRG1_0D" +.byte $FF +.segment "PRG1_0E" +.byte $FF +.segment "FIXED" +.byte $FF + +;; Everything happens on this segment. Check the `config/mmc3.cfg` for more +;; information on where it is placed in the end. +.segment "TAIL" + +;; Just like with `sprite0.s`, the engine in `include/` can be configured to a +;; degree. In this case we instruct it to never go over the `$0D` row as this +;; will be the one being used for showing the status bar. +BACKGROUND_ROW_MAX = $0D +.include "include/all.s" +.include "../shared/mmc3.s" + +;; Clear out the a rows of tiles from the high byte for the PPU address as given +;; in the `x` register, and the low byte as given on the `y` register. +.proc clear_row_x_y + bit PPU::STATUS + + stx PPU::ADDRESS + sty PPU::ADDRESS + + lda #$00 + ldx #$20 +@loop: + sta PPU::DATA + dex + bne @loop + + rts +.endproc + +;; Similar to `show_hud` in `sprite0.s`, we want to allocate this text in the +;; background where the engine will not touch it. +.proc show_status + ;; Clear out the space in which we want to allocate or status bar. + ldx #$23 + ldy #$40 + jsr clear_row_x_y + ldx #$23 + ldy #$60 + jsr clear_row_x_y + ldx #$23 + ldy #$80 + jsr clear_row_x_y + ldx #$27 + ldy #$40 + jsr clear_row_x_y + ldx #$27 + ldy #$60 + jsr clear_row_x_y + ldx #$27 + ldy #$80 + jsr clear_row_x_y + + ;; Just like with `sprite0.s`, the message has to be repeated over the + ;; nametable on $2400 as the engine will flip the base nametable address + ;; whenever the scroll wraps around. + + ;; This + WRITE_PPU_DATA $2368, $23 + WRITE_PPU_DATA $2369, $17 + WRITE_PPU_DATA $236A, $18 + WRITE_PPU_DATA $236B, $22 + WRITE_PPU_DATA $2768, $23 + WRITE_PPU_DATA $2769, $17 + WRITE_PPU_DATA $276A, $18 + WRITE_PPU_DATA $276B, $22 + + ;; is + WRITE_PPU_DATA $236D, $18 + WRITE_PPU_DATA $236E, $22 + WRITE_PPU_DATA $276D, $18 + WRITE_PPU_DATA $276E, $22 + + ;; a + WRITE_PPU_DATA $2370, $10 + WRITE_PPU_DATA $2770, $10 + + ;; message + WRITE_PPU_DATA $2372, $1C + WRITE_PPU_DATA $2373, $14 + WRITE_PPU_DATA $2374, $22 + WRITE_PPU_DATA $2375, $22 + WRITE_PPU_DATA $2376, $10 + WRITE_PPU_DATA $2377, $16 + WRITE_PPU_DATA $2378, $14 + WRITE_PPU_DATA $2772, $1C + WRITE_PPU_DATA $2773, $14 + WRITE_PPU_DATA $2774, $22 + WRITE_PPU_DATA $2775, $22 + WRITE_PPU_DATA $2776, $10 + WRITE_PPU_DATA $2777, $16 + WRITE_PPU_DATA $2778, $14 + + ;; NOTE: in stark contrast with `sprite0.s`, there's no need to waste a + ;; sprite for this purpose. + + rts +.endproc + +.proc main + ;; Setup the MMC3 chip. Note that this is better suited in the `reset` + ;; function, but the engine already provides one and I didn't want to start + ;; messing with `.ifdef` and the likes. + ;; + ;; NOTE: this is a copy-paste from `fx/blink.s`, so refer to that example on + ;; what any of the code below means. + + lda #$00 + sta MMC3::MIRRORING + sta MMC3::IRQ_DISABLE + + lda #$80 + sta MMC3::RAM_PROTECT + + BANK_REGISTER_SET 0, 0 + BANK_REGISTER_SET 1, 2 + BANK_REGISTER_SET 2, 4 + BANK_REGISTER_SET 3, 5 + BANK_REGISTER_SET 4, 6 + BANK_REGISTER_SET 5, 7 + BANK_REGISTER_SET 6, 0 + BANK_REGISTER_SET 7, 1 + + ;; MMC3 configured, now go on as usual. + + lda #$00 + sta PPU::zp_mask + sta PPU::MASK + + jsr Palettes::init + jsr Metatile::init + jsr Driver::init + jsr Background::init + jsr Player::init + + ;; Show the status bar down below. + jsr show_status + + cli + + lda #%10001000 + sta PPU::zp_control + sta PPU::CONTROL + +@main_game_loop: + READ_JOYPAD1 + jsr Player::update + jsr Driver::update + + lda #%10000000 + ora Globals::zp_flags + sta Globals::zp_flags + +@wait_for_render: + bit Globals::zp_flags + bmi @wait_for_render + + jmp @main_game_loop +.endproc + +;; The basics are laid out in `basics/sprite.s`. Read the comments below for +;; more info. +.proc nmi + bit Globals::zp_flags + bpl @next + + pha + txa + pha + tya + pha + + ;; Just like in `fx/blink.s`, acknowledge any previous IRQ as a safety + ;; measure. But anyways set the next IRQ to happen on scanline 210. + + ldx #$00 + stx MMC3::IRQ_DISABLE + + lda #210 + sta MMC3::IRQ_LATCH + sta MMC3::IRQ_RELOAD + sta MMC3::IRQ_ENABLE + + ;; From here on as in `level.s`. + + jsr Player::update_sprite + + OAM_WRITE_SPRITES + + FLUSH_PENDING_VRAM_BUFFER + + bit Globals::zp_flags + bvc @after_ppu + + lda #%10111111 + and Globals::zp_flags + sta Globals::zp_flags + + bit PPU::STATUS + + lda PPU::zp_control + sta PPU::CONTROL + lda PPU::zp_mask + sta PPU::MASK + +@after_ppu: + ;; NOTE: scroll is updated always. This is in contrast with `level.s`, but + ;; if we don't do that the scroll would be lost if the player stops moving + ;; the scroll position. + lda Background::zp_scroll + sta PPU::SCROLL + lda #$00 + sta PPU::SCROLL + + ;; And as usual again. + + lda #%01111111 + and Globals::zp_flags + sta Globals::zp_flags + + pla + tay + pla + tax + pla +@next: + rti +.endproc + +;; This is a much simpler version of IRQ handling as you can see in examples +;; such as `roulette.s`. +.proc irq + ;; Save current context. + pha + txa + pha + tya + pha + + ;; Disable IRQs and acknowledge the current one. + ldx #$00 + stx MMC3::IRQ_DISABLE + + ;; Reset the scroll so the status bar is kept in place. The scroll will be + ;; kept like this until VBlank happens, when the `nmi` function will set the + ;; scroll value to what's perceived by the player. + bit PPU::STATUS + lda #$00 + sta $2005 + sta $2005 + + ;; Restore previous context. + pla + tay + pla + tax + pla + + rti +.endproc + +.segment "CHARS" +;; Similar to `fx/blink.s` but here we really needed something for the +;; background :) +.incbin "../assets/diskun-background.chr" +.incbin "../assets/diskun0.chr" +.incbin "../assets/diskun1.chr" + +;; The 15 other 8KB portions are left empty. +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 +.res $2000, $00 |
