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.

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