-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsprite0.s
More file actions
260 lines (213 loc) · 6.76 KB
/
Copy pathsprite0.s
File metadata and controls
260 lines (213 loc) · 6.76 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
;;;
;; The same as in `level.s` but at the very top of the screen we have a "This is
;; a message" being shown. This message is part of the background but it does
;; not scroll like the rest of the screen, but it stays at the same coordinates
;; all the time. This is done through sprite 0 collision detection, which is a
;; technique is quite often for early games on the NES/Famicom library (e.g.
;; Super Mario Bros.).
;;
;; The code is really similar to what we had in `level.s`, so I have removed all
;; comments from sections that are identical to those of `level.s`. That is, you
;; can just read the comments to get a "diff" between this one and `level.s`.
.segment "HEADER"
.byte 'N', 'E', 'S', $1A
.byte $02
.byte $01
.byte $01
.byte $00
.segment "VECTORS"
.addr nmi, reset, irq
.segment "CODE"
;; We include the engine as in `level.s`, but we tweak the row offset so we have
;; at least one empty row in order to fit the HUD from this example.
BACKGROUND_ROW_OFFSET = 1
.include "include/all.s"
;; Clear out the first two rows of tiles from the nametable identified by the
;; `x` register.
.proc clear_row_x
bit PPU::STATUS
stx PPU::ADDRESS
lda #$00
sta PPU::ADDRESS
ldx #$40
@loop:
sta PPU::DATA
dex
bne @loop
rts
.endproc
;; Show our awesome HUD, which simply shows a background message with "This is a
;; message". Note that the first sprite on OAM will be initialized also here,
;; which if you look into the CHR file you will realize it's merely two dots put
;; together. The trick is to put this simple sprite right into the final "E" of
;; "message", so it's hidden there. Whenever the PPU detects the collision (i.e.
;; "sprite 0 collision"), we will be able to react accordingly.
;;
;; Note also that because of the limitations from the engine in `include` on not
;; being able to have multiple palettes, it really stands out. This is actually
;; useful on this example, but in a real game you'd want to dedicate a palette
;; which matches the same color from the background one. That is, you'd really
;; want to hide it.
.proc show_hud
;; Clear the first two rows of tiles for both nametables as this will be
;; where we will place the HUD.
ldx #$20
jsr clear_row_x
ldx #$24
jsr clear_row_x
;; Set the background for the HUD. Note that we need it in both nametables,
;; as the engine will actually flip the nametable being used on the
;; PPU::CONTROL register.
;; This
WRITE_PPU_DATA $2028, $23
WRITE_PPU_DATA $2029, $17
WRITE_PPU_DATA $202A, $18
WRITE_PPU_DATA $202B, $22
WRITE_PPU_DATA $2428, $23
WRITE_PPU_DATA $2429, $17
WRITE_PPU_DATA $242A, $18
WRITE_PPU_DATA $242B, $22
;; is
WRITE_PPU_DATA $202D, $18
WRITE_PPU_DATA $202E, $22
WRITE_PPU_DATA $242D, $18
WRITE_PPU_DATA $242E, $22
;; a
WRITE_PPU_DATA $2030, $10
WRITE_PPU_DATA $2430, $10
;; message
WRITE_PPU_DATA $2032, $1C
WRITE_PPU_DATA $2033, $14
WRITE_PPU_DATA $2034, $22
WRITE_PPU_DATA $2035, $22
WRITE_PPU_DATA $2036, $10
WRITE_PPU_DATA $2037, $16
WRITE_PPU_DATA $2038, $14
WRITE_PPU_DATA $2432, $1C
WRITE_PPU_DATA $2433, $14
WRITE_PPU_DATA $2434, $22
WRITE_PPU_DATA $2435, $22
WRITE_PPU_DATA $2436, $10
WRITE_PPU_DATA $2437, $16
WRITE_PPU_DATA $2438, $14
;; Initialize sprite 0, which won't change across the run.
lda #$06
sta $200
lda #$03
sta $201
lda #$00
sta $202
lda #$C0
sta $203
rts
.endproc
;; From level.s the only thing changed is the call to `show_hud`.
.proc main
lda #$00
sta PPU::zp_mask
sta PPU::MASK
jsr Palettes::init
jsr Metatile::init
jsr Driver::init
jsr Background::init
jsr Player::init
;; Show HUD.
jsr show_hud
cli
lda #%10001000
sta PPU::zp_control
sta PPU::CONTROL
@main_game_loop:
READ_JOYPAD1
jsr Player::update
jsr Driver::update
lda #%10000000
ora Globals::zp_flags
sta Globals::zp_flags
@wait_for_render:
bit Globals::zp_flags
bmi @wait_for_render
jmp @main_game_loop
.endproc
;; It's mostly as in `level.s`, but: 1. the scroll register is always set; 2. we
;; have to handle sprite 0 collision.
.proc nmi
bit Globals::zp_flags
bpl @next
pha
txa
pha
tya
pha
jsr Player::update_sprite
OAM_WRITE_SPRITES
FLUSH_PENDING_VRAM_BUFFER
;; Should we update PPU registers? If not, then we can go down to the next
;; section. Otherwise we need to update the PPU registers that have been
;; buffered. Note that in stark difference with `level.s` the scroll
;; register is not included inside of the code below. That's because it has
;; to be set unconditionally or the whole sprite 0 detection trick would
;; flicker whenever the player stops scrolling the screen.
bit Globals::zp_flags
bvc @scroll
lda #%10111111
and Globals::zp_flags
sta Globals::zp_flags
bit PPU::STATUS
lda PPU::zp_control
sta PPU::CONTROL
lda PPU::zp_mask
sta PPU::MASK
@scroll:
;; Is the level actually loaded? If not then it's pointless to mess with the
;; scroll register or waiting for anything: skip all of this.
lda Globals::zp_flags
and #%00100000
beq @unset_render_flag
;; First of all, reset the scroll register to zero for the HUD.
bit PPU::STATUS
lda #$00
sta PPU::SCROLL
lda #$00
sta PPU::SCROLL
;; To be safe, wait until the sprite 0 bit is unset. This will happen
;; whenever the PPU starts rendering the screen. In other words, we wait
;; until VBlank has been consumed because this whole trick has to happen
;; mid-frame rendering.
@wait_sprite0_unset:
bit PPU::STATUS
bvs @wait_sprite0_unset
;; And now everything will be rendered as usual, with scroll = 0. So wait
;; until the PPU detects a collision between a background element and sprite
;; 0. This will happen at the last point of the "E" in "MESSAGE", where our
;; sprite 0 has been "hidden". Whenever that happens, the PPU will set the
;; proper bit on the PPU::STATUS register.
@wait_sprite0_set:
bit PPU::STATUS
bvc @wait_sprite0_set
;; NOTE: after this some games like Super Mario Bros. set up a small delay,
;; but through testing both on emulators (FCEUX and Mesen) and on real
;; hardware, I haven't seen any problems without this delay.
;; Update the scroll register again to its real value.
lda Background::zp_scroll
sta PPU::SCROLL
lda #$00
sta PPU::SCROLL
;; And continue as in `level.s`.
@unset_render_flag:
lda #%01111111
and Globals::zp_flags
sta Globals::zp_flags
pla
tay
pla
tax
pla
@next:
rti
.endproc
.proc irq
rti
.endproc
.segment "CHARS"
.incbin "../assets/diskun.chr"