262 lines
11 KiB
Markdown
262 lines
11 KiB
Markdown
# Game Loop
|
|
|
|
The control flow of _Contra_ is dictated by the CPU memory address
|
|
`GAME_ROUTINE_INDEX` ($18). You can think of the `game_routine_xx` methods like
|
|
canal locks. As the game progresses, the flow moves to the next
|
|
`game_routine`.
|
|
|
|
* `game_routine_00` and `game_routine_01` are for setting up the game intro
|
|
and player select UI.
|
|
* `game_routine_02` is for showing the demo.
|
|
* `game_routine_03` loads and auto-plays the demo level
|
|
* `game_routine_04` clears level memory and player-specific memory
|
|
* `game_routine_05` is where majority of level logic is executed
|
|
* `game_routine_06` runs at the end of the game after defeating the final boss
|
|
alien
|
|
|
|
## game_routine_00
|
|
This label is only run once to initialize the NES's graphics and CPU memory that
|
|
is used for the introduction animation and player select.
|
|
|
|
* Set nametable tiles all to #$00 - `zero_out_nametables` (ROM: $1c9a2, MEM:
|
|
Bank 7 $c9a2)
|
|
* Load intro pattern table, nametable, and palette (`load_intro_graphics`)
|
|
* Reset Komani code number of correct sequence inputs
|
|
(`KONAMI_CODE_NUM_CORRECT`) to back to #$00
|
|
* Initialize the horizontal scroll to #$01 for the sliding intro animation
|
|
* Initialize the high byte of the delay timer that is used for waiting before
|
|
showing the demo
|
|
|
|
## game_routine_01
|
|
This label is executed once per frame repeatedly while the _Contra_ logo is
|
|
scrolled across the screen (intro animation). This label also checks for the
|
|
Konami code is checked. Once the logo is finished scrolling from right to left,
|
|
the label will load the sprites needed to show Bill and Lance as well as the
|
|
player select UI and cursor. Then the intro theme music will be played.
|
|
Finally, this label waits for both timers to complete before either starting
|
|
the game (if the player has made a selection), or show a demo level.
|
|
|
|
* Check for Konami code (`konami_input_check`)
|
|
* Scroll intro graphic
|
|
* This is executed repeatedly until scrolling stops, the the logic below is
|
|
executed
|
|
* Load assets after scrolling is complete (`game_routine_01_scroll_complete`)
|
|
* Load player select menu
|
|
* Play intro explosion sound
|
|
* Load sprite for yellow falcon cursor and sprites for Bill and Lance
|
|
* Wait for timer to complete before moving on to `game_routine_02`
|
|
(`dec_theme_delay_check_user_input`)
|
|
* The timer resets if select is pressed
|
|
|
|
While outside of the `game_routine_01` method, the `exe_game_routine` method
|
|
will call `dec_theme_delay_check_user_input` for game routine `game_routine_00`,
|
|
`game_routine_01`, and `game_routine_02`. For `game_routine_01` this method
|
|
will
|
|
|
|
* decrement timer used to wait for intro theme to play
|
|
* check whether the player has pressed the start button and if so stop the
|
|
intro scrolling animation and show player select UI
|
|
|
|
## game_routine_02
|
|
* Load demo level and plays the level
|
|
* Stop level when demo timer elapsed and loads next level to demo (only
|
|
levels 0-2)
|
|
* Reset `GAME_ROUTINE_INDEX` to #$0 between demo levels to reshow intro
|
|
scroll and player select
|
|
|
|
## game_routine_03
|
|
This label is executed once the player has pressed start to begin a game while
|
|
in `game_routine_02`. It simply ensures the intro theme is finished playing and
|
|
then flashes the player selection until all timers are elapsed
|
|
|
|
* Wait for intro theme to finish playing
|
|
* Flash "1 PLAYER" or "2 PLAYER" for a bit
|
|
|
|
## game_routine_04
|
|
This label clears level memory and player-specific memory like number of lives,
|
|
number of continues, as well as level header data.
|
|
|
|
* `init_score_player_lives` - clears memory addresses $0028 to $00f0 then
|
|
`CPU_SPRITE_BUFFER` ($300) up to but not including `CPU_GRAPHICS_BUFFER`
|
|
($700)
|
|
|
|
## game_routine_05
|
|
This is the where the majority of the game logic is executed from.
|
|
`game_routine_05` maintains its own separate set of routines for managing the
|
|
level state. See Level Routines. There are #$0a level routines.
|
|
|
|
## game_routine_06
|
|
This routine runs at the end of the game after defeating the final boss alien.
|
|
Begins the `game_end_routine_XX` routine execution flow and runs through each
|
|
routine
|
|
|
|
* `game_end_routine_00`
|
|
* `game_end_routine_01`
|
|
* `game_end_routine_02`
|
|
* `game_end_routine_03`
|
|
* `end_game_sequence_00`
|
|
* `end_game_sequence_01`
|
|
* `end_game_sequence_02`
|
|
* `game_end_routine_04`
|
|
* `game_end_routine_05`
|
|
|
|
# Level Routines
|
|
`game_routine_05` maintains a `LEVEL_ROUTINE_INDEX` ($2c). This is an offset
|
|
into the `level_routine_ptr_tbl` (ROM: $1ce35, MEM: Bank 7 $ce35). These
|
|
routines manage the state of the level as the player progresses. These routines
|
|
are not necessarily executed in order like a waterfall. There are #$0a level
|
|
routines in total.
|
|
|
|
These routines manage
|
|
* level initialization
|
|
* showing score before starting a level
|
|
* handle player input, including pause
|
|
* level completion, advancing level
|
|
* game over screen - continue / end
|
|
* game over with no more continues
|
|
* showing game ending sequence
|
|
|
|
Below is a mermaid diagram of the logic that dictates the state changes among
|
|
the level routines.
|
|
|
|
```mermaid
|
|
graph TD
|
|
level_routine_00 --> level_routine_01
|
|
level_routine_01 --> level_routine_02
|
|
level_routine_02 --> level_routine_03
|
|
level_routine_03 --> level_routine_04
|
|
level_routine_04 --> |Level Complete|level_routine_08
|
|
level_routine_04 --> |Game Over|level_routine_0a
|
|
level_routine_05 --> |Level Complete|level_routine_00
|
|
level_routine_05 --> |Complete Last Level/Game Over|level_routine_06
|
|
level_routine_05 --> |Game Over No Continues|level_routine_07
|
|
level_routine_06 --> |Continue/End Selected|level_routine_00
|
|
level_routine_08 --> |Game Over|level_routine_0a
|
|
level_routine_08 --> |Level Complete|level_routine_09
|
|
level_routine_0a --> |Game Over Timer Expired|level_routine_05
|
|
level_routine_09 --> |Level Complete|level_routine_05
|
|
```
|
|
|
|
## level_routine_00
|
|
This routine is responsible for loading the level header data into memory. This
|
|
specifies things like what type of level (indoor/base, outdoor), where graphics
|
|
data for the level are (super-tile pattern tiles, which super-tiles are on each
|
|
screen, and palette data). Then the routine initializes many PPU write
|
|
addresses, and loads the data which specifies the super-tiles on the first
|
|
screen. Note that the actual super-tile pattern tiles aren't loaded into memory
|
|
until `level_routine_03`.
|
|
|
|
* Load the level header data
|
|
* Load palette for screen that displays number of lives remaining and level/
|
|
stage name.
|
|
* Initialize `BOSS_DEFEATED_FLAG` and `LEVEL_END_PLAYERS_ALIVE` to #$00
|
|
* Call `init_ppu_write_screen_supertiles` to initialize PPU scroll offset,
|
|
PPU write offsets and call `load_current_supertiles_screen_indexes` to
|
|
decompress super-tiles indexes for level's screen to load into CPU memory at
|
|
`LEVEL_SCREEN_SUPERTILES`
|
|
* Loads bank 0 where the enemy routines are and initializes the memory address
|
|
$80 to point to where the enemy routines are for the current level
|
|
|
|
## level_routine_01
|
|
Responsible for displaying the number of lives remaining for players before the
|
|
level loads. This routine does not execute when demoing (in demo mode). It is
|
|
only executed once, whereas `level_routine_02` is executed repeatedly to flash
|
|
the score. The number of lives does not flash.
|
|
|
|
## level_routine_02
|
|
* Flash the score text until timer elapses
|
|
* Load the theme music for the level `level_vert_scroll_and_song` when not in
|
|
demo mode
|
|
* Sets the vertical scroll
|
|
|
|
## level_routine_03
|
|
Responsible for rendering the nametable super-tiles.
|
|
|
|
## level_routine_04
|
|
* Sees if the player is pausing or un-pausing by reading input.
|
|
* If the player is pausing, the pause sound is played and the game state is
|
|
updated
|
|
* If the player is paused, then the routine ends
|
|
* Check if BOSS_DEFEATED_FLAG is set, the the current level routine is
|
|
updated to `level_routine_08` and the current routine ends
|
|
* Check if player(s) have entered game over state, and if so, updates the
|
|
current level routine to `level_routine_0a` and the current routine ends
|
|
* Run all enemy logic
|
|
* Run soldier generation
|
|
* Update palette for flashing palettes, see palette cycling documentation in
|
|
`Graphics Documentation.md`
|
|
* Load alternate graphics if necessary
|
|
|
|
## level_routine_05
|
|
Handles when the the level is complete or ended due to game over. If it's not
|
|
the last level, then the sets things up so the next level is loaded. If it's the
|
|
last level, then configures things for the end of game sequences. During game
|
|
over, after GAME_OVER_DELAY_TIMER elapses, level routine #$0a sets the next
|
|
routine to be level_routine_05 so that the game over high score screen can be
|
|
shown
|
|
|
|
* Clear memory $40 to $f0, then $300 to $600 (exclusively)
|
|
* If game over
|
|
* Load game over screen pattern table and show game over screen
|
|
`show_game_over_screen`
|
|
* Increment `CURRENT_LEVEL`
|
|
* If the last level
|
|
* Start game ending sequence `inc_routine_index_set_timer`
|
|
* Increment `GAME_COMPLETION_COUNT` so next play through is more challenging
|
|
* Reset `LEVEL_ROUTINE_INDEX` back to level_routine_00
|
|
* If not the last level
|
|
* Loads graphics for current score screen and level intro screen in
|
|
`load_level_intro`
|
|
* Increments `LEVEL_ROUTINE_INDEX` to go to level_routine_06
|
|
`inc_routine_index_set_timer`
|
|
|
|
## level_routine_06
|
|
This level routine is the game over screen routine. It is executed after the
|
|
player has died. This routine shows the player scores, the high score. It also
|
|
shows the text "GAME OVER" and gives the player the option to either "CONTINUE"
|
|
or "END".
|
|
|
|
* Display score and "CONTINUE"/"END"
|
|
* Handle player input to change cursor between "CONTINUE" and "END" (select
|
|
button)
|
|
* Handle player input to select either "CONTINUE" and "END" (start button)
|
|
|
|
## level_routine_07
|
|
This routine is executed when there are no continues remaining. It shows the
|
|
score and the text "GAME OVER" while waiting for the player to press start. Once
|
|
the player presses start, the level routine is set to #$00
|
|
|
|
`level_routine_07` is executed after checking the number of remaining continues
|
|
in `level_routine_05` (`@no_continues_remaining`).
|
|
|
|
## level_routine_08
|
|
Boss destroy animation. Plays level end music
|
|
|
|
* Check if player(s) have entered game over state, and if so, updates the
|
|
current level routine to `level_routine_0a` and the current routine ends.
|
|
This seems unlikely as this check happens in `level_routine_04` as well.
|
|
This same code is part of `level_routine_07`, and seems equally unlikely to
|
|
happen for that level routine as well.
|
|
|
|
## level_routine_09
|
|
This routine runs the appropriate level routine from the
|
|
`end_level_sequence_ptr_tbl` table. After each sequence is finished executing,
|
|
control is sent back to `level_routine_05`.
|
|
|
|
* `end_level_sequence_00`
|
|
* Wait for the players to land if they were jumping
|
|
* Set a small delay before `end_level_sequence_01` does its logic
|
|
* `end_level_sequence_01`
|
|
* Wait for timer to elapse
|
|
* Run level-specific level routine ending animation from
|
|
`end_of_lvl_lvl_routine_ptr_tbl`
|
|
* `end_level_sequence_02`
|
|
|
|
## level_routine_0a
|
|
This is the last level routine, but is executed as part of a game over sequence.
|
|
After the player(s) get a game over, this is the routine that waits until
|
|
`GAME_OVER_DELAY_TIMER` has elapsed before setting the level routine to #$05
|
|
(`level_routine_05`) to reinitialize the level.
|
|
|
|
The `GAME_OVER_DELAY_TIMER` delay timer starts at #60 and is used to wait a bit
|
|
before showing the player scores. |