Skip to content

Commit

Permalink
scroll.metatiles: Add bounds checking when scrolling down
Browse files Browse the repository at this point in the history
  • Loading branch information
lajohnston committed Feb 1, 2023
1 parent 61a2d48 commit 341df86
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 36 deletions.
5 changes: 2 additions & 3 deletions docs/scroll/metatiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ Draw the initial view of the map when the display is off.
ld hl, myMetatileDefs
scroll.metatiles.setDefs
ld a, 64 ; map has a width of 64 metatiles
ld a, 64 ; the map has a width of 64 metatiles
ld d, 64 ; the map has a height of 64 metatiles
ld b, 0 ; starting column offset, in metatiles
ld c, 0 ; starting row offset, in metatiles
scroll.metatiles.init ; draw map
Expand All @@ -132,8 +133,6 @@ scroll.metatiles.adjustYPixels

After adjusting these, use `scroll.metatiles.update` to apply the changes to the RAM buffers.

Note: The metatile scroll handler doesn't currently enforce bounds checking, so the scroll could potentially start drawing non-existent tiles off the screen.

### 3. Transfer changes to VRAM (during VBlank)

`scroll.metatiles.render` will apply the scroll register changes to the VDP, and write the new row and/or column to the tilemap if required.
10 changes: 7 additions & 3 deletions examples/08-scrolling-metatiles/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@

;====
; The metatile map, consisting of a grid of 1-byte refereces to the metatile
; definitions. The max width is variable at runtime allowing a variety of level
; sizes.
; definitions. The width and height are variable at runtime allowing a variety
; of map sizes. The max width in metatiles is 255. The max height is the set
; scroll.metatiles.MAX_MAP_BYTES value (defaults to 4096) divided by this map
; width
;====
.define MAP_WIDTH_METATILES 64 ; map width in metatiles
.define MAP_WIDTH_METATILES 64 ; map width in metatiles
.define MAP_HEIGHT_METATILES 64 ; map height in metatiles

.section "metatileMap"
metatileMap:
Expand Down Expand Up @@ -94,6 +97,7 @@

; Initialise map (offset x0, y0)
ld a, MAP_WIDTH_METATILES ; set map width (in metatiles)
ld d, MAP_HEIGHT_METATILES ; the map's height
ld b, 0 ; metatile col offset (0 = left)
ld c, 0 ; metatile row offset (0 = top)
scroll.metatiles.init ; draw the inital screen
Expand Down
4 changes: 0 additions & 4 deletions examples/assets/metatiles/metatileMap.asm
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
; Here we programmatically generate the map but a real game may have a designed
; map that's compressed on the ROM.
;====

; The max height for the map will be the MAX_MAP_BYTES value divided by the width
.define MAP_HEIGHT_METATILES scroll.metatiles.MAX_MAP_BYTES / MAP_WIDTH_METATILES

.repeat MAP_HEIGHT_METATILES index row
.repeat MAP_WIDTH_METATILES index col
.if row == 0 || col == 0 || row == MAP_HEIGHT_METATILES - 1 || col == MAP_WIDTH_METATILES - 1
Expand Down
84 changes: 58 additions & 26 deletions scroll/metatiles.asm
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@
; Number of full metatile columns on the screen at a time
.define scroll.metatiles.FULL_VISIBLE_METATILE_COLS tilemap.COLS / scroll.metatiles.COLS_PER_METATILE

; Number of full metatile rows on the screen at a time
.define scroll.metatiles.FULL_VISIBLE_METATILE_ROWS tilemap.MIN_VISIBLE_ROWS / scroll.metatiles.ROWS_PER_METATILE

;====
; Structs
;====
Expand All @@ -170,8 +173,9 @@
scroll.metatiles.ram.topLeftTile: instanceof scroll.metatiles.TilePointer

; Col and row counters, to help with bounds checking
scroll.metatiles.ram.topMetatileRow: db ; 1-based (1 = col 0)
scroll.metatiles.ram.leftMetatileCol: db ; 1-based (1 = row 0)
scroll.metatiles.ram.maxTopMetatileRow: db ; 1-based (1 = row 0)
scroll.metatiles.ram.topMetatileRow: db ; 1-based (1 = row 0)
scroll.metatiles.ram.leftMetatileCol: db ; 1-based (1 = col 0)
scroll.metatiles.ram.maxLeftMetatileCol: db ; 1-based (1 = col 0)

