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.

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