aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-03-13 22:52:49 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-03-13 22:52:49 +0100
commit9ff2033e936689135210989a5fee057a4a13527e (patch)
treedd41ffd78f451d28a554f105ff40017ed31106a2 /src
parent2627b459d9a19ce7f1b7f3a359dca3b30b66b34e (diff)
downloadjetpac.nes-9ff2033e936689135210989a5fee057a4a13527e.tar.gz
jetpac.nes-9ff2033e936689135210989a5fee057a4a13527e.zip
Add a title and a main screens
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à <mikisabate@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/assets.s142
-rw-r--r--src/driver.s28
-rw-r--r--src/jetpac.s113
-rw-r--r--src/title.s100
-rw-r--r--src/vectors.s27
5 files changed, 355 insertions, 55 deletions
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