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.

1299 lines
28 KiB

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