aboutsummaryrefslogtreecommitdiff
path: root/scroll/include/collision.s
blob: 37debc5d670ae400faf4a9654bcff1df3c885765 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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