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.

1443 lines
30 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
  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. ; Modifies X and Y to move to the next elements in the sprite tables.
  33. .MACRO AdvanceSpritePointers
  34. .rept 4
  35. inx
  36. .endr
  37. iny
  38. .ENDM
  39. .BANK 0 SLOT 0
  40. .ORG 0
  41. .SECTION "MainCode"
  42. Start:
  43. InitSNES
  44. ; By default we assume 16-bit X/Y and 8-bit A.
  45. ; If any code wants to change this, it's expected to do so itself,
  46. ; and to change them back to the defaults before returning.
  47. SetXY16Bit
  48. SetA8Bit
  49. jsr LoadPaletteAndTileData
  50. jsr InitWorld
  51. ; Set screen mode: 16x16 tiles for backgrounds, mode 1.
  52. lda #%11000001
  53. sta BGMODE
  54. ; Set sprite size to 8x8 (small) and 32x32 (large).
  55. lda #%00100000
  56. sta OAMSIZE
  57. ; Main screen: enable sprites & BG3.
  58. lda #%00010100
  59. sta MSENABLE
  60. ; Turn on the screen.
  61. ; Format: x000bbbb
  62. ; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0-$F)
  63. lda #%00001111
  64. sta INIDISP
  65. jmp MainLoop
  66. LoadPaletteAndTileData:
  67. ; For more details on DMA, see:
  68. ; http://wiki.superfamicom.org/snes/show/Grog%27s+Guide+to+DMA+and+HDMA+on+the+SNES
  69. ; http://wiki.superfamicom.org/snes/show/Making+a+Small+Game+-+Tic-Tac-Toe
  70. ;
  71. ; A lot of the graphics-related registers are explained in Qwertie's doc:
  72. ; http://emu-docs.org/Super%20NES/General/snesdoc.html
  73. ; ... but be careful, because there are some errors in this doc.
  74. ;
  75. ; bazz's tutorial (available from http://wiki.superfamicom.org/snes/) is
  76. ; quite helpful with palette / sprites / DMA, especially starting at
  77. ; http://wiki.superfamicom.org/snes/show/Working+with+VRAM+-+Loading+the+Palette
  78. ; Initialize the palette memory in a loop.
  79. ; We could also do this with a DMA transfer (like we do with the tile data
  80. ; below), but it seems overkill for just a few bytes. :)
  81. ; TODO: do it with a DMA transfer.
  82. ; First, sprite palette data:
  83. ldx #0
  84. lda #128 ; Palette entries for sprites start at 128.
  85. sta CGADDR
  86. -
  87. lda.l SpritePalette, X
  88. sta CGDATA
  89. inx
  90. cpx #32 ; 32 bytes of palette data.
  91. bne -
  92. ; Now, BG3 palette data.
  93. ; Palette entries for BG3 start at 0.
  94. ldx #0
  95. lda #0
  96. sta CGADDR
  97. -
  98. lda.l TilePalette, X
  99. sta CGDATA
  100. inx
  101. cpx #8 ; 8 bytes of palette data.
  102. bne -
  103. ; TODO: make the "DMA stuff into VRAM" a macro or function.
  104. ; Set VMADDR to where we want the DMA to start. We'll store sprite data
  105. ; at the beginning of VRAM.
  106. ldx #$0000
  107. stx VMADDR
  108. ; DMA 0 source address & bank.
  109. ldx #SpriteData
  110. stx DMA0SRC
  111. lda #:SpriteData
  112. sta DMA0SRCBANK
  113. ; DMA 0 transfer size. Equal to the size of sprites32.pic.
  114. ldx #4096
  115. stx DMA0SIZE
  116. ; DMA 0 control register.
  117. ; Transfer type 001 = 2 addresses, LH.
  118. lda #%00000001
  119. sta DMA0CTRL
  120. ; DMA 0 destination.
  121. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  122. sta DMA0DST
  123. ; Enable DMA channel 0.
  124. lda #%00000001
  125. sta DMAENABLE
  126. ; Store background tile data at byte $2000 of VRAM.
  127. ; (VMADDR is a word address, so multiply by 2 to get the byte address.)
  128. ldx #$1000
  129. stx VMADDR
  130. ; DMA 0 source address & bank.
  131. ldx #TileData
  132. stx DMA0SRC
  133. lda #:TileData
  134. sta DMA0SRCBANK
  135. ; DMA 0 transfer size. Equal to the size of tiles.pic.
  136. ldx #512
  137. stx DMA0SIZE
  138. ; DMA 0 control register.
  139. ; Transfer type 001 = 2 addresses, LH.
  140. lda #%00000001
  141. sta DMA0CTRL
  142. ; DMA 0 destination.
  143. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  144. sta DMA0DST
  145. ; Enable DMA channel 0.
  146. lda #%00000001
  147. sta DMAENABLE
  148. ; Tell the system that the BG3 tilemap starts at $4000.
  149. lda #%00100000
  150. sta BG3TILEMAP
  151. ; ... and that the background tile data for BG3 starts at $2000.
  152. lda #%00000001
  153. sta BG34NBA
  154. ; Set up the BG3 tilemap.
  155. ; VRAM write mode: increments the address every time we write a word.
  156. lda #%10000000
  157. sta VMAIN
  158. ; Set word address for accessing VRAM.
  159. ldx #$2000 ; BG 3 tilemap starts here. (Byte address $4000.)
  160. stx VMADDR
  161. ; Now write entries into the tile map.
  162. ldy #0
  163. -
  164. GetRandomByte
  165. sta $00
  166. ldx #$0000 ; This is a blank tile.
  167. ; 1 in 8 chance that we choose a non-blank tile.
  168. bit #%00000111
  169. bne +
  170. ldx #$0002
  171. bit #%10000000
  172. bne +
  173. ldx #$8002 ; Flip vertically.
  174. +
  175. stx VMDATA
  176. iny
  177. ; The tile map is 32x32 (1024 entries).
  178. cpy #1024
  179. bne -
  180. rts
  181. InitWorld:
  182. ; Start the background color as black.
  183. stz backgroundRed
  184. stz backgroundGreen
  185. stz backgroundBlue
  186. ; Clear the memory that's used to keep track of normal game state.
  187. SetA16Bit
  188. lda #0
  189. ldx #$20
  190. -
  191. sta 0, X
  192. inx
  193. inx
  194. cpx #$1000
  195. bne -
  196. SetA8Bit
  197. ; Initial enemy ship-spawn cooldown.
  198. lda #30
  199. sta enemyShipSpawnCooldown
  200. ; Player's initial starting location and health.
  201. lda #(256 / 4)
  202. sta playerX
  203. lda #((224 - 32) / 2)
  204. sta playerY
  205. lda #16
  206. sta playerHealth
  207. ; (x-velocity, y-velocity) of 4 different player shot patterns.
  208. lda #6
  209. sta shotVelocityTable
  210. lda #0
  211. sta shotVelocityTable + 1
  212. lda #3
  213. sta shotVelocityTable + 2
  214. lda #3
  215. sta shotVelocityTable + 3
  216. lda #0
  217. sta shotVelocityTable + 4
  218. lda #6
  219. sta shotVelocityTable + 5
  220. lda #-3
  221. sta shotVelocityTable + 6
  222. lda #3
  223. sta shotVelocityTable + 7
  224. lda #-6
  225. sta shotVelocityTable + 8
  226. lda #0
  227. sta shotVelocityTable + 9
  228. lda #-3
  229. sta shotVelocityTable + 10
  230. lda #-3
  231. sta shotVelocityTable + 11
  232. lda #0
  233. sta shotVelocityTable + 12
  234. lda #-6
  235. sta shotVelocityTable + 13
  236. lda #3
  237. sta shotVelocityTable + 14
  238. lda #-3
  239. sta shotVelocityTable + 15
  240. rts
  241. MainLoop:
  242. lda #%10000001 ; Enable NMI interrupt & auto joypad read.
  243. sta NMITIMEN
  244. wai ; Wait for interrupt.
  245. lda #%00000001 ; Disable NMI interrupt while processing.
  246. sta NMITIMEN
  247. jsr JoypadRead
  248. lda playerHealth
  249. cmp #0
  250. beq +
  251. jsr UpdateWorld
  252. bra ++
  253. +
  254. jsr GameOver
  255. ++
  256. jsr UpdateSprites
  257. jsr FillSecondarySpriteTable
  258. jsr SetBackgroundColor
  259. bra MainLoop
  260. GameOver:
  261. ; Wait until the player hits Start.
  262. lda joy1 + 1
  263. bit #$10 ; Start
  264. beq +
  265. jsr InitWorld
  266. +
  267. rts
  268. JoypadRead:
  269. ; Load joypad registers into RAM for easy inspection & manipulation.
  270. -
  271. lda HVBJOY
  272. bit #$01 ; If auto-joypad read is happening, loop.
  273. bne -
  274. ldx JOY1L
  275. stx joy1
  276. ldx JOY2L
  277. stx joy2
  278. rts
  279. ; TODO: move functions around to be in a more sensible order.
  280. JoypadHandler:
  281. JoypadUp:
  282. lda joy1 + 1
  283. bit #$08 ; Up
  284. beq JoypadDown ; Button not pressed.
  285. lda playerY
  286. cmp #0
  287. beq JoypadDown ; Value saturated.
  288. dec playerY
  289. dec playerY
  290. JoypadDown:
  291. lda joy1 + 1
  292. bit #$04 ; Down
  293. beq JoypadLeft ; Button not pressed.
  294. lda playerY
  295. cmp #(224 - 32 - 8 - 4) ; player height, bottom status bar, bottom padding
  296. beq JoypadLeft ; Value saturated.
  297. inc playerY
  298. inc playerY
  299. JoypadLeft:
  300. lda joy1 + 1
  301. bit #$02 ; Left
  302. beq JoypadRight ; Button not pressed.
  303. lda playerX
  304. cmp #0
  305. beq JoypadRight ; Value saturated.
  306. dec playerX
  307. dec playerX
  308. JoypadRight:
  309. lda joy1 + 1
  310. bit #$01 ; Right
  311. beq JoypadY ; Button not pressed.
  312. lda playerX
  313. cmp #(256 - 32)
  314. beq JoypadY ; Value saturated.
  315. inc playerX
  316. inc playerX
  317. JoypadY:
  318. lda joy1 + 1
  319. bit #$40 ; Y
  320. beq JoypadX ; Button not pressed.
  321. lda backgroundRed
  322. cmp #0
  323. beq JoypadX ; Value saturated.
  324. dec backgroundRed
  325. JoypadX:
  326. lda joy1
  327. bit #$40 ; X
  328. beq JoypadL ; Button not pressed.
  329. lda backgroundRed
  330. cmp #31
  331. beq JoypadL ; Value saturated.
  332. inc backgroundRed
  333. JoypadL:
  334. lda joy1
  335. bit #$20 ; L
  336. beq JoypadR ; Button not pressed.
  337. lda backgroundBlue
  338. cmp #0
  339. beq JoypadR ; Value saturated.
  340. dec backgroundBlue
  341. JoypadR:
  342. lda joy1
  343. bit #$10 ; R
  344. beq JoypadB ; Button not pressed.
  345. lda backgroundBlue
  346. cmp #31
  347. beq JoypadB ; Value saturated.
  348. inc backgroundBlue
  349. JoypadB:
  350. lda joy1 + 1
  351. bit #$80 ; B
  352. beq JoypadDone
  353. jsr MaybeShoot
  354. JoypadDone:
  355. rts
  356. MaybeShoot:
  357. ; If the cooldown timer is non-zero, don't shoot.
  358. lda shotCooldown
  359. cmp #0
  360. bne MaybeShootDone
  361. ; Find the first empty spot in the shots array.
  362. ldx #playerShotArray
  363. -
  364. lda 0, X
  365. cmp #0
  366. beq +
  367. .rept shotSize
  368. inx
  369. .endr
  370. ; If we went all the way to the end, bail out.
  371. cpx #(playerShotArray + playerShotArrayLength * shotSize)
  372. bne -
  373. rts
  374. +
  375. ; Enable shot; set its position based on player position.
  376. lda #8 ; Sprite number.
  377. sta 0, X
  378. lda playerX
  379. clc
  380. adc #28
  381. sta 1, X
  382. lda playerY
  383. clc
  384. adc #14
  385. sta 2, X
  386. ; Get x- and y-velocity out of shotVelocityTable.
  387. lda nextShotState
  388. and #%00000000 ; 8 possibilities if we use #%00000111.
  389. ldy #0
  390. -
  391. cmp #0
  392. beq +
  393. .rept 2
  394. iny
  395. .endr
  396. dec A
  397. bra -
  398. +
  399. inc nextShotState
  400. ; x-velocity.
  401. lda shotVelocityTable, Y
  402. sta 3, X
  403. ; y-velocity.
  404. lda shotVelocityTable + 1, Y
  405. sta 4, X
  406. ; Set cooldown timer.
  407. lda #8
  408. sta shotCooldown
  409. MaybeShootDone:
  410. rts
  411. UpdateWorld:
  412. jsr UpdateShotCooldown
  413. jsr UpdateShotPositions
  414. jsr JoypadHandler
  415. jsr SpawnEnemyShips
  416. jsr UpdateEnemyShips
  417. jsr CheckCollisionsWithPlayer
  418. jsr CheckCollisionsWithEnemies
  419. jsr UpdateBackgroundScroll
  420. rts
  421. UpdateShotCooldown:
  422. ; Update shot cooldown.
  423. lda shotCooldown
  424. cmp #0
  425. beq +
  426. dec A
  427. sta shotCooldown
  428. +
  429. rts
  430. SpawnEnemyShips:
  431. lda enemyShipSpawnCooldown
  432. cmp #0
  433. beq +
  434. dec A
  435. sta enemyShipSpawnCooldown
  436. rts
  437. +
  438. GetRandomByte
  439. and #%00111111
  440. clc
  441. adc #32
  442. sta enemyShipSpawnCooldown
  443. ; Find an empty spot in the array.
  444. ldy #0
  445. -
  446. lda enemyShipArray, Y
  447. cmp #0
  448. beq +
  449. .rept enemyShipSize
  450. iny
  451. .endr
  452. cpy #(enemyShipArrayLength * enemyShipSize)
  453. bne -
  454. rts ; Too many ships; bail.
  455. +
  456. lda #4 ; Sprite number.
  457. sta enemyShipArray, Y
  458. lda #254
  459. sta enemyShipArray + 1, Y ; x.
  460. -
  461. GetRandomByte
  462. cmp #(224 - 32 - 8 - 4)
  463. bcs - ; Keep trying.
  464. sta enemyShipArray + 2, Y ; y.
  465. lda #0
  466. sta enemyShipArray + 3, Y ; move AI type.
  467. sta enemyShipArray + 4, Y ; shoot AI type.
  468. lda #12
  469. sta enemyShipArray + 5, Y ; shot cooldown.
  470. rts
  471. ; TODO: reap ships if they move off the top, bottom, or right too.
  472. UpdateEnemyShips:
  473. ldy #0 ; Index into enemyShipArray.
  474. sty $00 ; Index into enemyShotArray.
  475. --
  476. lda enemyShipArray, Y
  477. cmp #0 ; If it's not enabled, skip it.
  478. beq ++
  479. ; Move the ship.
  480. ; TODO: implement different movement based on AI-type.
  481. lda enemyShipArray + 1, Y ; x
  482. clc
  483. adc #-2 ; x-velocity.
  484. bcs +
  485. lda #0
  486. sta enemyShipArray, Y ; reap it.
  487. bra ++
  488. +
  489. sta enemyShipArray + 1, Y ; move it.
  490. lda enemyShipArray + 5, Y ; shot cooldown
  491. cmp #0
  492. beq +
  493. dec A
  494. sta enemyShipArray + 5, Y ; new shot cooldown
  495. bra ++
  496. + ; Add a shot.
  497. ; TODO: implement different shooting based on shoot-type.
  498. lda #12
  499. sta enemyShipArray + 5, Y ; new shot cooldown
  500. lda enemyShipArray + 1, Y
  501. sta $02 ; Enemy ship x.
  502. lda enemyShipArray + 2, Y
  503. sta $03 ; Enemy ship y.
  504. phy
  505. ldy $00
  506. jsr SpawnEnemyShot
  507. sty $00
  508. ply
  509. ++ ; Done processing this ship.
  510. .rept enemyShipSize
  511. iny
  512. .endr
  513. cpy #(enemyShipArrayLength * enemyShipSize)
  514. bne --
  515. rts
  516. ; Expects:
  517. ; Y: index into enemyShotArray (bytes).
  518. ; $02: enemy ship x-position.
  519. ; $03: enemy ship y-position.
  520. ;
  521. ; Modifies:
  522. ; A.
  523. ; Y: to point at the next possible free index into enemyShotArray.
  524. SpawnEnemyShot:
  525. -
  526. ; Bail if at end of array.
  527. cpy #(enemyShotArrayLength * shotSize)
  528. bne +
  529. rts
  530. +
  531. lda enemyShotArray, Y
  532. cmp #0
  533. beq +
  534. ; Try next slot.
  535. .rept shotSize
  536. iny
  537. .endr
  538. bra -
  539. +
  540. ; OK, found a spot.
  541. lda #9 ; Sprite number.
  542. sta enemyShotArray, Y
  543. lda $02 ; Get enemy x.
  544. sta enemyShotArray + 1, Y ; Save as shot x.
  545. lda $03 ; Get enemy y.
  546. clc
  547. adc #((32 - 4) / 2) ; Center it with enemy ship.
  548. sta enemyShotArray + 2, Y ; Save as shot y.
  549. lda #-6
  550. sta enemyShotArray + 3, Y ; x-velocity.
  551. lda #0
  552. sta enemyShotArray + 4, Y ; y-velocity.
  553. rts
  554. UpdateShotPositions:
  555. ldx #0
  556. UpdateShot: ; Updates position of one shot.
  557. lda playerShotArray, X
  558. cmp #0
  559. beq ShotDone
  560. ; Add to the x-coordinate. If the carry bit is set, we went off the edge
  561. ; of the screen, so disable the shot.
  562. lda playerShotArray + 3, X ; x-velocity.
  563. sta $00
  564. bit #%10000000 ; Check whether the velocity is negative.
  565. bne UpdateShotWithNegativeXVelocity
  566. lda playerShotArray + 1, X
  567. clc
  568. adc $00
  569. bcs DisableShot
  570. sta playerShotArray + 1, X ; Store new x-coord.
  571. bra UpdateShotY
  572. UpdateShotWithNegativeXVelocity:
  573. ; TODO: wrap sprites when they go negative here, like we do with
  574. ; y-velocities.
  575. lda playerShotArray + 1, X ; Current x.
  576. clc
  577. adc $00
  578. bcc DisableShot
  579. sta playerShotArray + 1, X
  580. UpdateShotY:
  581. ; Add to the y-coordinate.
  582. lda playerShotArray + 4, X ; y-velocity.
  583. sta $00
  584. bit #%10000000 ; Check whether the velocity is negative.
  585. bne UpdateShotWithNegativeYVelocity
  586. lda playerShotArray + 2, X
  587. clc
  588. adc $00
  589. cmp #224
  590. bcs DisableShot
  591. sta playerShotArray + 2, X ; Store new y-coord.
  592. bra ShotDone
  593. UpdateShotWithNegativeYVelocity:
  594. lda playerShotArray + 2, X ; Current y.
  595. cmp #224
  596. bcs + ; If the shot was "off the top" before moving, maybe we'll reap it.
  597. adc $00 ; Otherwise, just update it,
  598. sta playerShotArray + 2, X ; save the result,
  599. bra ShotDone ; and we know it shouldn't be reaped.
  600. +
  601. clc
  602. adc $00
  603. cmp #224
  604. bcc DisableShot ; If it's now wrapped around, reap it.
  605. sta playerShotArray + 2, X
  606. bra ShotDone
  607. DisableShot:
  608. stz playerShotArray, X
  609. ShotDone:
  610. ; TODO: in places where we .rept inx (etc), is it faster to use actual
  611. ; addition?
  612. .rept shotSize
  613. inx
  614. .endr
  615. cpx #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  616. bne UpdateShot
  617. rts
  618. ; Expects:
  619. ; $00: x-coordinate of ship's center.
  620. ; $01: y-coordinate of ship's center.
  621. ; $02: half of the shot's size.
  622. ; $03: x-coordinate of shot's upper-left.
  623. ; $04: y-coordinate of shot's upper-left.
  624. ;
  625. ; Modifies:
  626. ; $05
  627. ; A: set to non-zero if there was a collision, zero otherwise.
  628. CheckCollision:
  629. lda $03
  630. clc
  631. adc $02 ; Get the center of the shot.
  632. sbc $00
  633. bpl + ; If the result is positive, great!
  634. eor #$ff ; Otherwise, negate it.
  635. inc A
  636. +
  637. ; A now contains dx, guaranteed to be positive.
  638. cmp #18 ; Threshold for "successful hit".
  639. bcc +
  640. lda #0 ; Already too far to be a hit; bail.
  641. rts
  642. +
  643. sta $05 ; Save dx for later.
  644. ; Find dy.
  645. lda $04
  646. clc
  647. adc $02 ; Get the center of the shot.
  648. sbc $01
  649. bpl + ; If the result is positive, great!
  650. eor #$ff ; Otherwise, negate it.
  651. inc A
  652. +
  653. ; A now contains dy, guaranteed to be positive.
  654. clc
  655. adc $05 ; Add dx.
  656. cmp #18 ; Threshold for "successful hit".
  657. lda #0
  658. bcs +
  659. lda #1 ; Got a hit.
  660. +
  661. rts
  662. CheckCollisionsWithPlayer:
  663. lda #2 ; Half of shot's size.
  664. sta $02
  665. ; Store player position statically.
  666. clc
  667. lda playerX
  668. adc #16 ; Can't overflow.
  669. sta $00 ; Store the center.
  670. lda playerY
  671. ; Store the center. Our ship is actually 31 pixels tall, so offsetting by
  672. ; 15 feels more "fair": a shot that hits the invisible bottom edge of the
  673. ; ship won't count as a hit.
  674. adc #15
  675. sta $01
  676. ldx #0
  677. -
  678. lda enemyShotArray, X
  679. cmp #0 ; Check whether it's active.
  680. beq +
  681. lda enemyShotArray + 1, X ; x.
  682. sta $03
  683. lda enemyShotArray + 2, X ; y.
  684. sta $04
  685. jsr CheckCollision
  686. cmp #0
  687. beq +
  688. ; OK, we got a hit! Disable the shot.
  689. lda #0
  690. sta enemyShotArray, X
  691. ; ... and decrement the player's life.
  692. lda playerHealth
  693. cmp #0
  694. beq +
  695. dec playerHealth
  696. +
  697. .rept shotSize
  698. inx
  699. .endr
  700. cpx #(enemyShotArrayLength * shotSize)
  701. bne -
  702. rts
  703. CheckCollisionsWithEnemies:
  704. lda #2 ; Half of shot's size.
  705. sta $02
  706. ldy #0 ; Index into enemyShipArray.
  707. --
  708. lda enemyShipArray, Y
  709. cmp #0 ; Check whether it's active.
  710. beq ++
  711. ; Store enemy position statically.
  712. clc
  713. lda enemyShipArray + 1, Y ; x.
  714. adc #16 ; Can't overflow.
  715. sta $00 ; Store the center.
  716. lda enemyShipArray + 2, Y ; y.
  717. adc #15
  718. sta $01 ; Store the center.
  719. ldx #0 ; Index into playerShotArray.
  720. -
  721. lda playerShotArray, X
  722. cmp #0
  723. beq +
  724. lda playerShotArray + 1, X ; x.
  725. sta $03
  726. lda playerShotArray + 2, X ; y.
  727. sta $04
  728. jsr CheckCollision
  729. cmp #0
  730. beq +
  731. ; OK, we got a hit! Disable the shot.
  732. lda #0
  733. sta playerShotArray, X
  734. ; ... and also the enemy ship.
  735. sta enemyShipArray, Y
  736. ; Give that player some points. Players love points.
  737. SetA16Bit
  738. sed ; Set decimal mode.
  739. lda playerScore
  740. clc
  741. adc #1 ; We can't just "inc"; it doesn't know about decimal mode.
  742. sta playerScore
  743. cld ; Clear decimal mode.
  744. SetA8Bit
  745. bra ++ ; ... we're done with this ship; no need to check more shots.
  746. +
  747. .rept shotSize
  748. inx
  749. .endr
  750. cpx #(playerShotArrayLength * shotSize)
  751. bne -
  752. ++
  753. .rept enemyShipSize
  754. iny
  755. .endr
  756. cpy #(enemyShipArrayLength * enemyShipSize)
  757. bne --
  758. rts
  759. UpdateBackgroundScroll:
  760. ; Make the background scroll. Horizontal over time; vertical depending on
  761. ; player's y-coordinate.
  762. lda vBlankCounter
  763. sta BG3HOFS
  764. lda vBlankCounter + 1
  765. sta BG3HOFS
  766. lda playerY
  767. .rept 3
  768. lsr
  769. .endr
  770. sta BG3VOFS
  771. stz BG3VOFS
  772. rts
  773. UpdateSprites: ; TODO: refactor into smaller pieces.
  774. ; This page is a good reference on SNES sprite formats:
  775. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  776. ; It uses the same approach we're using, in which we keep a buffer of the
  777. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  778. ; during VBlank.
  779. ; Sprite table 1 has 4 bytes per sprite, laid out as follows:
  780. ; Byte 1: xxxxxxxx x: X coordinate
  781. ; Byte 2: yyyyyyyy y: Y coordinate
  782. ; Byte 3: cccccccc c: Starting tile #
  783. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  784. ; p: palette #
  785. ; Sprite table 2 has 2 bits per sprite, like so:
  786. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  787. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  788. ; Setting all the high bits keeps the sprites offscreen.
  789. ; Zero out the scratch space for the secondary sprite table.
  790. ldx #0
  791. -
  792. stz spriteTableScratchStart, X
  793. inx
  794. cpx #numSprites
  795. bne -
  796. ldx #0 ; Index into sprite table 1.
  797. ldy #0 ; Index into sprite table 2.
  798. jsr MaybeGameOver
  799. ; Copy player coords into sprite table.
  800. lda playerX
  801. sta spriteTableStart, X
  802. lda playerY
  803. sta spriteTableStart + 1, X
  804. lda #0
  805. sta spriteTableStart + 2, X
  806. ; Set priority bits so that the sprite is drawn in front.
  807. lda #%00010000
  808. sta spriteTableStart + 3, X
  809. lda #%11000000 ; Enable large sprite.
  810. sta spriteTableScratchStart, Y
  811. AdvanceSpritePointers
  812. ; Now add enemy ships.
  813. sty $00 ; Save sprite table 2 index.
  814. ldy #0 ; Index into enemyShipArray.
  815. -
  816. lda enemyShipArray, Y
  817. cmp #0 ; If not enabled, skip to next ship.
  818. beq +
  819. ; Update sprite table 1.
  820. sta spriteTableStart + 2, X ; sprite number
  821. lda enemyShipArray + 1, Y
  822. sta spriteTableStart, X ; x
  823. lda enemyShipArray + 2, Y
  824. sta spriteTableStart + 1, X ; y
  825. lda #%01000000 ; flip horizontally.
  826. sta spriteTableStart + 3, X
  827. ; Update secondary sprite table.
  828. phy ; Save enemyShipArray index.
  829. ldy $00
  830. lda #%11000000 ; Enable large sprite.
  831. sta spriteTableScratchStart, Y
  832. AdvanceSpritePointers
  833. sty $00
  834. ply ; Restore enemyShipArray index.
  835. +
  836. .rept enemyShipSize
  837. iny
  838. .endr
  839. cpy #(enemyShipArrayLength * enemyShipSize)
  840. bne -
  841. ldy $00 ; Restore Y to its rightful self.
  842. ; Now add shots.
  843. sty $00 ; Save sprite table 2 index.
  844. ldy #0 ; Index into playerShotArray.
  845. -
  846. lda playerShotArray, Y
  847. cmp #0
  848. beq + ; If not enabled, skip to next shot.
  849. ; Update sprite table 1.
  850. sta spriteTableStart + 2, X ; sprite number
  851. lda playerShotArray + 1, Y
  852. sta spriteTableStart, X ; x
  853. lda playerShotArray + 2, Y
  854. sta spriteTableStart + 1, X ; y
  855. ; Update secondary sprite table.
  856. phy ; Save playerShotArray index.
  857. ldy $00
  858. lda #%01000000 ; Enable small sprite.
  859. sta spriteTableScratchStart, Y
  860. AdvanceSpritePointers
  861. sty $00
  862. ply ; Restore playerShotArray index.
  863. +
  864. .rept shotSize
  865. iny
  866. .endr
  867. cpy #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  868. bne -
  869. ldy $00 ; Restore Y to its rightful self.
  870. ; Now add sprites to show player health.
  871. stz $01
  872. lda #4
  873. sta $02
  874. -
  875. lda $01
  876. cmp playerHealth
  877. beq + ; All done?
  878. lda #10
  879. sta spriteTableStart + 2, X ; sprite number
  880. lda $02
  881. sta spriteTableStart, X ; x
  882. clc
  883. adc #7
  884. sta $02
  885. lda #212
  886. sta spriteTableStart + 1, X ; y
  887. ; Set priority bits so that the sprite is drawn in front.
  888. lda #%00110000
  889. sta spriteTableStart + 3, X
  890. lda #%01000000 ; Enable small sprite.
  891. sta spriteTableScratchStart, Y
  892. AdvanceSpritePointers
  893. inc $01
  894. bra -
  895. +
  896. ; Sprites to show player score.
  897. lda #(252 - 7 * 6)
  898. sta $00 ; x-position
  899. lda #212
  900. sta $01 ; y-position
  901. stz $02 ; Don't render leading zeroes.
  902. stz $03 ; ... not even for the second digit.
  903. lda playerScore + 1
  904. jsr RenderTwoDigits
  905. lda playerScore
  906. jsr RenderTwoDigits
  907. inc $03 ; Render rightmost zero always.
  908. lda #0
  909. jsr RenderTwoDigits
  910. ; Now clear out the unused entries in the sprite table.
  911. -
  912. cpx #spriteTable1Size
  913. beq +
  914. lda #1
  915. sta spriteTableStart, X
  916. AdvanceSpritePointers
  917. bra -
  918. +
  919. rts
  920. MaybeGameOver:
  921. lda playerHealth
  922. cmp #0
  923. beq +
  924. rts
  925. +
  926. ; Sprites to show "Game Over" text.
  927. lda #80 ; G
  928. sta spriteTableStart + 2, X
  929. lda #112
  930. sta spriteTableStart, X
  931. lda #104
  932. sta spriteTableStart + 1, X
  933. lda #%00110000
  934. sta spriteTableStart + 3, X
  935. lda #%01000000 ; Enable small sprite.
  936. sta spriteTableScratchStart, Y
  937. AdvanceSpritePointers
  938. lda #81 ; A
  939. sta spriteTableStart + 2, X
  940. lda #120
  941. sta spriteTableStart, X
  942. lda #104
  943. sta spriteTableStart + 1, X
  944. lda #%00110000
  945. sta spriteTableStart + 3, X
  946. lda #%01000000 ; Enable small sprite.
  947. sta spriteTableScratchStart, Y
  948. AdvanceSpritePointers
  949. lda #82 ; M
  950. sta spriteTableStart + 2, X
  951. lda #128
  952. sta spriteTableStart, X
  953. lda #104
  954. sta spriteTableStart + 1, X
  955. lda #%00110000
  956. sta spriteTableStart + 3, X
  957. lda #%01000000 ; Enable small sprite.
  958. sta spriteTableScratchStart, Y
  959. AdvanceSpritePointers
  960. lda #83 ; E
  961. sta spriteTableStart + 2, X
  962. lda #136
  963. sta spriteTableStart, X
  964. lda #104
  965. sta spriteTableStart + 1, X
  966. lda #%00110000
  967. sta spriteTableStart + 3, X
  968. lda #%01000000 ; Enable small sprite.
  969. sta spriteTableScratchStart, Y
  970. AdvanceSpritePointers
  971. lda #84 ; O
  972. sta spriteTableStart + 2, X
  973. lda #112
  974. sta spriteTableStart, X
  975. lda #114
  976. sta spriteTableStart + 1, X
  977. lda #%00110000
  978. sta spriteTableStart + 3, X
  979. lda #%01000000 ; Enable small sprite.
  980. sta spriteTableScratchStart, Y
  981. AdvanceSpritePointers
  982. lda #85 ; V
  983. sta spriteTableStart + 2, X
  984. lda #120
  985. sta spriteTableStart, X
  986. lda #114
  987. sta spriteTableStart + 1, X
  988. lda #%00110000
  989. sta spriteTableStart + 3, X
  990. lda #%01000000 ; Enable small sprite.
  991. sta spriteTableScratchStart, Y
  992. AdvanceSpritePointers
  993. lda #83 ; E
  994. sta spriteTableStart + 2, X
  995. lda #128
  996. sta spriteTableStart, X
  997. lda #114
  998. sta spriteTableStart + 1, X
  999. lda #%00110000
  1000. sta spriteTableStart + 3, X
  1001. lda #%01000000 ; Enable small sprite.
  1002. sta spriteTableScratchStart, Y
  1003. AdvanceSpritePointers
  1004. lda #86 ; R
  1005. sta spriteTableStart + 2, X
  1006. lda #136
  1007. sta spriteTableStart, X
  1008. lda #114
  1009. sta spriteTableStart + 1, X
  1010. lda #%00110000
  1011. sta spriteTableStart + 3, X
  1012. lda #%01000000 ; Enable small sprite.
  1013. sta spriteTableScratchStart, Y
  1014. AdvanceSpritePointers
  1015. rts
  1016. ; Expects:
  1017. ; A: number to display (a byte where each nybble is from 0-9).
  1018. ; X/Y: pointing at appropriate locations into the sprite tables.
  1019. ; $00: x-position to render the leftmost digit into.
  1020. ; $01: y-position to render the leftmost digit into.
  1021. ; $02: if set, render leading zeroes.
  1022. ; $03: if set, always render the zero for the low-order digit.
  1023. ;
  1024. ; Updates:
  1025. ; X & Y to point at the next locations in the sprite tables.
  1026. ; The sprite tables, to add (up to) 2 sprites for digits.
  1027. ; $00: x-position to render additional digits into.
  1028. ; $02: whether to render leading zeroes.
  1029. ; $04-$06 (scratch).
  1030. RenderTwoDigits:
  1031. ; Store the high digit in $05 and the low digit in $06.
  1032. sta $06
  1033. .rept 4
  1034. lsr
  1035. .endr
  1036. sta $05
  1037. lda $06
  1038. and #$0F
  1039. sta $06
  1040. ; Render the first digit.
  1041. lda $05
  1042. jsr RenderDigit
  1043. lda $00
  1044. clc
  1045. adc #7
  1046. sta $00
  1047. ; Set "render zero" for rightmost digit to true if requested.
  1048. lda $02
  1049. ora $03
  1050. sta $02
  1051. ; Render the second digit.
  1052. lda $06
  1053. jsr RenderDigit
  1054. lda $00
  1055. clc
  1056. adc #7
  1057. sta $00
  1058. rts
  1059. ; Expects:
  1060. ; A: number to display (from 0-9).
  1061. ; X/Y: pointing at appropriate locations into the sprite tables.
  1062. ; $00: x-position to render the digit into.
  1063. ; $01: y-position to render the digit into.
  1064. ; $02: whether to render if the number is zero.
  1065. ;
  1066. ; Updates:
  1067. ; X & Y to point at the next locations in the sprite tables.
  1068. ; The sprite tables, to add (up to) 1 sprite for digits.
  1069. ; $02: whether to render further leading zeroes.
  1070. ; $04 (scratch).
  1071. RenderDigit:
  1072. sta $04
  1073. cmp #0
  1074. bne + ; Non-zero: render it regardless.
  1075. lda $02
  1076. cmp #0
  1077. bne + ; Render because "render zeroes" is set.
  1078. rts ; Nothing to render.
  1079. +
  1080. lda #1
  1081. sta $02 ; Render leading zeroes from here on out.
  1082. lda $04
  1083. clc
  1084. adc #64 ; Base index of digit sprites.
  1085. sta spriteTableStart + 2, X ; sprite number
  1086. lda $00
  1087. sta spriteTableStart, X ; x
  1088. lda $01
  1089. sta spriteTableStart + 1, X ; y
  1090. ; Set priority bits so that the sprite is drawn in front.
  1091. lda #%00110000
  1092. sta spriteTableStart + 3, X
  1093. lda #%01000000 ; Enable small sprite.
  1094. sta spriteTableScratchStart, Y
  1095. AdvanceSpritePointers
  1096. rts
  1097. FillSecondarySpriteTable:
  1098. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  1099. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  1100. ; It's annoying to deal with bitfields when thinking about business logic,
  1101. ; so the spriteTableScratch array contains one byte for each sprite, in
  1102. ; which the two most significant bits are the "size" and "upper x" bits.
  1103. ; This function is meant to be called after UpdateWorld, and packs those
  1104. ; bytes into the actual bitfield that the OAM wants for the secondary
  1105. ; sprite table.
  1106. ;
  1107. ; The expected format of every byte in the scratch sprite table is:
  1108. ; sx------ s = size (0 = small, 1 = large)
  1109. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  1110. ldx #0 ; Index into input table.
  1111. ldy #0 ; Index into output table.
  1112. -
  1113. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  1114. .rept 4
  1115. ; For each byte, the lower-order bits correspond to the lower-numbered
  1116. ; sprites; therefore we insert the current sprite's bits "at the top"
  1117. ; and shift them right for each successive sprite.
  1118. lsr $00
  1119. lsr $00
  1120. lda spriteTableScratchStart, X
  1121. ora $00
  1122. sta $00
  1123. inx
  1124. .endr
  1125. lda $00
  1126. eor #%01010101
  1127. sta spriteTable2Start, Y
  1128. iny
  1129. cpx #numSprites
  1130. bne -
  1131. rts
  1132. SetBackgroundColor:
  1133. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  1134. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  1135. ; Set the background color.
  1136. ; Entry 0 corresponds to the SNES background color.
  1137. stz CGADDR
  1138. ; Compute and the low-order byte and store it in CGDATA.
  1139. lda backgroundGreen
  1140. .rept 5
  1141. asl
  1142. .endr
  1143. ora backgroundRed
  1144. sta CGDATA
  1145. ; Compute the high-order byte and store it in CGDATA.
  1146. lda backgroundBlue
  1147. .rept 2
  1148. asl
  1149. .endr
  1150. sta $00
  1151. lda backgroundGreen
  1152. .rept 3
  1153. lsr
  1154. .endr
  1155. ora $00
  1156. sta CGDATA
  1157. rts
  1158. VBlankHandler:
  1159. jsr VBlankCounter
  1160. jsr DMASpriteTables
  1161. rti
  1162. VBlankCounter:
  1163. ; Increment a counter of how many VBlanks we've done.
  1164. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  1165. ; 77 hours to wrap around; that's good enough for me :)
  1166. inc vBlankCounter
  1167. bne +
  1168. inc vBlankCounter + 1
  1169. bne +
  1170. inc vBlankCounter + 2
  1171. +
  1172. rts
  1173. DMASpriteTables:
  1174. ; Store at the base OAM address.
  1175. ldx #$0000
  1176. stx OAMADDR
  1177. ; Default DMA control; destination $2104 (OAM data register).
  1178. stz DMA0CTRL
  1179. lda #$04
  1180. sta DMA0DST
  1181. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  1182. ldx #spriteTableStart
  1183. stx DMA0SRC
  1184. stz DMA0SRCBANK
  1185. ldx #spriteTableSize
  1186. stx DMA0SIZE
  1187. ; Kick off the DMA transfer.
  1188. lda #%00000001
  1189. sta DMAENABLE
  1190. rts
  1191. .ENDS
  1192. ; Bank 1 is used for our graphics assets.
  1193. .BANK 1 SLOT 0
  1194. .ORG 0
  1195. .SECTION "GraphicsData"
  1196. SpriteData:
  1197. .INCBIN "sprites32.pic"
  1198. SpritePalette:
  1199. .INCBIN "sprites32.clr"
  1200. TileData:
  1201. .INCBIN "tiles.pic"
  1202. TilePalette:
  1203. .INCBIN "tiles.clr"
  1204. .ENDS
  1205. ; Fill an entire bank with random numbers.
  1206. .SEED 1
  1207. .BANK 2 SLOT 0
  1208. .ORG 0
  1209. .SECTION "RandomBytes"
  1210. .DBRND 32 * 1024, 0, 255
  1211. .ENDS