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.

1121 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 JoypadHandler
  230. jsr UpdateWorld
  231. jsr UpdateSprites
  232. jsr FillSecondarySpriteTable
  233. jsr SetBackgroundColor
  234. bra MainLoop
  235. JoypadRead:
  236. ; Load joypad registers into RAM for easy inspection & manipulation.
  237. -
  238. lda HVBJOY
  239. bit #$01 ; If auto-joypad read is happening, loop.
  240. bne -
  241. ldx JOY1L
  242. stx joy1
  243. ldx JOY2L
  244. stx joy2
  245. rts
  246. JoypadHandler:
  247. JoypadUp:
  248. lda joy1 + 1
  249. bit #$08 ; Up
  250. beq JoypadDown ; Button not pressed.
  251. lda playerY
  252. cmp #0
  253. beq JoypadDown ; Value saturated.
  254. dec playerY
  255. dec playerY
  256. JoypadDown:
  257. lda joy1 + 1
  258. bit #$04 ; Down
  259. beq JoypadLeft ; Button not pressed.
  260. lda playerY
  261. cmp #(224 - 32)
  262. beq JoypadLeft ; Value saturated.
  263. inc playerY
  264. inc playerY
  265. JoypadLeft:
  266. lda joy1 + 1
  267. bit #$02 ; Left
  268. beq JoypadRight ; Button not pressed.
  269. lda playerX
  270. cmp #0
  271. beq JoypadRight ; Value saturated.
  272. dec playerX
  273. dec playerX
  274. JoypadRight:
  275. lda joy1 + 1
  276. bit #$01 ; Right
  277. beq JoypadStart ; Button not pressed.
  278. lda playerX
  279. cmp #(256 - 32)
  280. beq JoypadStart ; Value saturated.
  281. inc playerX
  282. inc playerX
  283. JoypadStart:
  284. lda joy1 + 1
  285. bit #$10 ; Start
  286. beq JoypadSelect ; Button not pressed.
  287. lda backgroundRed
  288. cmp #31
  289. beq JoypadSelect ; Value saturated.
  290. inc backgroundRed
  291. JoypadSelect:
  292. lda joy1 + 1
  293. bit #$20 ; Select
  294. beq JoypadY ; Button not pressed.
  295. lda backgroundRed
  296. cmp #0
  297. beq JoypadY ; Value saturated.
  298. dec backgroundRed
  299. JoypadY:
  300. lda joy1 + 1
  301. bit #$40 ; Y
  302. beq JoypadX ; Button not pressed.
  303. lda backgroundGreen
  304. cmp #0
  305. beq JoypadX ; Value saturated.
  306. dec backgroundGreen
  307. JoypadX:
  308. lda joy1
  309. bit #$40 ; X
  310. beq JoypadL ; Button not pressed.
  311. lda backgroundGreen
  312. cmp #31
  313. beq JoypadL ; Value saturated.
  314. inc backgroundGreen
  315. JoypadL:
  316. lda joy1
  317. bit #$20 ; L
  318. beq JoypadR ; Button not pressed.
  319. lda backgroundBlue
  320. cmp #0
  321. beq JoypadR ; Value saturated.
  322. dec backgroundBlue
  323. JoypadR:
  324. lda joy1
  325. bit #$10 ; R
  326. beq JoypadB ; Button not pressed.
  327. lda backgroundBlue
  328. cmp #31
  329. beq JoypadB ; Value saturated.
  330. inc backgroundBlue
  331. JoypadB:
  332. lda joy1 + 1
  333. bit #$80 ; B
  334. beq JoypadDone
  335. jsr MaybeShoot
  336. JoypadDone:
  337. rts
  338. MaybeShoot:
  339. ; If the cooldown timer is non-zero, don't shoot.
  340. lda shotCooldown
  341. cmp #0
  342. bne MaybeShootDone
  343. ; Find the first empty spot in the shots array.
  344. ldx #playerShotArray
  345. -
  346. lda 0, X
  347. cmp #0
  348. beq +
  349. .rept shotSize
  350. inx
  351. .endr
  352. ; If we went all the way to the end, bail out.
  353. cpx #(playerShotArray + playerShotArrayLength * shotSize)
  354. beq MaybeShootDone
  355. bra -
  356. +
  357. ; Enable shot; set its position based on player position.
  358. ; TODO: it might be easier/faster to keep N arrays: one for each
  359. ; field of shot (shotSpriteArray, shotXArray, shotYArray, ...)
  360. lda #8 ; Sprite number.
  361. sta 0, X
  362. lda playerX
  363. clc
  364. adc #28
  365. sta 1, X
  366. lda playerY
  367. clc
  368. adc #14
  369. sta 2, X
  370. ; Get x- and y-velocity out of shotVelocityTable.
  371. lda nextShotState
  372. and #%00000000 ; 8 possibilities if we use #%00000111.
  373. ldy #0
  374. -
  375. cmp #0
  376. beq +
  377. .rept 2
  378. iny
  379. .endr
  380. dec A
  381. bra -
  382. +
  383. inc nextShotState
  384. ; x-velocity.
  385. lda shotVelocityTable, Y
  386. sta 3, X
  387. ; y-velocity.
  388. lda shotVelocityTable + 1, Y
  389. sta 4, X
  390. ; Set cooldown timer.
  391. lda #8
  392. sta shotCooldown
  393. MaybeShootDone:
  394. rts
  395. UpdateWorld:
  396. jsr UpdateShotCooldown
  397. jsr UpdateShotPositions
  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
  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 enemyShipArray + 1, Y
  481. sta $02 ; Enemy ship X.
  482. lda enemyShipArray + 2, Y
  483. sta $03 ; Enemy ship Y.
  484. lda #12
  485. sta enemyShipArray + 5, Y ; new shot cooldown
  486. phy
  487. jsr SpawnEnemyShot
  488. ply
  489. ++ ; Done processing this ship.
  490. .rept enemyShipSize
  491. iny
  492. .endr
  493. cpy #(enemyShipArrayLength * enemyShipSize)
  494. bne --
  495. rts
  496. ; Expects:
  497. ; $00: index into enemyShotArray (bytes).
  498. ; $02: enemy ship x-position.
  499. ; $03: enemy ship y-position.
  500. ;
  501. ; Modifies: A & Y.
  502. ; $00: new index into enemyShotArray (bytes).
  503. SpawnEnemyShot:
  504. ldy $00
  505. -
  506. ; Bail if at end of array.
  507. cpy #(enemyShotArrayLength * shotSize)
  508. bne +
  509. sty $00
  510. rts
  511. +
  512. lda enemyShotArray, Y
  513. cmp #0
  514. beq +
  515. ; Try next slot.
  516. .rept shotSize
  517. iny
  518. .endr
  519. bra -
  520. +
  521. ; OK, found a spot.
  522. lda #9 ; Sprite number.
  523. sta enemyShotArray, Y
  524. lda $02 ; Get enemy x.
  525. sta enemyShotArray + 1, Y ; Save as shot x.
  526. lda $03 ; Get enemy y.
  527. clc
  528. adc #((32 - 4) / 2) ; Center it with enemy ship.
  529. sta enemyShotArray + 2, Y ; Save as shot y.
  530. lda #-6
  531. sta enemyShotArray + 3, Y ; x-velocity.
  532. lda #0
  533. sta enemyShotArray + 4, Y ; y-velocity.
  534. sty $00
  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