From 9ff2033e936689135210989a5fee057a4a13527e Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Thu, 13 Mar 2025 22:52:49 +0100 Subject: Add a title and a main screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the skeleton for having a title and a main screen. For now the title menu doesn't do much, as the selection is simply ignored, but at least it already knows how to cycle between these two states. Signed-off-by: Miquel Sabaté Solà --- src/assets.s | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/driver.s | 28 ++++++++++++ src/jetpac.s | 113 +++++++++++++++++++++++----------------------- src/title.s | 100 +++++++++++++++++++++++++++++++++++++++++ src/vectors.s | 27 +++++++++++ 5 files changed, 355 insertions(+), 55 deletions(-) create mode 100644 src/assets.s create mode 100644 src/driver.s create mode 100644 src/title.s (limited to 'src') diff --git a/src/assets.s b/src/assets.s new file mode 100644 index 0000000..8e839d3 --- /dev/null +++ b/src/assets.s @@ -0,0 +1,142 @@ +.segment "CODE" + +;; The game is so simple that it can fit in both nametables. This scope has all +;; the functions and data that initializes and resets all of them properly. +.scope Assets + ;; Initialize palettes and nametables. + .proc init + jsr init_palettes + + ;; Load the title screen on the first nametable. + lda #.lobyte(title_screen) + sta Globals::zp_arg0 + lda #.hibyte(title_screen) + sta Globals::zp_arg1 + ldx #$20 + jsr load_screen_x + + ;; Ensure that the title looks as expected. + jsr prepare_for_title_screen + + ;; Load the main screen on the second nametable. + lda #.lobyte(main_screen) + sta Globals::zp_arg0 + lda #.hibyte(main_screen) + sta Globals::zp_arg1 + ldx #$28 + jsr load_screen_x + + rts + .endproc + + ;; Load the 1KB worth of screen data located via the 16-bit pointer on + ;; Globals::zp_arg{0,1}. Set the `x` register to the high byte of the + ;; nametable to be used. + .proc load_screen_x + bit PPU::STATUS + + stx PPU::ADDRESS + lda #$00 + sta PPU::ADDRESS + + ldy #0 + ldx #4 + @loop: + lda (Globals::zp_arg0), y + sta PPU::DATA + iny + bne @loop + + dex + beq @end + + inc Globals::zp_arg1 + jmp @loop + + @end: + rts + .endproc + + ;; Performs all the needed tricks in order to get the first nametable as + ;; expected. + .proc prepare_for_title_screen + bit PPU::STATUS + + lda #$23 + sta $2006 + lda #$C8 + sta $2006 + + ldx #$10 + @upper_title_bar_loop: + lda #%10101010 + sta $2007 + dex + bne @upper_title_bar_loop + + ;; TODO: store back palettes after game over. + + rts + .endproc + + ;; Performs all the needed tricks in order to get the second nametable as + ;; expected. + .proc prepare_for_main_screen + ;; TODO: second palette for background should be: + ;; .byte $0F, $14, $2C, $28 + ;; + ;; TODO: first palette for the foreground should be: + ;; .byte $0F, $16, $10, $30 + rts + .endproc + + ;; Copies all the palettes for our game into the proper PPU address. + .proc init_palettes + lda #$3F + sta PPU::ADDRESS + lda #$00 + sta PPU::ADDRESS + + ldx #0 + @load_palettes_loop: + lda palettes, x + sta PPU::DATA + inx + cpx #$20 + bne @load_palettes_loop + rts + palettes: + ;; Background + ;; 0: score + .byte $0F, $30, $2C, $28 + ;; 1: floating platforms + .byte $0F, $2C, $30, $2A + ;; 2: ground + .byte $0F, $28, $2C, $16 + ;; 3: ship + .byte $0F, $16, $30, $00 + + ;; TODO: fuel tank needs color $24 + ;; Foreground + ;; 0: player & ship + .byte $0F, $30, $10, $30 + ;; 1: enemy 1 & bonuses + .byte $0F, $16, $2C, $2A + ;; 2: enemy 2, fuel & bonuses + .byte $0F, $16, $14, $28 + ;; 3: SUSE easter egg + .byte $0F, $16, $00, $2B + .endproc + + ;; Having 2KB for screen data is quite wasteful, but since it's such a + ;; simple game, I have so much space left in the cartridge that I can go + ;; bananas with it. This helps out loading the screen as we can go faster + ;; and it is less error prone. + title_screen: + .incbin "../assets/title.nam" + main_screen: + .incbin "../assets/main.nam" +.endscope + +.segment "CHARS" + .incbin "../assets/jetpac.chr" diff --git a/src/driver.s b/src/driver.s new file mode 100644 index 0000000..521c335 --- /dev/null +++ b/src/driver.s @@ -0,0 +1,28 @@ +.segment "CODE" + +.scope Driver + .proc switch + ;; Get the assets ready for the main screen. That is, make sure that the + ;; palettes and such are as desired since the title screen needed + ;; another setup. + jsr Assets::prepare_for_main_screen + + ;; Switch to the other base nametable. + lda #%10001010 + sta PPU::zp_control + + ;; Mark the state of the game as "game". That is, the player has + ;; started. Also set the `ppu` flag so the PPU control update takes + ;; place. + lda #%01000001 + ora Globals::zp_flags + sta Globals::zp_flags + + rts + .endproc + + .proc update + ;; TODO + rts + .endproc +.endscope diff --git a/src/jetpac.s b/src/jetpac.s index 2f273a3..8acbb4e 100644 --- a/src/jetpac.s +++ b/src/jetpac.s @@ -5,28 +5,74 @@ .segment "CODE" +;; NOTE: automatically generated by the build system. +.include "../config/generated.s" + .include "../include/apu.s" .include "../include/oam.s" .include "../include/ppu.s" +.include "../include/asm.s" +.include "../include/joypad.s" .include "../include/globals.s" + +.include "assets.s" +.include "driver.s" +.include "title.s" .include "vectors.s" .proc main - jsr init_palettes - jsr init_nametables + ;; Disable the PPU. + lda #0 + sta PPU::MASK + + ;; Initialize the assets for the game. + jsr Assets::init + + ;; We shadow the PPU control register in memory. Depending on the `make` + ;; target we might need to switch directly to the main game. Otherwise we go + ;; into the title as expected. + .ifdef PARTIAL + jsr Driver::switch + + lda PPU::zp_control + sta PPU::CONTROL + .else + jsr Title::init + + lda #%10001000 + sta PPU::zp_control + sta PPU::CONTROL + .endif -;;; TODO cli - lda #%10110000 - sta $2000 + + ;; Enable back the PPU lda #%00011110 - sta $2001 + sta PPU::MASK @main_game_loop: - ;;; - ;; TODO - ;;; + READ_JOYPAD1 + + lda Globals::zp_flags + and #%00000011 + beq @title_screen + cmp #1 + beq @game_screen + jmp @over +@title_screen: + jsr Title::update + beq @set_flags + + ;; Start was pressed by the player, switch to the main game. + jsr Driver::switch + jmp @set_flags + +@game_screen: + jsr Driver::update + ;; NOTE: fallthrough + +@set_flags: lda #%10000000 ora Globals::zp_flags sta Globals::zp_flags @@ -36,51 +82,8 @@ ;; Rendering is done, we can perform another iteration of the loop! jmp @main_game_loop -.endproc -;; Copies all the palettes for our game into the proper PPU address. -.proc init_palettes - lda #$3F - sta PPU::ADDRESS - lda #$00 - sta PPU::ADDRESS - - ldx #0 -@load_palettes_loop: - lda palettes, x - sta PPU::DATA - inx - cpx #$20 - bne @load_palettes_loop - rts -palettes: - ;; Background - ;; 0: score - .byte $0F, $30, $2C, $28 - ;; 1: floating platforms - .byte $0F, $2C, $30, $2A - ;; 2: ground - .byte $0F, $28, $14, $28 - ;; 3: ship - .byte $0F, $16, $30, $00 - - ;; TODO: fuel tank needs color $24 - ;; Foreground - ;; 0: player & ship - .byte $0F, $16, $10, $30 - ;; 1: enemy 1 & bonuses - .byte $0F, $16, $2C, $2A - ;; 2: enemy 2, fuel & bonuses - .byte $0F, $16, $14, $28 - ;; 3: SUSE easter egg - .byte $0F, $16, $00, $2B +@over: + ;; TODO: allow to start over, reset flags, control register, etc. + jmp @over .endproc - -.proc init_nametables - ;; TODO - rts -.endproc - - -.segment "CHARS" - .incbin "../assets/jetpac.chr" diff --git a/src/title.s b/src/title.s new file mode 100644 index 0000000..db627ae --- /dev/null +++ b/src/title.s @@ -0,0 +1,100 @@ +.segment "CODE" + +;; All the functions and variables which are related to the title screen. +.scope Title + ;; Y and X coordinates for the sprite that guides the player on the menu. + SPRITE_Y_POSITION0 = $A7 + SPRITE_Y_POSITION1 = $BF + SPRITE_X_POSITION = $40 + + ;; The title has a timer as a delay between joypad presses from the player. + TIMER_INIT_VALUE = 20 + zp_title_timer = $30 + + ;; Initialize all the elements for the title screen. + .proc init + lda #0 + sta zp_title_timer + + ;; Initialize the sprite that guides the player on the menu. + lda #SPRITE_Y_POSITION0 + sta $200 + lda #$30 + sta $201 + lda #$00 + sta $202 + lda #SPRITE_X_POSITION + sta $203 + + rts + .endproc + + ;; Checks the pressed buttons from the joypad and moves the sprite for the + ;; menu accordingly. + ;; + ;; Returns 1 if the player hit start and the game can start, 0 otherwise. + .proc update + lda zp_title_timer + bne @end + + lda #Joypad::BUTTON_UP + and Joypad::zp_buttons1 + beq @check_down + + lda #SPRITE_Y_POSITION0 + cmp $200 + beq @end + sta $200 + jmp @set_timer_and_end + + @check_down: + lda #Joypad::BUTTON_DOWN + and Joypad::zp_buttons1 + beq @check_select + + lda #SPRITE_Y_POSITION1 + cmp $200 + beq @end + sta $200 + jmp @set_timer_and_end + + @check_select: + lda #Joypad::BUTTON_SELECT + and Joypad::zp_buttons1 + beq @check_start + + lda #SPRITE_Y_POSITION0 + cmp $200 + beq @down + sta $200 + jmp @set_timer_and_end + + @check_start: + lda #Joypad::BUTTON_START + and Joypad::zp_buttons1 + beq @end + JAL start + + @down: + lda #SPRITE_Y_POSITION1 + sta $200 + + @set_timer_and_end: + lda #TIMER_INIT_VALUE + sta zp_title_timer + @end: + lda #0 + rts + .endproc + + ;; Save the selection from the menu (TODO), hide all elements from the title + ;; screen, and return always 1. + .proc start + ;; Hide the sprite from the menu. + lda #$EF + sta $200 + + lda #1 + rts + .endproc +.endscope diff --git a/src/vectors.s b/src/vectors.s index 2edc6a6..b06f404 100644 --- a/src/vectors.s +++ b/src/vectors.s @@ -78,16 +78,43 @@ tya pha + ;; Sprite DMA. lda #$00 sta OAM::ADDRESS lda #$02 sta OAM::DMA + ;; TODO: some actions here will depend on the status of the game... + + ;; Decrease title timer. + lda Title::zp_title_timer + beq :+ + dec Title::zp_title_timer +: + + ;; Should we update PPU registers? + bit Globals::zp_flags + bvc @scroll + + ;; Zero out the `ppu` flag. + lda #%10111111 + and Globals::zp_flags + sta Globals::zp_flags + + bit PPU::STATUS + + ;; Update the PPU control register with the shadowed value. + lda PPU::zp_control + sta PPU::CONTROL + +@scroll: + ;; Always reset the scroll just in case. bit PPU::STATUS lda #$00 sta PPU::SCROLL sta PPU::SCROLL + ;; Unblock the main code. lda #%01111111 and Globals::zp_flags sta Globals::zp_flags -- cgit v1.2.3