Simple SNES shoot-'em-up game.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1080 lines
23 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. .INCLUDE "header.asm"
  2. .INCLUDE "init.asm"
  3. .INCLUDE "registers.asm"
  4. .INCLUDE "memory.asm"
  5. ; Sets A to 8-bit (& enables 8-bit "B" register).
  6. .MACRO SetA8Bit
  7. sep #%00100000 ; 8-bit A/B.
  8. .ENDM
  9. ; Sets A to 16-bit.
  10. .MACRO SetA16Bit
  11. rep #%00100000 ; 16-bit A.
  12. .ENDM
  13. ; Sets X/Y to 16-bit.
  14. .MACRO SetXY16Bit
  15. rep #%00010000 ; 16-bit X/Y.
  16. .ENDM
  17. ; Stores result to A.
  18. ; Assumes 16-bit X & 8-bit A.
  19. ; Modifies X.
  20. ; Updates randomBytePtr.
  21. .MACRO GetRandomByte
  22. ldx randomBytePtr
  23. lda $028000, X ; $028000: beginning of ROM bank 2.
  24. inx
  25. cpx #$8000 ; This is the size of the entire ROM bank.
  26. bne +++
  27. ldx #0
  28. +++
  29. stx randomBytePtr
  30. .ENDM
  31. .BANK 0 SLOT 0
  32. .ORG 0
  33. .SECTION "MainCode"
  34. Start:
  35. InitSNES
  36. ; By default we assume 16-bit X/Y and 8-bit A.
  37. ; If any code wants to change this, it's expected to do so itself,
  38. ; and to change them back to the defaults before returning.
  39. SetXY16Bit
  40. SetA8Bit
  41. jsr LoadPaletteAndTileData
  42. jsr InitWorld
  43. ; Set screen mode: 16x16 tiles for backgrounds, mode 1.
  44. lda #%11000001
  45. sta BGMODE
  46. ; Set sprite size to 8x8 (small) and 32x32 (large).
  47. lda #%00100000
  48. sta OAMSIZE
  49. ; Main screen: enable sprites & BG3.
  50. lda #%00010100
  51. sta MSENABLE
  52. ; Turn on the screen.
  53. ; Format: x000bbbb
  54. ; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0-$F)
  55. lda #%00001111
  56. sta INIDISP
  57. jmp MainLoop
  58. LoadPaletteAndTileData:
  59. ; For more details on DMA, see:
  60. ; http://wiki.superfamicom.org/snes/show/Grog%27s+Guide+to+DMA+and+HDMA+on+the+SNES
  61. ; http://wiki.superfamicom.org/snes/show/Making+a+Small+Game+-+Tic-Tac-Toe
  62. ;
  63. ; A lot of the graphics-related registers are explained in Qwertie's doc:
  64. ; http://emu-docs.org/Super%20NES/General/snesdoc.html
  65. ; ... but be careful, because there are some errors in this doc.
  66. ;
  67. ; bazz's tutorial (available from http://wiki.superfamicom.org/snes/) is
  68. ; quite helpful with palette / sprites / DMA, especially starting at
  69. ; http://wiki.superfamicom.org/snes/show/Working+with+VRAM+-+Loading+the+Palette
  70. ; Initialize the palette memory in a loop.
  71. ; We could also do this with a DMA transfer (like we do with the tile data
  72. ; below), but it seems overkill for just a few bytes. :)
  73. ; TODO(mcmillen): do it with a DMA transfer.
  74. ; First, sprite palette data:
  75. ldx #0
  76. lda #128 ; Palette entries for sprites start at 128.
  77. sta CGADDR
  78. -
  79. lda.l SpritePalette, X
  80. sta CGDATA
  81. inx
  82. cpx #32 ; 32 bytes of palette data.
  83. bne -
  84. ; Now, BG3 palette data.
  85. ; Palette entries for BG3 start at 0.
  86. ldx #0
  87. lda #0
  88. sta CGADDR
  89. -
  90. lda.l TilePalette, X
  91. sta CGDATA
  92. inx
  93. cpx #8 ; 8 bytes of palette data.
  94. bne -
  95. ; TODO(mcmillen): make the "DMA stuff into VRAM" a macro or function.
  96. ; Set VMADDR to where we want the DMA to start. We'll store sprite data
  97. ; at the beginning of VRAM.
  98. ldx #$0000
  99. stx VMADDR
  100. ; DMA 0 source address & bank.
  101. ldx #SpriteData
  102. stx DMA0SRC
  103. lda #:SpriteData
  104. sta DMA0SRCBANK
  105. ; DMA 0 transfer size. Equal to the size of sprites32.pic.
  106. ldx #2048
  107. stx DMA0SIZE
  108. ; DMA 0 control register.
  109. ; Transfer type 001 = 2 addresses, LH.
  110. lda #%00000001
  111. sta DMA0CTRL
  112. ; DMA 0 destination.
  113. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  114. sta DMA0DST
  115. ; Enable DMA channel 0.
  116. lda #%00000001
  117. sta DMAENABLE
  118. ; Store background tile data at byte $2000 of VRAM.
  119. ; (VMADDR is a word address, so multiply by 2 to get the byte address.)
  120. ldx #$1000
  121. stx VMADDR
  122. ; DMA 0 source address & bank.
  123. ldx #TileData
  124. stx DMA0SRC
  125. lda #:TileData
  126. sta DMA0SRCBANK
  127. ; DMA 0 transfer size. Equal to the size of tiles.pic.
  128. ldx #512
  129. stx DMA0SIZE
  130. ; DMA 0 control register.
  131. ; Transfer type 001 = 2 addresses, LH.
  132. lda #%00000001
  133. sta DMA0CTRL
  134. ; DMA 0 destination.
  135. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  136. sta DMA0DST
  137. ; Enable DMA channel 0.
  138. lda #%00000001
  139. sta DMAENABLE
  140. ; Tell the system that the BG3 tilemap starts at $4000.
  141. lda #%00100000
  142. sta BG3TILEMAP
  143. ; ... and that the background tile data for BG3 starts at $2000.
  144. lda #%00000001
  145. sta BG34NBA
  146. ; Set up the BG3 tilemap.
  147. ; VRAM write mode: increments the address every time we write a word.
  148. lda #%10000000
  149. sta VMAIN
  150. ; Set word address for accessing VRAM.
  151. ldx #$2000 ; BG 3 tilemap starts here. (Byte address $4000.)
  152. stx VMADDR
  153. ; Now write entries into the tile map.
  154. ldy #0
  155. -
  156. GetRandomByte
  157. sta $00
  158. ldx #$0000 ; This is a blank tile.
  159. ; 1 in 8 chance that we choose a non-blank tile.
  160. bit #%00000111
  161. bne +
  162. ldx #$0002
  163. bit #%10000000
  164. bne +
  165. ldx #$8002 ; Flip vertically.
  166. +
  167. stx VMDATA
  168. iny
  169. ; The tile map is 32x32 (1024 entries).
  170. cpy #1024
  171. bne -
  172. rts
  173. InitWorld:
  174. ; Start the background color as a dark blue.
  175. lda #4
  176. sta backgroundBlue
  177. ; Player's initial starting location and health.
  178. lda #(256 / 4)
  179. sta playerX
  180. lda #((224 - 32) / 2)
  181. sta playerY
  182. lda #20
  183. sta playerHealth
  184. ; (x-velocity, y-velocity) of 4 different player shot patterns.
  185. lda #6
  186. sta shotVelocityTable
  187. lda #0
  188. sta shotVelocityTable + 1
  189. lda #3
  190. sta shotVelocityTable + 2
  191. lda #3
  192. sta shotVelocityTable + 3
  193. lda #0
  194. sta shotVelocityTable + 4
  195. lda #6
  196. sta shotVelocityTable + 5
  197. lda #-3
  198. sta shotVelocityTable + 6
  199. lda #3
  200. sta shotVelocityTable + 7
  201. lda #-6
  202. sta shotVelocityTable + 8
  203. lda #0
  204. sta shotVelocityTable + 9
  205. lda #-3
  206. sta shotVelocityTable + 10
  207. lda #-3
  208. sta shotVelocityTable + 11
  209. lda #0
  210. sta shotVelocityTable + 12
  211. lda #-6
  212. sta shotVelocityTable + 13
  213. lda #3
  214. sta shotVelocityTable + 14
  215. lda #-3
  216. sta shotVelocityTable + 15
  217. rts
  218. MainLoop:
  219. lda #%10000001 ; Enable NMI interrupt & auto joypad read.
  220. sta NMITIMEN
  221. wai ; Wait for interrupt.
  222. lda #%00000001 ; Disable NMI interrupt while processing.
  223. sta NMITIMEN
  224. jsr JoypadRead
  225. jsr JoypadHandler
  226. jsr UpdateWorld
  227. jsr UpdateSprites
  228. jsr FillSecondarySpriteTable
  229. jsr SetBackgroundColor
  230. bra MainLoop
  231. JoypadRead:
  232. ; Load joypad registers into RAM for easy inspection & manipulation.
  233. -
  234. lda HVBJOY
  235. bit #$01 ; If auto-joypad read is happening, loop.
  236. bne -
  237. ldx JOY1L
  238. stx joy1
  239. ldx JOY2L
  240. stx joy2
  241. rts
  242. JoypadHandler:
  243. JoypadUp:
  244. lda joy1 + 1
  245. bit #$08 ; Up
  246. beq JoypadDown ; Button not pressed.
  247. lda playerY
  248. cmp #0
  249. beq JoypadDown ; Value saturated.
  250. dec playerY
  251. dec playerY
  252. JoypadDown:
  253. lda joy1 + 1
  254. bit #$04 ; Down
  255. beq JoypadLeft ; Button not pressed.
  256. lda playerY
  257. cmp #(224 - 32)
  258. beq JoypadLeft ; Value saturated.
  259. inc playerY
  260. inc playerY
  261. JoypadLeft:
  262. lda joy1 + 1
  263. bit #$02 ; Left
  264. beq JoypadRight ; Button not pressed.
  265. lda playerX
  266. cmp #0
  267. beq JoypadRight ; Value saturated.
  268. dec playerX
  269. dec playerX
  270. JoypadRight:
  271. lda joy1 + 1
  272. bit #$01 ; Right
  273. beq JoypadStart ; Button not pressed.
  274. lda playerX
  275. cmp #(256 - 32)
  276. beq JoypadStart ; Value saturated.
  277. inc playerX
  278. inc playerX
  279. JoypadStart:
  280. lda joy1 + 1
  281. bit #$10 ; Start
  282. beq JoypadSelect ; Button not pressed.
  283. lda backgroundRed
  284. cmp #31
  285. beq JoypadSelect ; Value saturated.
  286. inc backgroundRed
  287. JoypadSelect:
  288. lda joy1 + 1
  289. bit #$20 ; Select
  290. beq JoypadY ; Button not pressed.
  291. lda backgroundRed
  292. cmp #0
  293. beq JoypadY ; Value saturated.
  294. dec backgroundRed
  295. JoypadY:
  296. lda joy1 + 1
  297. bit #$40 ; Y
  298. beq JoypadX ; Button not pressed.
  299. lda backgroundGreen
  300. cmp #0
  301. beq JoypadX ; Value saturated.
  302. dec backgroundGreen
  303. JoypadX:
  304. lda joy1
  305. bit #$40 ; X
  306. beq JoypadL ; Button not pressed.
  307. lda backgroundGreen
  308. cmp #31
  309. beq JoypadL ; Value saturated.
  310. inc backgroundGreen
  311. JoypadL:
  312. lda joy1
  313. bit #$20 ; L
  314. beq JoypadR ; Button not pressed.
  315. lda backgroundBlue
  316. cmp #0
  317. beq JoypadR ; Value saturated.
  318. dec backgroundBlue
  319. JoypadR:
  320. lda joy1
  321. bit #$10 ; R
  322. beq JoypadB ; Button not pressed.
  323. lda backgroundBlue
  324. cmp #31
  325. beq JoypadB ; Value saturated.
  326. inc backgroundBlue
  327. JoypadB:
  328. lda joy1 + 1
  329. bit #$80 ; B
  330. beq JoypadDone
  331. jsr MaybeShoot
  332. JoypadDone:
  333. rts
  334. MaybeShoot:
  335. ; If the cooldown timer is non-zero, don't shoot.
  336. lda shotCooldown
  337. cmp #0
  338. bne MaybeShootDone
  339. ; Find the first empty spot in the shots array.
  340. ldx #playerShotArray
  341. -
  342. lda 0, X
  343. cmp #0
  344. beq +
  345. .rept shotSize
  346. inx
  347. .endr
  348. ; If we went all the way to the end, bail out.
  349. cpx #(playerShotArray + playerShotArrayLength * shotSize)
  350. beq MaybeShootDone
  351. bra -
  352. +
  353. ; Enable shot; set its position based on player position.
  354. ; TODO(mcmillen): it might be easier/faster to keep N arrays: one for each
  355. ; field of shot (shotSpriteArray, shotXArray, shotYArray, ...)
  356. lda #8 ; Sprite number.
  357. sta 0, X
  358. lda playerX
  359. clc
  360. adc #28
  361. sta 1, X
  362. lda playerY
  363. clc
  364. adc #14
  365. sta 2, X
  366. ; Get x- and y-velocity out of shotVelocityTable.
  367. lda nextShotState
  368. and #%00000000 ; 8 possibilities if we use #%00000111.
  369. ldy #0
  370. -
  371. cmp #0
  372. beq +
  373. .rept 2
  374. iny
  375. .endr
  376. dec A
  377. bra -
  378. +
  379. inc nextShotState
  380. ; x-velocity.
  381. lda shotVelocityTable, Y
  382. sta 3, X
  383. ; y-velocity.
  384. lda shotVelocityTable + 1, Y
  385. sta 4, X
  386. ; Set cooldown timer.
  387. lda #8
  388. sta shotCooldown
  389. MaybeShootDone:
  390. rts
  391. UpdateWorld:
  392. jsr UpdateShotCooldown
  393. jsr SpawnEnemyShips
  394. jsr SpawnEnemyShots
  395. jsr UpdateEnemyShips
  396. jsr UpdateShotPositions
  397. jsr CheckCollisionsWithPlayer
  398. jsr UpdateBackgroundScroll
  399. rts
  400. UpdateShotCooldown:
  401. ; Update shot cooldown.
  402. lda shotCooldown
  403. cmp #0
  404. beq +
  405. dec A
  406. sta shotCooldown
  407. +
  408. rts
  409. SpawnEnemyShips:
  410. GetRandomByte
  411. bit #%00111111 ; Spawn ships every this-many frames (on average).
  412. beq +
  413. rts
  414. +
  415. ; Find an empty spot in the array.
  416. ldy #0
  417. -
  418. lda enemyShipArray, Y
  419. cmp #0
  420. beq +
  421. .rept enemyShipSize
  422. iny
  423. .endr
  424. cpy #(enemyShipArrayLength * enemyShipSize)
  425. bne -
  426. rts ; Too many ships; bail.
  427. +
  428. lda #4 ; Sprite number.
  429. sta enemyShipArray, Y
  430. lda #(256 - 32)
  431. sta enemyShipArray + 1, Y ; x.
  432. -
  433. GetRandomByte
  434. cmp #(224 - 32)
  435. bcs - ; Keep trying.
  436. sta enemyShipArray + 2, Y ; y.
  437. lda #0
  438. sta enemyShipArray + 3, Y ; move AI type.
  439. sta enemyShipArray + 4, Y ; shoot AI type.
  440. lda #8
  441. sta enemyShipArray + 5, Y ; shot cooldown.
  442. rts
  443. ; TODO(mcmillen): reap ships if they move off the top, bottom, or right too.
  444. UpdateEnemyShips:
  445. ldy #0
  446. --
  447. lda enemyShipArray, Y
  448. cmp #0
  449. beq ++
  450. lda enemyShipArray + 1, Y ; x
  451. clc
  452. adc #-2
  453. bcs +
  454. lda #0
  455. sta enemyShipArray, Y ; reap it.
  456. bra ++
  457. +
  458. sta enemyShipArray + 1, Y
  459. ++
  460. .rept enemyShipSize
  461. iny
  462. .endr
  463. cpy #(enemyShipArrayLength * enemyShipSize)
  464. bne --
  465. rts
  466. SpawnEnemyShots:
  467. lda vBlankCounter
  468. bit #%00001111 ; Spawn shots every this-many frames.
  469. beq +
  470. rts
  471. +
  472. ; Find an empty spot in the array.
  473. ldy #0
  474. -
  475. lda enemyShotArray, Y
  476. cmp #0
  477. beq +
  478. .rept 6
  479. iny
  480. .endr
  481. cpy #(enemyShotArrayLength * shotSize)
  482. bne -
  483. rts ; Too many shots; bail.
  484. +
  485. lda #9 ; Sprite number.
  486. sta enemyShotArray, Y
  487. lda #254
  488. sta enemyShotArray + 1, Y ; x.
  489. lda #((224 - 32) / 2)
  490. and #%01111111
  491. sta enemyShotArray + 2, Y ; y.
  492. lda #-4
  493. sta enemyShotArray + 3, Y ; x-velocity.
  494. GetRandomByte
  495. and #%00000111 ; [0, 7]
  496. clc
  497. adc #-3 ; [-3, 4]
  498. cmp #4
  499. bne +
  500. lda #0 ; [-3, 3] with 2x chance of zero.
  501. +
  502. sta enemyShotArray + 4, Y ; y-velocity.
  503. rts
  504. UpdateShotPositions:
  505. ldx #0
  506. UpdateShot: ; Updates position of one shot.
  507. lda playerShotArray, X
  508. cmp #0
  509. beq ShotDone
  510. ; Add to the x-coordinate. If the carry bit is set, we went off the edge
  511. ; of the screen, so disable the shot.
  512. lda playerShotArray + 3, X ; x-velocity.
  513. sta $00
  514. bit #%10000000 ; Check whether the velocity is negative.
  515. bne UpdateShotWithNegativeXVelocity
  516. lda playerShotArray + 1, X
  517. clc
  518. adc $00
  519. bcs DisableShot
  520. sta playerShotArray + 1, X ; Store new x-coord.
  521. bra UpdateShotY
  522. UpdateShotWithNegativeXVelocity:
  523. ; TODO(mcmillen): wrap sprites when they go negative here, like we do
  524. ; with y-velocities.
  525. lda playerShotArray + 1, X ; Current x.
  526. clc
  527. adc $00
  528. bcc DisableShot
  529. sta playerShotArray + 1, X
  530. UpdateShotY:
  531. ; Add to the y-coordinate.
  532. lda playerShotArray + 4, X ; y-velocity.
  533. sta $00
  534. bit #%10000000 ; Check whether the velocity is negative.
  535. bne UpdateShotWithNegativeYVelocity
  536. lda playerShotArray + 2, X
  537. clc
  538. adc $00
  539. cmp #224
  540. bcs DisableShot
  541. sta playerShotArray + 2, X ; Store new y-coord.
  542. bra ShotDone
  543. UpdateShotWithNegativeYVelocity:
  544. lda playerShotArray + 2, X ; Current y.
  545. cmp #224
  546. bcs + ; If the shot was "off the top" before moving, maybe we'll reap it.
  547. adc $00 ; Otherwise, just update it,
  548. sta playerShotArray + 2, X ; save the result,
  549. bra ShotDone ; and we know it shouldn't be reaped.
  550. +
  551. clc
  552. adc $00
  553. cmp #224
  554. bcc DisableShot ; If it's now wrapped around, reap it.
  555. sta playerShotArray + 2, X
  556. bra ShotDone
  557. DisableShot:
  558. stz playerShotArray, X
  559. ShotDone:
  560. ; TODO(mcmillen): in places where we .rept inx (etc), is it faster to use
  561. ; actual addition?
  562. .rept shotSize
  563. inx
  564. .endr
  565. cpx #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  566. bne UpdateShot
  567. rts
  568. CheckCollisionsWithPlayer:
  569. ; Store player position statically.
  570. clc
  571. lda playerX
  572. adc #16 ; Can't overflow.
  573. sta $00 ; Store the center.
  574. lda playerY
  575. ; Store the center. Our ship is actually 31 pixels tall, so offsetting by
  576. ; 15 feels more "fair": a shot that hits the invisible bottom edge of the
  577. ; ship won't count as a hit.
  578. adc #15
  579. sta $01
  580. ldx #0
  581. --
  582. lda enemyShotArray, X
  583. cmp #0 ; Check whether it's active.
  584. beq ++
  585. ; Find dx.
  586. lda enemyShotArray + 1, X ; x.
  587. clc
  588. adc #2 ; Get the center of the shot.
  589. sbc $00
  590. bpl + ; If the result is positive, great!
  591. eor #$ff ; Otherwise, negate it.
  592. inc A
  593. +
  594. ; A now contains dx, guaranteed to be positive.
  595. cmp #18 ; Threshold for "successful hit".
  596. bcs ++ ; Already too far; bail.
  597. sta $02
  598. ; Find dy.
  599. lda enemyShotArray + 2, X ; y.
  600. clc
  601. adc #2
  602. sbc $01
  603. bpl + ; If the result is positive, great!
  604. eor #$ff ; Otherwise, negate it.
  605. inc A
  606. +
  607. ; A now contains dy, guaranteed to be positive.
  608. clc
  609. adc $02 ; Add dx.
  610. cmp #18 ; Threshold for "successful hit".
  611. bcs ++
  612. ; OK, we got a hit!
  613. ; Disable the shot.
  614. lda #0
  615. sta enemyShotArray, X
  616. ; And decrement the player's life.
  617. lda playerHealth
  618. cmp #0
  619. beq ++
  620. dec playerHealth
  621. ++
  622. .rept shotSize
  623. inx
  624. .endr
  625. cpx #(enemyShotArrayLength * shotSize)
  626. bne --
  627. rts
  628. UpdateBackgroundScroll:
  629. ; Make the background scroll. Horizontal over time; vertical depending on
  630. ; player's y-coordinate.
  631. lda vBlankCounter
  632. sta BG3HOFS
  633. lda vBlankCounter + 1
  634. sta BG3HOFS
  635. lda playerY
  636. .rept 3
  637. lsr
  638. .endr
  639. sta BG3VOFS
  640. stz BG3VOFS
  641. rts
  642. UpdateSprites: ; TODO(mcmillen): refactor into smaller pieces.
  643. ; This page is a good reference on SNES sprite formats:
  644. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  645. ; It uses the same approach we're using, in which we keep a buffer of the
  646. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  647. ; during VBlank.
  648. ; Sprite table 1 has 4 bytes per sprite, laid out as follows:
  649. ; Byte 1: xxxxxxxx x: X coordinate
  650. ; Byte 2: yyyyyyyy y: Y coordinate
  651. ; Byte 3: cccccccc c: Starting tile #
  652. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  653. ; p: palette #
  654. ; Sprite table 2 has 2 bits per sprite, like so:
  655. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  656. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  657. ; Setting all the high bits keeps the sprites offscreen.
  658. ; Zero out the scratch space for the secondary sprite table.
  659. ldx #0
  660. -
  661. stz spriteTableScratchStart, X
  662. inx
  663. cpx #numSprites
  664. bne -
  665. ldx #0 ; Index into sprite table 1.
  666. ldy #0 ; Index into sprite table 2.
  667. ; Copy player coords into sprite table.
  668. lda playerX
  669. sta spriteTableStart, X
  670. lda playerY
  671. sta spriteTableStart + 1, X
  672. lda #0
  673. sta spriteTableStart + 2, X
  674. ; Set priority bits so that the sprite is drawn in front.
  675. lda #%00010000
  676. sta spriteTableStart + 3, X
  677. lda #%11000000 ; Enable large sprite.
  678. sta spriteTableScratchStart, Y
  679. .rept 4
  680. inx
  681. .endr
  682. iny
  683. ; Now add enemy ships.
  684. sty $00 ; Save sprite table 2 index.
  685. ldy #0 ; Index into enemyShipArray.
  686. -
  687. lda enemyShipArray, Y
  688. cmp #0 ; If not enabled, skip to next ship.
  689. beq +
  690. ; Update sprite table 1.
  691. sta spriteTableStart + 2, X ; sprite number
  692. lda enemyShipArray + 1, Y
  693. sta spriteTableStart, X ; x
  694. lda enemyShipArray + 2, Y
  695. sta spriteTableStart + 1, X ; y
  696. lda #%01000000 ; flip horizontally.
  697. sta spriteTableStart + 3, X
  698. ; Update secondary sprite table.
  699. phy ; Save enemyShipArray index.
  700. ldy $00
  701. lda #%11000000 ; Enable large sprite.
  702. sta spriteTableScratchStart, Y
  703. iny
  704. sty $00
  705. ply ; Restore enemyShipArray index.
  706. .rept 4
  707. inx
  708. .endr
  709. +
  710. .rept enemyShipSize
  711. iny
  712. .endr
  713. cpy #(enemyShipArrayLength * enemyShipSize)
  714. bne -
  715. ldy $00 ; Restore Y to its rightful self.
  716. ; Now add shots.
  717. sty $00 ; Save sprite table 2 index.
  718. ldy #0 ; Index into playerShotArray.
  719. -
  720. lda playerShotArray, Y
  721. cmp #0
  722. beq + ; If not enabled, skip to next shot.
  723. ; Update sprite table 1.
  724. sta spriteTableStart + 2, X ; sprite number
  725. lda playerShotArray + 1, Y
  726. sta spriteTableStart, X ; x
  727. lda playerShotArray + 2, Y
  728. sta spriteTableStart + 1, X ; y
  729. ; Update secondary sprite table.
  730. phy ; Save playerShotArray index.
  731. ldy $00
  732. lda #%01000000 ; Enable small sprite.
  733. sta spriteTableScratchStart, Y
  734. iny
  735. sty $00
  736. ply ; Restore playerShotArray index.
  737. .rept 4
  738. inx
  739. .endr
  740. +
  741. .rept shotSize
  742. iny
  743. .endr
  744. cpy #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  745. bne -
  746. ldy $00 ; Restore Y to its rightful self.
  747. ; Now add sprites to show player health.
  748. ; TODO(mcmillen): why aren't they in front?
  749. stz $01
  750. lda #4
  751. sta $02
  752. -
  753. lda $01
  754. cmp playerHealth
  755. beq + ; All done?
  756. lda #10
  757. sta spriteTableStart + 2, X ; sprite number
  758. lda $02
  759. sta spriteTableStart, X ; x
  760. clc
  761. adc #7
  762. sta $02
  763. lda #212
  764. sta spriteTableStart + 1, X ; y
  765. ; Set priority bits so that the sprite is drawn in front.
  766. lda #%00110000
  767. sta spriteTableStart + 3, X
  768. lda #%01000000 ; Enable small sprite.
  769. sta spriteTableScratchStart, Y
  770. .rept 4
  771. inx
  772. .endr
  773. iny
  774. inc $01
  775. bra -
  776. +
  777. ; Now clear out the unused entries in the sprite table.
  778. -
  779. cpx #spriteTable1Size
  780. beq +
  781. lda #1
  782. sta spriteTableStart, X
  783. .rept 4
  784. inx
  785. .endr
  786. bra -
  787. +
  788. rts
  789. FillSecondarySpriteTable:
  790. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  791. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  792. ; It's annoying to deal with bitfields when thinking about business logic,
  793. ; so the spriteTableScratch array contains one byte for each sprite, in
  794. ; which the two most significant bits are the "size" and "upper x" bits.
  795. ; This function is meant to be called after UpdateWorld, and packs those
  796. ; bytes into the actual bitfield that the OAM wants for the secondary
  797. ; sprite table.
  798. ;
  799. ; The expected format of every byte in the scratch sprite table is:
  800. ; sx------ s = size (0 = small, 1 = large)
  801. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  802. ldx #0 ; Index into input table.
  803. ldy #0 ; Index into output table.
  804. -
  805. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  806. .rept 4
  807. ; For each byte, the lower-order bits correspond to the lower-numbered
  808. ; sprites; therefore we insert the current sprite's bits "at the top"
  809. ; and shift them right for each successive sprite.
  810. lsr $00
  811. lsr $00
  812. lda spriteTableScratchStart, X
  813. ora $00
  814. sta $00
  815. inx
  816. .endr
  817. lda $00
  818. eor #%01010101
  819. sta spriteTable2Start, Y
  820. iny
  821. cpx #numSprites
  822. bne -
  823. rts
  824. SetBackgroundColor:
  825. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  826. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  827. ; Set the background color.
  828. ; Entry 0 corresponds to the SNES background color.
  829. stz CGADDR
  830. ; Compute and the low-order byte and store it in CGDATA.
  831. lda backgroundGreen
  832. .rept 5
  833. asl
  834. .endr
  835. ora backgroundRed
  836. sta CGDATA
  837. ; Compute the high-order byte and store it in CGDATA.
  838. lda backgroundBlue
  839. .rept 2
  840. asl
  841. .endr
  842. sta $00
  843. lda backgroundGreen
  844. .rept 3
  845. lsr
  846. .endr
  847. ora $00
  848. sta CGDATA
  849. rts
  850. VBlankHandler:
  851. jsr VBlankCounter
  852. jsr DMASpriteTables
  853. rti
  854. VBlankCounter:
  855. ; Increment a counter of how many VBlanks we've done.
  856. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  857. ; 77 hours to wrap around; that's good enough for me :)
  858. inc vBlankCounter
  859. bne +
  860. inc vBlankCounter + 1
  861. bne +
  862. inc vBlankCounter + 2
  863. +
  864. rts
  865. DMASpriteTables:
  866. ; Store at the base OAM address.
  867. ldx #$0000
  868. stx OAMADDR
  869. ; Default DMA control; destination $2104 (OAM data register).
  870. stz DMA0CTRL
  871. lda #$04
  872. sta DMA0DST
  873. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  874. ldx #spriteTableStart
  875. stx DMA0SRC
  876. stz DMA0SRCBANK
  877. ldx #spriteTableSize
  878. stx DMA0SIZE
  879. ; Kick off the DMA transfer.
  880. lda #%00000001
  881. sta DMAENABLE
  882. rts
  883. .ENDS
  884. ; Bank 1 is used for our graphics assets.
  885. .BANK 1 SLOT 0
  886. .ORG 0
  887. .SECTION "GraphicsData"
  888. SpriteData:
  889. .INCBIN "sprites32.pic"
  890. SpritePalette:
  891. .INCBIN "sprites32.clr"
  892. TileData:
  893. .INCBIN "tiles.pic"
  894. TilePalette:
  895. .INCBIN "tiles.clr"
  896. .ENDS
  897. ; Fill an entire bank with random numbers.
  898. .SEED 1
  899. .BANK 2 SLOT 0
  900. .ORG 0
  901. .SECTION "RandomBytes"
  902. .DBRND 32 * 1024, 0, 255
  903. .ENDS