aboutsummaryrefslogtreecommitdiff
path: root/scroll/include/collision.s
diff options
context:
space:
mode:
Diffstat (limited to 'scroll/include/collision.s')
-rw-r--r--scroll/include/collision.s234
1 files changed, 234 insertions, 0 deletions
diff --git a/scroll/include/collision.s b/scroll/include/collision.s
new file mode 100644
index 0000000..37debc5
--- /dev/null
+++ b/scroll/include/collision.s
@@ -0,0 +1,234 @@
+;;;
+;; Collisions for the background are handled by building up an array starting at
+;; RAM address $0700. This array contains a list of values to be considered when
+;; checking up background collision, and it's expected to be built when loading
+;; a screen or parts of it (e.g. `load_column`).
+;;
+;; In summary, each row of metatiles is mapped by two bytes. There are 16 tiles
+;; in a row (see `metatile.s`), so each bit from these two bytes map whether
+;; that metatile has a collision block or not. More specifically, the first bit
+;; from the first byte maps the first metatile on that row, the second bit from
+;; the first byte the second metatile on that row, and so on. Considering that
+;; there are 15 rows of tiles * 2 bytes for each row, it means that we can get a
+;; whole screen mapped with 30 bytes. As explained on the `metatile.s` file and
+;; taking the example of Super Mario Bros., things could have been further
+;; compressed by disregarding some of the rows which are anyways unaccessible by
+;; the player (e.g. by the HUD), but I have tried to keep things generic.
+;;
+;; Last but not least, we reserve space for two screens: one per nametable.
+;; Thus, the memory usage looks like this:
+;;
+;; - $0700-$071D: collision map for the screen on nametable 0.
+;; - $071E-$073B: collision map for the screen on nametable 1.
+.scope Collision
+ ;; The high byte value for the collision map (which lives at $0700 in RAM).
+ COLLISION_MAP_HI = $07
+
+ ;; Starting index for the collision map of the second screen.
+ COLLISION_MAP_SECOND = $1E
+
+ ;; Shadow references to Player::zp_screen_{x,y} and Background::zp_scroll.
+ ;; Not awesome, but cc65 doesn't allow to re-open scopes or similar
+ ;; code-sharing scenarios.
+ zp_player_screen_x = $30
+ zp_player_screen_y = $31
+ zp_background_scroll = $95
+
+ ;; Set/unset the proper bit on the collision map for the given metatile
+ ;; position and collision bit.
+ ;;
+ ;; The `a` register has to contain the collision information as given on a
+ ;; metatile reference for a screen. Hence, something like `$81` informs that
+ ;; metatile with id 1 is a solid block, while `$01` would mean the same but
+ ;; unsetting collision for that block.
+ ;;
+ ;; In memory you have to pass `Globals::zp_arg0` and `Globals::zp_arg1`,
+ ;; which contain the Y and X metatile coordinates as passed around in
+ ;; `Buffer::push_metatile`. This function won't touch other parameters, so
+ ;; future code can rely on `Globals::zp_arg2` and the rest to be preserved
+ ;; (i.e. only `Globals::zp_arg0` and `Globals::zp_arg1` will be affected).
+ .proc set_background_collision
+ ;; Preserve the collision byte for later.
+ sta Globals::zp_tmp0
+
+ ;; Select the screen to be used and save it into the `y` register, as
+ ;; expected by the `get_background_collision_y` function.
+ lda Globals::zp_flags
+ and #%00000100
+ lsr
+ lsr
+ tay
+
+ ;; Get the bitmap address for the Y and X coordinates.
+ jsr get_background_collision_y
+ tax
+
+ ;; Should we set or unset the collision bit?
+ lda Globals::zp_tmp0
+ bmi @set_collision
+
+ ;; We have to unset it: flip the mask, unset the given bit and save the
+ ;; result.
+ txa
+ eor #$FF
+ and (Globals::zp_arg0), y
+ sta (Globals::zp_arg0), y
+ rts
+
+ @set_collision:
+ ;; We have to set it: apply the given mask and save the result.
+ txa
+ ora (Globals::zp_arg0), y
+ sta (Globals::zp_arg0), y
+ rts
+ .endproc
+
+ ;; Get the collision value for the Y and X positions of a metatile. These
+ ;; two coordinates are given in memory arguments in this exact order, while
+ ;; the `y` register selects which map to pick from (i.e. the screen).
+ ;;
+ ;; Returns two bytes in memory which correspond to the 16-bit pointer to the
+ ;; row section that contains this metatile (remember that a row of metatiles
+ ;; in screen is split in two). This pointer is complemented by the fact that
+ ;; the `y` register will be set to the proper value. Hence, after calling
+ ;; this function you can get the collision bitmap for the row section with:
+ ;;
+ ;; lda (Globals::zp_arg0), y
+ ;;
+ ;; The `a` register will contain the mask which pin points the exact
+ ;; location on the X coordinate. This is useful for collision detection.
+ ;; Hence, combining with the above you can do:
+ ;;
+ ;; jsr get_background_collision_y
+ ;; and (Globals::zp_arg0), y ; Use the mask on `a` against the bitmap.
+ ;; bne @collision ; Collision!
+ ;;
+ .proc get_background_collision_y
+ ;; Each row is made up of two bytes worth of bitmaps. Hence, the Y
+ ;; metatile position has to be multiplied by two to get to the proper
+ ;; row.
+ lda Globals::zp_arg0
+ asl
+
+ ;; Depending on the nametable being mapped, we have to pick one map or
+ ;; the other. Since they are contiguous, this is a matter of simply
+ ;; adding the base address for the second map if that's the case.
+ cpy #1
+ bne @store_base_pointer
+ clc
+ adc #COLLISION_MAP_SECOND
+ @store_base_pointer:
+ sta Globals::zp_arg0
+
+ ;; Now it's time to figure things out given the value for the X
+ ;; coordinate. First of all, set on the `y` register which bitmap to
+ ;; pick for the row of metatiles (remember that a row is split in two
+ ;; bitmaps). Moreover, update the coordinate if it's on the second
+ ;; bitmap.
+ lda Globals::zp_arg1
+ ldy #0
+ cmp #8
+ bcc @eval_x
+ sec
+ sbc #8
+ iny
+
+ ;; With that, now we have to shift as many times as the X coordinate to
+ ;; get the mask for the bitmap we are pointing at.
+ @eval_x:
+ tax
+ lda #1
+ cpx #0
+ beq @end
+ @loop:
+ asl
+ dex
+ bne @loop
+
+ @end:
+ ;; The only thing missing is to set the high byte of the end 16-bit
+ ;; pointer.
+ ldx #COLLISION_MAP_HI
+ stx Globals::zp_arg1
+
+ ;; NOTE: Returned value is left untouched into the `a` register,
+ ;; computed in the previous loop.
+
+ rts
+ .endproc
+
+ ;; Translates the player's screen coordinates into metatile ones.
+ ;;
+ ;; This function takes two memory arguments, which are offsets to the Y and
+ ;; X screen coordinates respectively. The screen coordinates are taken
+ ;; directly from `Player::zp_screen_{y,x}`, and it also handles
+ ;; `Background::zp_scroll`.
+ ;;
+ ;; In turn this function will store in `zp_arg0` the Y metatile coordinate,
+ ;; and in `zp_arg1` the X one. The `y` register will also point if we are in
+ ;; nametable 0 or 1.
+ .proc screen_to_mt_coordinates
+ ;; Add up the player's screen coordinates with the given Y offset.
+ ;; Transforming that result into a metatile index is basically a matter
+ ;; of shifting right 4 times, since metatiles are 16x16 pixels.
+ lda Globals::zp_arg0
+ clc
+ adc Collision::zp_player_screen_y
+ lsr
+ lsr
+ lsr
+ lsr
+ sta Globals::zp_arg0
+
+ ;; The `y` register is generally the nametable where the focus is on.
+ ;; This is handled on the PPU control register, and shadowed by a
+ ;; `PPU::zp_control` variable. This will have to be adjusted depending
+ ;; on the current scroll. See more below.
+ lda #%00000001
+ and PPU::zp_control
+ tay
+
+ ;; Before figuring out the X metatile coordinates, compute the metatile
+ ;; coordinates if we were only to consider the scroll. This gives us
+ ;; information on from which metatile column is the current nametable
+ ;; visible. It will be used to correct the `y` register whenever columns
+ ;; from the next nametable start appearing on the player's area.
+ lda Collision::zp_background_scroll
+ lsr
+ lsr
+ lsr
+ lsr
+ sta Globals::zp_tmp0
+
+ ;; The coordinates for the X axis is similar to the Y one but we also
+ ;; need to take into account scrolling. Note that overflow is permitted
+ ;; as this will be handled by the correction code below (i.e. if the X
+ ;; metatile is beyond what can be seen by the current nametable, focus
+ ;; on the next one).
+ lda Globals::zp_arg1
+ clc
+ adc Collision::zp_player_screen_x
+ clc
+ adc Collision::zp_background_scroll
+ lsr
+ lsr
+ lsr
+ lsr
+ sta Globals::zp_arg1
+
+ ;; Is the X metatile coordinate actually smaller than the scrolling
+ ;; view?
+ cmp Globals::zp_tmp0
+ bcs @end
+
+ ;; Yes! Then we are on the next nametable. Amend the value on the `y`
+ ;; register and make sure it's either 1 or 0.
+ iny
+ tya
+ and #%00000001
+ tay
+
+ @end:
+ rts
+ .endproc
+.endscope