; Pointer to the metatile definitions
Expand Down Expand Up @@ -248,11 +252,29 @@
; @in a the map's width in metatiles
; @in b the column offset in metatiles
; @in c the row offset in metatiles
; @in d the map's height in metatiles
;====
.section "scroll.metatiles.init" free
scroll.metatiles.init:
; Store metatiles per row
ld (scroll.metatiles.ram.bytesPerRow), a
ld e, a ; set E to map width in metatiles

;===
; Subtract screen width in metatiles from map width to get
; maxLeftMetatileCol. Minus 1 from the subtraction as this will be the
; partial metatile on the right of the screen
;===
sub scroll.metatiles.FULL_VISIBLE_METATILE_COLS - 1
ld (scroll.metatiles.ram.maxLeftMetatileCol), a

;====
; Subtract screen height in metatiles from map height to get
; maxTopMetatileRow
;====
ld a, d ; set A to map height in metatiles
sub scroll.metatiles.FULL_VISIBLE_METATILE_ROWS
ld (scroll.metatiles.ram.maxTopMetatileRow), a

; Set topMetatileRow to C and leftMetatileCol to B
inc b ; make leftMetatileCol 1-based
Expand All @@ -275,7 +297,6 @@

; Set HL to metatileRowOffset * mapWidthCols
ld h, c ; set H to rows to offset
ld e, a ; set E to map width in metatiles
utils.math.multiplyHByE ; set HL to H (metatileRowOffset) * E (mapWidthCols)

; Add metatile column offset to HL
Expand All @@ -290,14 +311,6 @@
; Store resulting metaTileAddress
ld (scroll.metatiles.ram.topLeftTile.metatileAddress), hl

;===
; Subtract screen width in metatiles from map width to get
; maxLeftMetatileCol. Minus 1 from the subtraction as this will be the
; partial metatile on the right of the screen
;===
sub scroll.metatiles.FULL_VISIBLE_METATILE_COLS - 1
ld (scroll.metatiles.ram.maxLeftMetatileCol), a

; Draw a full screen of tiles (routine then returns)
jp scroll.metatiles._drawFullScreen
.ends
Expand Down Expand Up @@ -536,24 +549,43 @@
; point to next metatile row
;===

; We'll now be pointing to the first subrow of the next
; metatile, so set rowsRemaining to max
ld a, scroll.metatiles.ROWS_PER_METATILE
ld (scroll.metatiles.ram.topLeftTile.rowsRemaining), a
;===
; Check bounds. Screen height (24) is a multiple of the allowed
; ROWS_PER_METATILE values, so we don't need to do this check at
; the subRow level. If variable heights are implemented this
; will need to be changed
;===

; Add 1 row to metatileAddress
ld a, (scroll.metatiles.ram.bytesPerRow)
ld d, 0
ld e, a ; set DE to bytesPerRow
ld hl, (scroll.metatiles.ram.topLeftTile.metatileAddress)
add hl, de ; add one row to HL
; Set L to maxTopMetatileRow and H to topMetatileRow
ld hl, (scroll.metatiles.ram.maxTopMetatileRow)
ld a, l ; set A to maxTopMetatileRow
cp h ; compare to current topMetatileRow
jr z, ++ ; jp if topMetatileRow == maxTopMetatileRow
; In bounds

; Store updated metatileAddress
ld (scroll.metatiles.ram.topLeftTile.metatileAddress), hl
; We'll now be pointing to the first subrow of the next
; metatile, so set rowsRemaining to max
ld a, scroll.metatiles.ROWS_PER_METATILE
ld (scroll.metatiles.ram.topLeftTile.rowsRemaining), a

; Increment topMetatileRow
ld hl, scroll.metatiles.ram.topMetatileRow
inc (hl)
; Set DE to bytesPerRow
ld a, (scroll.metatiles.ram.bytesPerRow)
ld d, 0
ld e, a

; Add 1 row to metatileAddress
ld hl, (scroll.metatiles.ram.topLeftTile.metatileAddress)
add hl, de ; add one row to HL
ld (scroll.metatiles.ram.topLeftTile.metatileAddress), hl

; Increment topMetatileRow
ld hl, scroll.metatiles.ram.topMetatileRow
inc (hl)
jp +
++:

; Out of bounds - only scroll to bottom edge of in-bound tile
tilemap.stopDownRowScroll
+:

;===
Expand Down

0 comments on commit 341df86

Please sign in to comment.