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.

1123 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: 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: 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: 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. ; Initial enemy ship-spawn cooldown.
  179. lda #30
  180. sta enemyShipSpawnCooldown
  181. ; Player's initial starting location and health.
  182. lda #(256 / 4)
  183. sta playerX
  184. lda #((224 - 32) / 2)
  185. sta playerY
  186. lda #20
  187. sta playerHealth
  188. ; (x-velocity, y-velocity) of 4 different player shot patterns.
  189. lda #6
  190. sta shotVelocityTable
  191. lda #0
  192. sta shotVelocityTable + 1
  193. lda #3
  194. sta shotVelocityTable + 2
  195. lda #3
  196. sta shotVelocityTable + 3
  197. lda #0
  198. sta shotVelocityTable + 4
  199. lda #6
  200. sta shotVelocityTable + 5
  201. lda #-3
  202. sta shotVelocityTable + 6
  203. lda #3
  204. sta shotVelocityTable + 7
  205. lda #-6
  206. sta shotVelocityTable + 8
  207. lda #0
  208. sta shotVelocityTable + 9
  209. lda #-3
  210. sta shotVelocityTable + 10
  211. lda #-3
  212. sta shotVelocityTable + 11
  213. lda #0
  214. sta shotVelocityTable + 12
  215. lda #-6
  216. sta shotVelocityTable + 13
  217. lda #3
  218. sta shotVelocityTable + 14
  219. lda #-3
  220. sta shotVelocityTable + 15
  221. rts
  222. MainLoop:
  223. lda #%10000001 ; Enable NMI interrupt & auto joypad read.
  224. sta NMITIMEN
  225. wai ; Wait for interrupt.
  226. lda #%00000001 ; Disable NMI interrupt while processing.
  227. sta NMITIMEN
  228. jsr JoypadRead
  229. jsr UpdateWorld
  230. jsr UpdateSprites
  231. jsr FillSecondarySpriteTable
  232. jsr SetBackgroundColor
  233. bra MainLoop
  234. JoypadRead:
  235. ; Load joypad registers into RAM for easy inspection & manipulation.
  236. -
  237. lda HVBJOY
  238. bit #$01 ; If auto-joypad read is happening, loop.
  239. bne -
  240. ldx JOY1L
  241. stx joy1
  242. ldx JOY2L
  243. stx joy2
  244. rts
  245. JoypadHandler:
  246. JoypadUp:
  247. lda joy1 + 1
  248. bit #$08 ; Up
  249. beq JoypadDown ; Button not pressed.
  250. lda playerY
  251. cmp #0
  252. beq JoypadDown ; Value saturated.
  253. dec playerY
  254. dec playerY
  255. JoypadDown:
  256. lda joy1 + 1
  257. bit #$04 ; Down
  258. beq JoypadLeft ; Button not pressed.
  259. lda playerY
  260. cmp #(224 - 32)
  261. beq JoypadLeft ; Value saturated.
  262. inc playerY
  263. inc playerY
  264. JoypadLeft:
  265. lda joy1 + 1
  266. bit #$02 ; Left
  267. beq JoypadRight ; Button not pressed.
  268. lda playerX
  269. cmp #0
  270. beq JoypadRight ; Value saturated.
  271. dec playerX
  272. dec playerX
  273. JoypadRight:
  274. lda joy1 + 1
  275. bit #$01 ; Right
  276. beq JoypadStart ; Button not pressed.
  277. lda playerX
  278. cmp #(256 - 32)
  279. beq JoypadStart ; Value saturated.
  280. inc playerX
  281. inc playerX
  282. JoypadStart:
  283. lda joy1 + 1
  284. bit #$10 ; Start
  285. beq JoypadSelect ; Button not pressed.
  286. lda backgroundRed
  287. cmp #31
  288. beq JoypadSelect ; Value saturated.
  289. inc backgroundRed
  290. JoypadSelect:
  291. lda joy1 + 1
  292. bit #$20 ; Select
  293. beq JoypadY ; Button not pressed.
  294. lda backgroundRed
  295. cmp #0
  296. beq JoypadY ; Value saturated.
  297. dec backgroundRed
  298. JoypadY:
  299. lda joy1 + 1
  300. bit #$40 ; Y
  301. beq JoypadX ; Button not pressed.
  302. lda backgroundGreen
  303. cmp #0
  304. beq JoypadX ; Value saturated.
  305. dec backgroundGreen
  306. JoypadX:
  307. lda joy1
  308. bit #$40 ; X
  309. beq JoypadL ; Button not pressed.
  310. lda backgroundGreen
  311. cmp #31
  312. beq JoypadL ; Value saturated.
  313. inc backgroundGreen
  314. JoypadL:
  315. lda joy1
  316. bit #$20 ; L
  317. beq JoypadR ; Button not pressed.
  318. lda backgroundBlue
  319. cmp #0
  320. beq JoypadR ; Value saturated.
  321. dec backgroundBlue
  322. JoypadR:
  323. lda joy1
  324. bit #$10 ; R
  325. beq JoypadB ; Button not pressed.
  326. lda backgroundBlue
  327. cmp #31
  328. beq JoypadB ; Value saturated.
  329. inc backgroundBlue
  330. JoypadB:
  331. lda joy1 + 1
  332. bit #$80 ; B
  333. beq JoypadDone
  334. jsr MaybeShoot
  335. JoypadDone:
  336. rts
  337. MaybeShoot:
  338. ; If the cooldown timer is non-zero, don't shoot.
  339. lda shotCooldown
  340. cmp #0
  341. bne MaybeShootDone
  342. ; Find the first empty spot in the shots array.
  343. ldx #playerShotArray
  344. -
  345. lda 0, X
  346. cmp #0
  347. beq +
  348. .rept shotSize
  349. inx
  350. .endr
  351. ; If we went all the way to the end, bail out.
  352. cpx #(playerShotArray + playerShotArrayLength * shotSize)
  353. bne -
  354. rts
  355. +
  356. ; Enable shot; set its position based on player position.
  357. ; TODO: it might be easier/faster to keep N arrays: one for each
  358. ; field of shot (shotSpriteArray, shotXArray, shotYArray, ...)
  359. lda #8 ; Sprite number.
  360. sta 0, X
  361. lda playerX
  362. clc
  363. adc #28
  364. sta 1, X
  365. lda playerY
  366. clc
  367. adc #14
  368. sta 2, X
  369. ; Get x- and y-velocity out of shotVelocityTable.
  370. lda nextShotState
  371. and #%00000000 ; 8 possibilities if we use #%00000111.
  372. ldy #0
  373. -
  374. cmp #0
  375. beq +
  376. .rept 2
  377. iny
  378. .endr
  379. dec A
  380. bra -
  381. +
  382. inc nextShotState
  383. ; x-velocity.
  384. lda shotVelocityTable, Y
  385. sta 3, X
  386. ; y-velocity.
  387. lda shotVelocityTable + 1, Y
  388. sta 4, X
  389. ; Set cooldown timer.
  390. lda #8
  391. sta shotCooldown
  392. MaybeShootDone:
  393. rts
  394. UpdateWorld:
  395. jsr UpdateShotCooldown
  396. jsr UpdateShotPositions
  397. jsr JoypadHandler
  398. jsr SpawnEnemyShips
  399. jsr UpdateEnemyShips
  400. jsr CheckCollisionsWithPlayer
  401. jsr UpdateBackgroundScroll
  402. rts
  403. UpdateShotCooldown:
  404. ; Update shot cooldown.
  405. lda shotCooldown
  406. cmp #0
  407. beq +
  408. dec A
  409. sta shotCooldown
  410. +
  411. rts
  412. SpawnEnemyShips:
  413. lda enemyShipSpawnCooldown
  414. cmp #0
  415. beq +
  416. dec A
  417. sta enemyShipSpawnCooldown
  418. rts
  419. +
  420. GetRandomByte
  421. and #%00111111
  422. clc
  423. adc #32
  424. sta enemyShipSpawnCooldown
  425. ; Find an empty spot in the array.
  426. ldy #0
  427. -
  428. lda enemyShipArray, Y
  429. cmp #0
  430. beq +
  431. .rept enemyShipSize
  432. iny
  433. .endr
  434. cpy #(enemyShipArrayLength * enemyShipSize)
  435. bne -
  436. rts ; Too many ships; bail.
  437. +
  438. lda #4 ; Sprite number.
  439. sta enemyShipArray, Y
  440. lda #254
  441. sta enemyShipArray + 1, Y ; x.
  442. -
  443. GetRandomByte
  444. cmp #(224 - 32)
  445. bcs - ; Keep trying.
  446. sta enemyShipArray + 2, Y ; y.
  447. lda #0
  448. sta enemyShipArray + 3, Y ; move AI type.
  449. sta enemyShipArray + 4, Y ; shoot AI type.
  450. lda #12
  451. sta enemyShipArray + 5, Y ; shot cooldown.
  452. rts
  453. ; TODO: reap ships if they move off the top, bottom, or right too.
  454. UpdateEnemyShips:
  455. ldy #0 ; Index into enemyShipArray.
  456. sty $00 ; Index into enemyShotArray.
  457. --
  458. lda enemyShipArray, Y
  459. cmp #0 ; If it's not enabled, skip it.
  460. beq ++
  461. ; Move the ship.
  462. ; TODO: implement different movement based on AI-type.
  463. lda enemyShipArray + 1, Y ; x
  464. clc
  465. adc #-2 ; x-velocity.
  466. bcs +
  467. lda #0
  468. sta enemyShipArray, Y ; reap it.
  469. bra ++
  470. +
  471. sta enemyShipArray + 1, Y ; move it.
  472. lda enemyShipArray + 5, Y ; shot cooldown
  473. cmp #0
  474. beq +
  475. dec A
  476. sta enemyShipArray + 5, Y ; new shot cooldown
  477. bra ++
  478. + ; Add a shot.
  479. ; TODO: implement different shooting based on shoot-type.
  480. lda #12
  481. sta enemyShipArray + 5, Y ; new shot cooldown
  482. lda enemyShipArray + 1, Y
  483. sta $02 ; Enemy ship x.
  484. lda enemyShipArray + 2, Y
  485. sta $03 ; Enemy ship y.
  486. phy
  487. ldy $00
  488. jsr SpawnEnemyShot
  489. sty $00
  490. ply
  491. ++ ; Done processing this ship.
  492. .rept enemyShipSize
  493. iny
  494. .endr
  495. cpy #(enemyShipArrayLength * enemyShipSize)
  496. bne --
  497. rts
  498. ; Expects:
  499. ; Y: index into enemyShotArray (bytes).
  500. ; $02: enemy ship x-position.
  501. ; $03: enemy ship y-position.
  502. ;
  503. ; Modifies:
  504. ; A.
  505. ; Y: to point at the next possible free index into enemyShotArray.
  506. SpawnEnemyShot:
  507. -
  508. ; Bail if at end of array.
  509. cpy #(enemyShotArrayLength * shotSize)
  510. bne +
  511. rts
  512. +
  513. lda enemyShotArray, Y
  514. cmp #0
  515. beq +
  516. ; Try next slot.
  517. .rept shotSize
  518. iny
  519. .endr
  520. bra -
  521. +
  522. ; OK, found a spot.
  523. lda #9 ; Sprite number.
  524. sta enemyShotArray, Y
  525. lda $02 ; Get enemy x.
  526. sta enemyShotArray + 1, Y ; Save as shot x.
  527. lda $03 ; Get enemy y.
  528. clc
  529. adc #((32 - 4) / 2) ; Center it with enemy ship.
  530. sta enemyShotArray + 2, Y ; Save as shot y.
  531. lda #-6
  532. sta enemyShotArray + 3, Y ; x-velocity.
  533. lda #0
  534. sta enemyShotArray + 4, Y ; y-velocity.
  535. rts
  536. UpdateShotPositions:
  537. ldx #0
  538. UpdateShot: ; Updates position of one shot.
  539. lda playerShotArray, X
  540. cmp #0
  541. beq ShotDone
  542. ; Add to the x-coordinate. If the carry bit is set, we went off the edge
  543. ; of the screen, so disable the shot.
  544. lda playerShotArray + 3, X ; x-velocity.
  545. sta $00
  546. bit #%10000000 ; Check whether the velocity is negative.
  547. bne UpdateShotWithNegativeXVelocity
  548. lda playerShotArray + 1, X
  549. clc
  550. adc $00
  551. bcs DisableShot
  552. sta playerShotArray + 1, X ; Store new x-coord.
  553. bra UpdateShotY
  554. UpdateShotWithNegativeXVelocity:
  555. ; TODO: wrap sprites when they go negative here, like we do with
  556. ; y-velocities.
  557. lda playerShotArray + 1, X ; Current x.
  558. clc
  559. adc $00
  560. bcc DisableShot
  561. sta playerShotArray + 1, X
  562. UpdateShotY:
  563. ; Add to the y-coordinate.
  564. lda playerShotArray + 4, X ; y-velocity.
  565. sta $00
  566. bit #%10000000 ; Check whether the velocity is negative.
  567. bne UpdateShotWithNegativeYVelocity
  568. lda playerShotArray + 2, X
  569. clc
  570. adc $00
  571. cmp #224
  572. bcs DisableShot
  573. sta playerShotArray + 2, X ; Store new y-coord.
  574. bra ShotDone
  575. UpdateShotWithNegativeYVelocity:
  576. lda playerShotArray + 2, X ; Current y.
  577. cmp #224
  578. bcs + ; If the shot was "off the top" before moving, maybe we'll reap it.
  579. adc $00 ; Otherwise, just update it,
  580. sta playerShotArray + 2, X ; save the result,
  581. bra ShotDone ; and we know it shouldn't be reaped.
  582. +
  583. clc
  584. adc $00
  585. cmp #224
  586. bcc DisableShot ; If it's now wrapped around, reap it.
  587. sta playerShotArray + 2, X
  588. bra ShotDone
  589. DisableShot:
  590. stz playerShotArray, X
  591. ShotDone:
  592. ; TODO: in places where we .rept inx (etc), is it faster to use actual
  593. ; addition?
  594. .rept shotSize
  595. inx
  596. .endr
  597. cpx #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  598. bne UpdateShot
  599. rts
  600. CheckCollisionsWithPlayer:
  601. ; Store player position statically.
  602. clc
  603. lda playerX
  604. adc #16 ; Can't overflow.
  605. sta $00 ; Store the center.
  606. lda playerY
  607. ; Store the center. Our ship is actually 31 pixels tall, so offsetting by
  608. ; 15 feels more "fair": a shot that hits the invisible bottom edge of the
  609. ; ship won't count as a hit.
  610. adc #15
  611. sta $01
  612. ldx #0
  613. --
  614. lda enemyShotArray, X
  615. cmp #0 ; Check whether it's active.
  616. beq ++
  617. ; Find dx.
  618. lda enemyShotArray + 1, X ; x.
  619. clc
  620. adc #2 ; Get the center of the shot.
  621. sbc $00
  622. bpl + ; If the result is positive, great!
  623. eor #$ff ; Otherwise, negate it.
  624. inc A
  625. +
  626. ; A now contains dx, guaranteed to be positive.
  627. cmp #18 ; Threshold for "successful hit".
  628. bcs ++ ; Already too far; bail.
  629. sta $02
  630. ; Find dy.
  631. lda enemyShotArray + 2, X ; y.
  632. clc
  633. adc #2
  634. sbc $01
  635. bpl + ; If the result is positive, great!
  636. eor #$ff ; Otherwise, negate it.
  637. inc A
  638. +
  639. ; A now contains dy, guaranteed to be positive.
  640. clc
  641. adc $02 ; Add dx.
  642. cmp #18 ; Threshold for "successful hit".
  643. bcs ++
  644. ; OK, we got a hit!
  645. ; Disable the shot.
  646. lda #0
  647. sta enemyShotArray, X
  648. ; And decrement the player's life.
  649. lda playerHealth
  650. cmp #0
  651. beq ++
  652. dec playerHealth
  653. ++
  654. .rept shotSize
  655. inx
  656. .endr
  657. cpx #(enemyShotArrayLength * shotSize)
  658. bne --
  659. rts
  660. UpdateBackgroundScroll:
  661. ; Make the background scroll. Horizontal over time; vertical depending on
  662. ; player's y-coordinate.
  663. lda vBlankCounter
  664. sta BG3HOFS
  665. lda vBlankCounter + 1
  666. sta BG3HOFS
  667. lda playerY
  668. .rept 3
  669. lsr
  670. .endr
  671. sta BG3VOFS
  672. stz BG3VOFS
  673. rts
  674. UpdateSprites: ; TODO: refactor into smaller pieces.
  675. ; This page is a good reference on SNES sprite formats:
  676. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  677. ; It uses the same approach we're using, in which we keep a buffer of the
  678. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  679. ; during VBlank.
  680. ; Sprite table 1 has 4 bytes per sprite, laid out as follows:
  681. ; Byte 1: xxxxxxxx x: X coordinate
  682. ; Byte 2: yyyyyyyy y: Y coordinate
  683. ; Byte 3: cccccccc c: Starting tile #
  684. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  685. ; p: palette #
  686. ; Sprite table 2 has 2 bits per sprite, like so:
  687. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  688. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  689. ; Setting all the high bits keeps the sprites offscreen.
  690. ; Zero out the scratch space for the secondary sprite table.
  691. ldx #0
  692. -
  693. stz spriteTableScratchStart, X
  694. inx
  695. cpx #numSprites
  696. bne -
  697. ldx #0 ; Index into sprite table 1.
  698. ldy #0 ; Index into sprite table 2.
  699. ; Copy player coords into sprite table.
  700. lda playerX
  701. sta spriteTableStart, X
  702. lda playerY
  703. sta spriteTableStart + 1, X
  704. lda #0
  705. sta spriteTableStart + 2, X
  706. ; Set priority bits so that the sprite is drawn in front.
  707. lda #%00010000
  708. sta spriteTableStart + 3, X
  709. lda #%11000000 ; Enable large sprite.
  710. sta spriteTableScratchStart, Y
  711. .rept 4
  712. inx
  713. .endr
  714. iny
  715. ; Now add enemy ships.
  716. sty $00 ; Save sprite table 2 index.
  717. ldy #0 ; Index into enemyShipArray.
  718. -
  719. lda enemyShipArray, Y
  720. cmp #0 ; If not enabled, skip to next ship.
  721. beq +
  722. ; Update sprite table 1.
  723. sta spriteTableStart + 2, X ; sprite number
  724. lda enemyShipArray + 1, Y
  725. sta spriteTableStart, X ; x
  726. lda enemyShipArray + 2, Y
  727. sta spriteTableStart + 1, X ; y
  728. lda #%01000000 ; flip horizontally.
  729. sta spriteTableStart + 3, X
  730. ; Update secondary sprite table.
  731. phy ; Save enemyShipArray index.
  732. ldy $00
  733. lda #%11000000 ; Enable large sprite.
  734. sta spriteTableScratchStart, Y
  735. iny
  736. sty $00
  737. ply ; Restore enemyShipArray index.
  738. .rept 4
  739. inx
  740. .endr
  741. +
  742. .rept enemyShipSize
  743. iny
  744. .endr
  745. cpy #(enemyShipArrayLength * enemyShipSize)
  746. bne -
  747. ldy $00 ; Restore Y to its rightful self.
  748. ; Now add shots.
  749. sty $00 ; Save sprite table 2 index.
  750. ldy #0 ; Index into playerShotArray.
  751. -
  752. lda playerShotArray, Y
  753. cmp #0
  754. beq + ; If not enabled, skip to next shot.
  755. ; Update sprite table 1.
  756. sta spriteTableStart + 2, X ; sprite number
  757. lda playerShotArray + 1, Y
  758. sta spriteTableStart, X ; x
  759. lda playerShotArray + 2, Y
  760. sta spriteTableStart + 1, X ; y
  761. ; Update secondary sprite table.
  762. phy ; Save playerShotArray index.
  763. ldy $00
  764. lda #%01000000 ; Enable small sprite.
  765. sta spriteTableScratchStart, Y
  766. iny
  767. sty $00
  768. ply ; Restore playerShotArray index.
  769. .rept 4
  770. inx
  771. .endr
  772. +
  773. .rept shotSize
  774. iny
  775. .endr
  776. cpy #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  777. bne -
  778. ldy $00 ; Restore Y to its rightful self.
  779. ; Now add sprites to show player health.
  780. ; TODO: why aren't they in front?
  781. stz $01
  782. lda #4
  783. sta $02
  784. -
  785. lda $01
  786. cmp playerHealth
  787. beq + ; All done?
  788. lda #10
  789. sta spriteTableStart + 2, X ; sprite number
  790. lda $02
  791. sta spriteTableStart, X ; x
  792. clc
  793. adc #7
  794. sta $02
  795. lda #212
  796. sta spriteTableStart + 1, X ; y
  797. ; Set priority bits so that the sprite is drawn in front.
  798. lda #%00110000
  799. sta spriteTableStart + 3, X
  800. lda #%01000000 ; Enable small sprite.
  801. sta spriteTableScratchStart, Y
  802. .rept 4
  803. inx
  804. .endr
  805. iny
  806. inc $01
  807. bra -
  808. +
  809. ; Now clear out the unused entries in the sprite table.
  810. -
  811. cpx #spriteTable1Size
  812. beq +
  813. lda #1
  814. sta spriteTableStart, X
  815. .rept 4
  816. inx
  817. .endr
  818. bra -
  819. +
  820. rts
  821. FillSecondarySpriteTable:
  822. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  823. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  824. ; It's annoying to deal with bitfields when thinking about business logic,
  825. ; so the spriteTableScratch array contains one byte for each sprite, in
  826. ; which the two most significant bits are the "size" and "upper x" bits.
  827. ; This function is meant to be called after UpdateWorld, and packs those
  828. ; bytes into the actual bitfield that the OAM wants for the secondary
  829. ; sprite table.
  830. ;
  831. ; The expected format of every byte in the scratch sprite table is:
  832. ; sx------ s = size (0 = small, 1 = large)
  833. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  834. ldx #0 ; Index into input table.
  835. ldy #0 ; Index into output table.
  836. -
  837. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  838. .rept 4
  839. ; For each byte, the lower-order bits correspond to the lower-numbered
  840. ; sprites; therefore we insert the current sprite's bits "at the top"
  841. ; and shift them right for each successive sprite.
  842. lsr $00
  843. lsr $00
  844. lda spriteTableScratchStart, X
  845. ora $00
  846. sta $00
  847. inx
  848. .endr
  849. lda $00
  850. eor #%01010101
  851. sta spriteTable2Start, Y
  852. iny
  853. cpx #numSprites
  854. bne -
  855. rts
  856. SetBackgroundColor:
  857. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  858. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  859. ; Set the background color.
  860. ; Entry 0 corresponds to the SNES background color.
  861. stz CGADDR
  862. ; Compute and the low-order byte and store it in CGDATA.
  863. lda backgroundGreen
  864. .rept 5
  865. asl
  866. .endr
  867. ora backgroundRed
  868. sta CGDATA
  869. ; Compute the high-order byte and store it in CGDATA.
  870. lda backgroundBlue
  871. .rept 2
  872. asl
  873. .endr
  874. sta $00
  875. lda backgroundGreen
  876. .rept 3
  877. lsr
  878. .endr
  879. ora $00
  880. sta CGDATA
  881. rts
  882. VBlankHandler:
  883. jsr VBlankCounter
  884. jsr DMASpriteTables
  885. rti
  886. VBlankCounter:
  887. ; Increment a counter of how many VBlanks we've done.
  888. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  889. ; 77 hours to wrap around; that's good enough for me :)
  890. inc vBlankCounter
  891. bne +
  892. inc vBlankCounter + 1
  893. bne +
  894. inc vBlankCounter + 2
  895. +
  896. rts
  897. DMASpriteTables:
  898. ; Store at the base OAM address.
  899. ldx #$0000
  900. stx OAMADDR
  901. ; Default DMA control; destination $2104 (OAM data register).
  902. stz DMA0CTRL
  903. lda #$04
  904. sta DMA0DST
  905. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  906. ldx #spriteTableStart
  907. stx DMA0SRC
  908. stz DMA0SRCBANK
  909. ldx #spriteTableSize
  910. stx DMA0SIZE
  911. ; Kick off the DMA transfer.
  912. lda #%00000001
  913. sta DMAENABLE
  914. rts
  915. .ENDS
  916. ; Bank 1 is used for our graphics assets.
  917. .BANK 1 SLOT 0
  918. .ORG 0
  919. .SECTION "GraphicsData"
  920. SpriteData:
  921. .INCBIN "sprites32.pic"
  922. SpritePalette:
  923. .INCBIN "sprites32.clr"
  924. TileData:
  925. .INCBIN "tiles.pic"
  926. TilePalette:
  927. .INCBIN "tiles.clr"
  928. .ENDS
  929. ; Fill an entire bank with random numbers.
  930. .SEED 1
  931. .BANK 2 SLOT 0
  932. .ORG 0
  933. .SECTION "RandomBytes"
  934. .DBRND 32 * 1024, 0, 255
  935. .ENDS