diff options
| author | Miquel Sabaté Solà <msabate@suse.com> | 2023-11-27 10:31:03 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-02-04 21:02:54 +0100 |
| commit | cee93625989b651cfe91387eabd0abd3f64a00df (patch) | |
| tree | d1525bd15c191c2926ea1166263b249a8bbeb79c | |
| parent | 0e1839f48ada69b81f5879449d0e0fe435f851c5 (diff) | |
| download | code.nes-cee93625989b651cfe91387eabd0abd3f64a00df.tar.gz code.nes-cee93625989b651cfe91387eabd0abd3f64a00df.zip | |
scroll: Start with the scrolling examples
This is still work in progress but at least there's some basic structure
to it.
Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | scroll/README.md | 9 | ||||
| -rw-r--r-- | scroll/assets/nexxt-session.nss | 186 | ||||
| -rw-r--r-- | scroll/assets/scroll.chr | bin | 0 -> 8192 bytes | |||
| -rw-r--r-- | scroll/include/apu.s | 6 | ||||
| -rw-r--r-- | scroll/include/globals.s | 43 | ||||
| -rw-r--r-- | scroll/include/joypad.s | 63 | ||||
| -rw-r--r-- | scroll/include/oam.s | 13 | ||||
| -rw-r--r-- | scroll/include/palettes.s | 30 | ||||
| -rw-r--r-- | scroll/include/player.s | 88 | ||||
| -rw-r--r-- | scroll/include/ppu.s | 27 |
12 files changed, 471 insertions, 14 deletions
@@ -11,14 +11,14 @@ clean: @rm -rf out @find . -type f -name "*.o" -delete @find . -type f -name "*.nes" -delete - @mkdir -p out/basics + @mkdir -p out/basics out/scroll .PHONY: deps deps: @which $(CC65) >/dev/null 2>/dev/null || (echo "ERROR: $(CC65) not found." && false) .PHONY: build -build: basics space +build: basics space scroll .PHONY: basics basics: unrom @@ -36,3 +36,7 @@ unrom: space: @cd space && CC65=$(CC65) CCOPTS="$(CCOPTS)" $(MAKE) @mv space/space.nes out/ + +.PHONY: scroll +scroll: + $(CC65) $(CCOPTS) scroll/level.s -o out/scroll/level.nes @@ -5,12 +5,12 @@ --- -This repository consists of some examples on programs for the NES. You can build -them by just calling `make`. Before doing that, though, you will need a compiler -for the 6052 platform. A good option is [cc65](https://github.com/cc65/cc65), -which is available on all major platforms. Otherwise, if you want to use another -compiler, you can pass the `CC65` and `CCOPTS` variables to the Makefile. Either -way, the resulting ROMs will be placed in the `out` directory. +This repository consists of some examples of programs for the NES. You can build +them by just calling `make` and they will then be available at the `out` +directory. Before doing that, though, you will need a compiler for the 6052 +platform. A good option is [cc65](https://github.com/cc65/cc65), which is +available on all major platforms. Otherwise, if you want to use another +compiler, you can pass the `CC65` and `CCOPTS` variables to the Makefile. After that, it's recommended that you run the ROMs with an emulator with debugging support or at least some form of memory visualization. This is because diff --git a/scroll/README.md b/scroll/README.md index 553a497..d24117d 100644 --- a/scroll/README.md +++ b/scroll/README.md @@ -5,8 +5,8 @@ This set of examples try to cover it as much as possible while being approachable as single files. First of all, you should take a look at `level.s`, which shows how games can -scroll a level and continuously load/unload the next/previous sections of the -level. +scroll a level that spans multiple screens and continuously load/unload the +next/previous sections of the level. The second example is `sprite0.s`, which covers the scrolling done by games such as Super Mario Bros. or Punch-out. That is, we use the "sprite 0 hit" detection @@ -18,7 +18,4 @@ part does not move and shows a "THIS DOES NOT MOVE" message. Last but not least, the `mmc3.s` example shows how to configure the MMC3 chip (e.g. Super Mario Bros. 3, Kirby's Adventure) to have better control on which parts of the screen to scroll or not. To showcase this the example implements a -Pong game by scrolling the paddles instead of directly setting their positions. -This is probably the most stupid way to use this hardware expansion (kind of -like killing a fly with a cannon), but it at least shows a simple way to have -two independent scrolls while having a static middle ground. +roulette mini-game (similar to Super Mario Bros. 3 but simpler). diff --git a/scroll/assets/nexxt-session.nss b/scroll/assets/nexxt-session.nss new file mode 100644 index 0000000..003ee87 --- /dev/null +++ b/scroll/assets/nexxt-session.nss @@ -0,0 +1,186 @@ +NSTssTXT
+
+BtnTiles=1
+BtnChecker=0
+BtnSelTiles=0
+BtnChrBank1=1
+BtnChrBank2=0
+BtnGridAll=0
+BtnGridTile=0
+BtnGridAtr=1
+BtnGridBlock=0
+BtnGridScr=0
+BtnPal=1
+BtnTypeIn=0
+BtnFrameAll=1
+BtnFrameSelected=0
+BtnFrameNone=0
+BtnSpriteSnap=0
+BtnSprite8x16=0
+BtnForce8x16=1
+MenuBestOffsets=0
+MenuLossy=0
+MenuThreshold=0
+MenuNoColorData=0
+MenuMetaSprAutoInc=0
+MenuMetaSprSkipZero=0
+MenuMetaSprMerge=0
+MenuSaveIncName=1
+MenuSaveIncAttr=1
+MenuSaveRLE=0
+VarTileViewTagBuf=0
+VarBgPalCur=2
+VarPalActive=0
+VarTileActive=18
+VarBankActive=0
+VarPPUMask=0
+VarPPUMaskSet0=0
+VarPPUMaskSet1=0
+VarPPUMaskSet2=0
+VarPPUMaskSet3=0
+VarPalBank=0
+VarMetaSpriteActive=0
+VarSpriteActive=0
+VarSpriteGridX=64
+VarSpriteGridY=64
+VarNameW=32
+VarNameH=30
+VarNameViewX=0
+VarNameViewY=-1
+VarNameSelectionL=-1
+VarNameSelectionR=0
+VarNameSelectionT=-1
+VarNameSelectionB=0
+VarNameCopyW=-1
+VarNameCopyH=-1
+VarCHRSelectionL=2
+VarCHRSelectionR=3
+VarCHRSelectionT=1
+VarCHRSelectionB=2
+VarCHRCopyW=1
+VarCHRCopyH=1
+VarCHRCopyRect=1
+VarCHRSelectRect=1
+VarCHRBanks=4
+VarCHRBanksUndo=4
+VarCheckpoint_CHRBanks=4
+VarCheckpoint_CHRBanksUndo=4
+
+
+VarCHRSelected=00[12]0100[ed]
+
+
+Palette=11280f301101213111061626110919290f1121310f1222320f1323330f1424340f1525350f1626360f1727370f1828380f1929390f1a2a3a0f1b2b3b0f1c2c3c
+
+
+PalUndo=11280f301101213111061626110919290f1121310f1222320f1323330f1424340f1525350f1626360f1727370f1828380f1929390f1a2a3a0f1b2b3b0f1c2c3c
+
+
+CHRMain=00[12]1f3f312e2d6e003f60404e51d29100[2]f8fc8c74b47600fc0602728a4b8900[e0]797f3f1f3f7e7c008680c0604081837c8efefcf8fcfe7e00710103060201817e00[ee0]102022bae6e1c0[2]ff[3]67599ebf[2]c1c3c684fc[2]0e02bfbebd7b[2]07f3fdf111[2]1f10[3]ff0fef[6]e01f10[2]f010[3]ffe0ef[6]0f7fbfdfeff0[4]804020100f[4]f0[4]ff[4]0f[4]1f3f7fff[5]0f[4]0103070fff[4]0f[4]f7fbfdfeff[8]00[f70]
+
+
+CHRCopy=8efefcf8fcfe7e00710103060201817e1f10[2]f010[3]ffe0ef[6]0f7fbfdfeff0[4]804020100f[4]f0[4]ff[4]0f[4]1f3f7fff[5]0f[4]0103070fff[4]0f[4]f7fbfdfeff[8]00[fa0]
+
+
+CHRUndo=00[12]1f3f312e2d6e003f60404e51d29100[2]f8fc8c74b47600fc0602728a4b8900[e0]797f3f1f3f7e7c008680c0604081837c00[ef0]102022bae6e1c0[2]ff[3]67599ebf[2]c1c3c684fc[2]0e02bfbebd7b[2]07f3fdf111[2]1f10[3]ff0fef[6]e01f10[2]f010[3]ffe0ef[6]0f7fbfdfeff0[4]804020100f[4]f0[4]ff[4]0f[4]1f3f7fff[5]0f[4]0103070fff[4]0f[4]f7fbfdfeff[8]00[f70]
+
+
+NameTable=00[3c0]
+
+
+NameCopy=00[3c0]
+
+
+NameUndo=00[3c0]
+
+
+AttrTable=00[40]
+
+
+AttrCopy=00[40]
+
+
+AttrUndo=00[40]
+
+
+MetaSprites=ff[10000]
+
+
+32_TileCollisionProperties=00000000[200]
+
+
+32_TileCollisionPropertiesUndo=00000000[200]
+
+
+PropertyConditions=1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]
+
+
+PropertyConditionsUndo=1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]1[2]0[2]
+
+
+TilePropConditionType=00[7]02
+
+
+TilePropConditionTypeUndo=00[7]02
+
+
+Checkpoint_Palette=00[40]
+
+
+Checkpoint_PalUndo=00[40]
+
+
+Checkpoint_CHRMain=00[2000]
+
+
+Checkpoint_CHRUndo=00[2000]
+
+
+Checkpoint_NameTable=00[3c0]
+
+
+Checkpoint_NameUndo=00[3c0]
+
+
+Checkpoint_AttrTable=00[40]
+
+
+Checkpoint_AttrUndo=00[40]
+
+
+Checkpoint_MetaSprites=ff[10000]
+
+
+Checkpoint_32_TileCollisionProperties=00000000[200]
+
+
+Checkpoint_32_TileCollisionPropertiesUndo=00000000[200]
+
+
+Checkpoint_PropertyConditions=[20]
+
+
+Checkpoint_PropertyConditionsUndo=[20]
+
+
+Checkpoint_TilePropConditionType=00[7]02
+
+
+Checkpoint_TilePropConditionTypeUndo=00[7]02
+propertyCHRlabel0=solid
+propertyCHRlabel1=dangerous
+propertyCHRlabel2=unlabeled2
+propertyCHRlabel3=unlabeled3
+propertyCHRlabel4=unlabeled4
+propertyCHRlabel5=unlabeled5
+propertyCHRlabel6=escape bit
+propertyCHRlabel7=global condition
+MetaSpriteBankName=session
+
+FilterCHR=0
+FilterName=1
+FileNameCHR=Z:\home\mssola\src\github.com\mssola\NES\scroll\assets\scroll
+FileNameName=
+FileNamePal=
+FileNameMetaSpriteBank=
+FileNameTileProps=
diff --git a/scroll/assets/scroll.chr b/scroll/assets/scroll.chr Binary files differnew file mode 100644 index 0000000..3ed0dc4 --- /dev/null +++ b/scroll/assets/scroll.chr diff --git a/scroll/include/apu.s b/scroll/include/apu.s new file mode 100644 index 0000000..32ccff2 --- /dev/null +++ b/scroll/include/apu.s @@ -0,0 +1,6 @@ +.segment "CODE" + +.scope APU + DMC = $4010 + FRAME_COUNTER = $4017 +.endscope diff --git a/scroll/include/globals.s b/scroll/include/globals.s new file mode 100644 index 0000000..d941736 --- /dev/null +++ b/scroll/include/globals.s @@ -0,0 +1,43 @@ +;; Resets the background buffer to a zero-sized array. +.macro CLEAR_BACKGROUND_BUFFER + lda #$FF + sta Globals::m_background_buffer +.endmacro + +;; TODO: macro PUSH_TO_BACKGROUND_BUFFER_X + +;; Global variables used throughout the scrolling examples. +.scope Globals + ;;; + ;; Temporary values. + + m_tmp_1 = $90 + m_tmp_2 = $91 + m_tmp_3 = $92 + + ;; Current value for the scroll on the X axis. + m_scroll = $93 + + m_background_idx = $19 + + ;; TODO + m_collisions = $20 + + ;; TODO + m_background_buffer = $40 + + ;; Initialize global variables. + .proc init + lda #0 + sta m_tmp_1 + sta m_tmp_2 + sta m_tmp_3 + sta m_scroll + sta m_background_idx + + CLEAR_BACKGROUND_BUFFER + sta m_collisions + + rts + .endproc +.endscope diff --git a/scroll/include/joypad.s b/scroll/include/joypad.s new file mode 100644 index 0000000..8711c28 --- /dev/null +++ b/scroll/include/joypad.s @@ -0,0 +1,63 @@ +.segment "CODE" + +;;; +;; Joypad controller code. The following memory addresses are reserved: $21-$23. +;; +;; Memory address $21 is used for internal purposes, whereas $22 and $23 contain +;; the bitmask of the buttons that are pressed from each controller. +;;; + +.scope Joypad + ;; Button masks. + BUTTON_A = 1 << 7 + BUTTON_B = 1 << 6 + BUTTON_SELECT = 1 << 5 + BUTTON_START = 1 << 4 + BUTTON_UP = 1 << 3 + BUTTON_DOWN = 1 << 2 + BUTTON_LEFT = 1 << 1 + BUTTON_RIGHT = 1 << 0 + + ;; Port addresses for controllers. + JOYPAD1 = $4016 + JOYPAD2 = $4017 + + ;; We keep all the information from controller from mainly two variables: + ;; m_buttons1 and m_buttons2; containing respectively the buttons pressed + ;; for this frame for both controllers. The m_inv_buttons ($21) is an + ;; internal variable and should not be used for anything outside of this + ;; usage. + m_inv_buttons = $21 + m_buttons1 = $22 + m_buttons2 = $23 + + ;; READ_CONTROLLER reads the input from the controller mapped into the given + ;; port, and saves the state into the given `buttons` address. + ;; Implementation taken from NESHacker's example of smb3-like movement. + .macro READ_CONTROLLER port, buttons + lda m_inv_buttons + tay + lda #1 + sta port + sta m_inv_buttons + lsr + sta port + : + lda port + lsr + rol m_inv_buttons + bcc :- + tya + eor m_inv_buttons + and m_inv_buttons + sta buttons + .endmacro + + ;; read sets the values for m_buttons1 and m_buttons2 as read from both + ;; controllers. + .proc read + READ_CONTROLLER JOYPAD1, m_buttons1 + READ_CONTROLLER JOYPAD2, m_buttons2 + rts + .endproc +.endscope diff --git a/scroll/include/oam.s b/scroll/include/oam.s new file mode 100644 index 0000000..ecd1b7d --- /dev/null +++ b/scroll/include/oam.s @@ -0,0 +1,13 @@ +.segment "CODE" + +.scope OAM + ADDR = $2003 + DMA = $4014 +.endscope + +.macro OAM_WRITE_SPRITES + lda #$00 + sta OAM::ADDR + lda #$02 + sta OAM::DMA +.endmacro diff --git a/scroll/include/palettes.s b/scroll/include/palettes.s new file mode 100644 index 0000000..9403b7b --- /dev/null +++ b/scroll/include/palettes.s @@ -0,0 +1,30 @@ +;; Shared code for palettes across scrolling examples. +.scope Palettes + DEFAULT_COLOR = $11 + + ;; Copies all the palettes for our game into the proper PPU address. + .proc init + PPU_ADDR $3F00 + + ldx #0 + @load_palettes_loop: + lda palettes, x + sta PPU::DATA + inx + cpx #$20 + bne @load_palettes_loop + rts + palettes: + ;; Background + .byte DEFAULT_COLOR, $36, $17, $0F + .byte DEFAULT_COLOR, $00, $00, $00 + .byte DEFAULT_COLOR, $00, $00, $00 + .byte DEFAULT_COLOR, $00, $00, $00 + + ;; Foreground + .byte DEFAULT_COLOR, $28, $0F, $30 + .byte DEFAULT_COLOR, $00, $00, $00 + .byte DEFAULT_COLOR, $00, $00, $00 + .byte DEFAULT_COLOR, $00, $00, $00 + .endproc +.endscope diff --git a/scroll/include/player.s b/scroll/include/player.s new file mode 100644 index 0000000..95d710d --- /dev/null +++ b/scroll/include/player.s @@ -0,0 +1,88 @@ +;; This is the player code that gets re-used on all scrolling examples. This +;; will show a meta-sprite made up of four sprites that make up diskun. You can +;; move this player with the arrows, and the movement will be pretty basic (1 +;; pixel each time). Furthermore, the sprite handles collision with background +;; elements (TODO), which is shared across all the scrolling examples. +;; +;; The sprite is stored in OAM memory $200-$20F, and the $30 and $31 memory +;; addresses are used for keeping up with the screen coordinates. +.scope Player + m_screen_x = $30 + m_screen_y = $31 + + .proc init + lda #40 + sta m_screen_x + sta m_screen_y + + lda #$01 + sta $201 + lda #$02 + sta $205 + lda #$11 + sta $209 + lda #$12 + sta $20D + + lda #0 + sta $202 + sta $206 + sta $20A + sta $20E + + rts + .endproc + + .proc update + jsr update_coordinates + jsr update_sprites + rts + .endproc + + .proc update_coordinates + lda #Joypad::BUTTON_UP + and Joypad::m_buttons1 + beq @check_down + dec m_screen_y + jmp @check_left + @check_down: + lda #Joypad::BUTTON_DOWN + and Joypad::m_buttons1 + beq @check_left + inc m_screen_y + @check_left: + lda #Joypad::BUTTON_LEFT + and Joypad::m_buttons1 + beq @check_right + dec m_screen_x + rts + @check_right: + lda #Joypad::BUTTON_RIGHT + and Joypad::m_buttons1 + beq @end + inc m_screen_x + ;; TODO: move scroll + @end: + rts + .endproc + + .proc update_sprites + lda m_screen_x + sta $203 + sta $20B + clc + adc #8 + sta $207 + sta $20F + + lda m_screen_y + sta $200 + sta $204 + clc + adc #8 + sta $208 + sta $20C + + rts + .endproc +.endscope diff --git a/scroll/include/ppu.s b/scroll/include/ppu.s new file mode 100644 index 0000000..23fd978 --- /dev/null +++ b/scroll/include/ppu.s @@ -0,0 +1,27 @@ +.segment "CODE" + +.scope PPU + CONTROL = $2000 + MASK = $2001 + STATUS = $2002 + SCROLL = $2005 + ADDRESS = $2006 + DATA = $2007 +.endscope + +.macro PPU_ADDR address + lda #.HIBYTE(address) + sta PPU::ADDRESS + lda #.LOBYTE(address) + sta PPU::ADDRESS +.endmacro + +;; WRITE_PPU_DATA is a macro that will write into PPU::ADDRESS the given address +;; and into PPU::DATA the given byte value. +.macro WRITE_PPU_DATA address, value + bit PPU::STATUS + + PPU_ADDR address + lda #value + sta PPU::DATA +.endmacro |
