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.

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