aboutsummaryrefslogtreecommitdiff
path: root/scroll/level.s
blob: de386db08d632d080e26f99366e6ef9702c3d4b9 (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
;;;
;; Allow the player to scroll a level which spans more than two screens wide.
;; The heavy lifting is pulled by the engine contained in `include`, which even
;; if it has some big limitations, it's good enough for showing how this can be
;; achieved on the NES/Famicom. Read the comments along this file, but you will
;; have to dig deeper into `include` to better grasp how any of this works.
;;
;; As a final touch, you can press "Select" to switch between different levels,
;; even if I was lazy enough to only provide a second level.

.segment "HEADER"
    .byte 'N', 'E', 'S', $1A

    .byte $02
    .byte $01

    .byte $01                   ; Vertical mirroring (important since we are using horizontal scrolling!)
    .byte $00

.segment "VECTORS"
    .addr nmi, reset, irq

.segment "CODE"

.include "include/all.s"

.proc main
    ;; The PPU mask is zero'ed out so no background nor sprites will be shown at
    ;; first. This will be the task of the `load_level_x` function which is
    ;; called during background initialization.
    lda #$00
    sta PPU::zp_mask
    sta PPU::MASK

    ;; Initialize engine.
    jsr Palettes::init
    jsr Metatile::init
    jsr Driver::init
    jsr Background::init
    jsr Player::init

    ;; Accept back interrupts.
    cli

    ;; 7: allow NMI; 5-4: background pattern table at $0000, sprites at $1000.
    ;;
    ;; NOTE: we store this same value both into the PPU register and into this
    ;; special `zp_control` variable. This variable will shadow the contents for
    ;; the PPU control register. Any operation that requires changing the value
    ;; for this register, from now on, we will simply touch this variable
    ;; instead. This is because from now on touching this register is only safe
    ;; inside of Vblank (a.k.a. NMI code). Our `nmi` handler has special code to
    ;; check whether there was an update on this register's value and update
    ;; things accordingly.
    lda #%10001000
    sta PPU::zp_control
    sta PPU::CONTROL

@main_game_loop:
    jsr joypad_read
    jsr Player::update
    jsr Driver::update

    ;; Set the `render` flag, meaning that the code logic is over and we can
    ;; safely go into `nmi` code.
    lda #%10000000
    ora Globals::zp_flags
    sta Globals::zp_flags

@wait_for_render:
    bit Globals::zp_flags
    bmi @wait_for_render

    ;; Rendering is done, we can perform another iteration of the loop!
    jmp @main_game_loop
.endproc

;; The basics are laid out in `basics/sprite.s`. Read the comments below for
;; more info.
.proc nmi
    ;; Skip this entirely if code is not finished. This is a safeguard, but
    ;; whenever it happens it means that we are stalling at least for a frame.
    bit Globals::zp_flags
    bpl @next

    ;; Save registers
    pha
    txa
    pha
    tya
    pha

    ;; Update the sprite of the player. For this simple game, it just means to
    ;; update its position.
    jsr Player::update_sprite

    ;; Render stuff.
    OAM_WRITE_SPRITES

    ;; Flush any pending background updates. This macro actually hides the most
    ;; expensive part executed during VBlank. Check the document on `buffer.s`
    ;; for more information.
    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 and set the scroll.
    bit Globals::zp_flags
    bvc @after_ppu

    ;; Zero out the "ppu" flag.
    lda #%10111111
    and Globals::zp_flags
    sta Globals::zp_flags

    ;; Reset the PPU address latch.
    bit PPU::STATUS

    ;; Update the PPU control/mask registers with their buffered values.
    lda PPU::zp_control
    sta PPU::CONTROL
    lda PPU::zp_mask
    sta PPU::MASK

    ;; Update scroll.
    lda Background::zp_scroll
    sta PPU::SCROLL
    lda #$00
    sta PPU::SCROLL

@after_ppu:
    ;; And unset the `render` flag so the `main` code is unblocked.
    lda #%01111111
    and Globals::zp_flags
    sta Globals::zp_flags

    ;; Restore registers.
    pla
    tay
    pla
    tax
    pla
@next:
    rti
.endproc

;; Unused.
.proc irq
    rti
.endproc

.segment "CHARS"
    .incbin "../assets/diskun.chr"