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.

1323 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 ; We can't just "inc"; it doesn't know about decimal mode.
  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. lda #(252 - 7 * 6)
  888. sta $00 ; x-position
  889. lda #212
  890. sta $01 ; y-position
  891. stz $02 ; Don't render leading zeroes.
  892. stz $03 ; ... not even for the second digit.
  893. lda playerScore + 1
  894. jsr RenderTwoDigits
  895. lda playerScore
  896. jsr RenderTwoDigits
  897. inc $03 ; Render rightmost zero always.
  898. lda #0
  899. jsr RenderTwoDigits
  900. ; Now clear out the unused entries in the sprite table.
  901. -
  902. cpx #spriteTable1Size
  903. beq +
  904. lda #1
  905. sta spriteTableStart, X
  906. AdvanceSpritePointers
  907. bra -
  908. +
  909. rts
  910. ; Expects:
  911. ; A: number to display (a byte where each nybble is from 0-9).
  912. ; X/Y: pointing at appropriate locations into the sprite tables.
  913. ; $00: x-position to render the leftmost digit into.
  914. ; $01: y-position to render the leftmost digit into.
  915. ; $02: if set, render leading zeroes.
  916. ; $03: if set, always render the zero for the low-order digit.
  917. ;
  918. ; Updates:
  919. ; X & Y to point at the next locations in the sprite tables.
  920. ; The sprite tables, to add (up to) 2 sprites for digits.
  921. ; $00: x-position to render additional digits into.
  922. ; $02: whether to render leading zeroes.
  923. ; $04-$06 (scratch).
  924. RenderTwoDigits:
  925. ; Store the high digit in $05 and the low digit in $06.
  926. sta $06
  927. .rept 4
  928. lsr
  929. .endr
  930. sta $05
  931. lda $06
  932. and #$0F
  933. sta $06
  934. ; Render the first digit.
  935. lda $05
  936. jsr RenderDigit
  937. lda $00
  938. clc
  939. adc #7
  940. sta $00
  941. ; Set "render zero" for rightmost digit to true if requested.
  942. lda $02
  943. ora $03
  944. sta $02
  945. ; Render the second digit.
  946. lda $06
  947. jsr RenderDigit
  948. lda $00
  949. clc
  950. adc #7
  951. sta $00
  952. rts
  953. ; Expects:
  954. ; A: number to display (from 0-9).
  955. ; X/Y: pointing at appropriate locations into the sprite tables.
  956. ; $00: x-position to render the digit into.
  957. ; $01: y-position to render the digit into.
  958. ; $02: whether to render if the number is zero.
  959. ;
  960. ; Updates:
  961. ; X & Y to point at the next locations in the sprite tables.
  962. ; The sprite tables, to add (up to) 1 sprite for digits.
  963. ; $02: whether to render further leading zeroes.
  964. ; $04 (scratch).
  965. RenderDigit:
  966. sta $04
  967. cmp #0
  968. bne + ; Non-zero: render it regardless.
  969. lda $02
  970. cmp #0
  971. bne + ; Render because "render zeroes" is set.
  972. rts ; Nothing to render.
  973. +
  974. lda #1
  975. sta $02 ; Render leading zeroes from here on out.
  976. lda $04
  977. clc
  978. adc #64 ; Base index of digit sprites.
  979. sta spriteTableStart + 2, X ; sprite number
  980. lda $00
  981. sta spriteTableStart, X ; x
  982. lda $01
  983. sta spriteTableStart + 1, X ; y
  984. ; Set priority bits so that the sprite is drawn in front.
  985. lda #%00110000
  986. sta spriteTableStart + 3, X
  987. lda #%01000000 ; Enable small sprite.
  988. sta spriteTableScratchStart, Y
  989. AdvanceSpritePointers
  990. rts
  991. FillSecondarySpriteTable:
  992. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  993. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  994. ; It's annoying to deal with bitfields when thinking about business logic,
  995. ; so the spriteTableScratch array contains one byte for each sprite, in
  996. ; which the two most significant bits are the "size" and "upper x" bits.
  997. ; This function is meant to be called after UpdateWorld, and packs those
  998. ; bytes into the actual bitfield that the OAM wants for the secondary
  999. ; sprite table.
  1000. ;
  1001. ; The expected format of every byte in the scratch sprite table is:
  1002. ; sx------ s = size (0 = small, 1 = large)
  1003. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  1004. ldx #0 ; Index into input table.
  1005. ldy #0 ; Index into output table.
  1006. -
  1007. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  1008. .rept 4
  1009. ; For each byte, the lower-order bits correspond to the lower-numbered
  1010. ; sprites; therefore we insert the current sprite's bits "at the top"
  1011. ; and shift them right for each successive sprite.
  1012. lsr $00
  1013. lsr $00
  1014. lda spriteTableScratchStart, X
  1015. ora $00
  1016. sta $00
  1017. inx
  1018. .endr
  1019. lda $00
  1020. eor #%01010101
  1021. sta spriteTable2Start, Y
  1022. iny
  1023. cpx #numSprites
  1024. bne -
  1025. rts
  1026. SetBackgroundColor:
  1027. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  1028. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  1029. ; Set the background color.
  1030. ; Entry 0 corresponds to the SNES background color.
  1031. stz CGADDR
  1032. ; Compute and the low-order byte and store it in CGDATA.
  1033. lda backgroundGreen
  1034. .rept 5
  1035. asl
  1036. .endr
  1037. ora backgroundRed
  1038. sta CGDATA
  1039. ; Compute the high-order byte and store it in CGDATA.
  1040. lda backgroundBlue
  1041. .rept 2
  1042. asl
  1043. .endr
  1044. sta $00
  1045. lda backgroundGreen
  1046. .rept 3
  1047. lsr
  1048. .endr
  1049. ora $00
  1050. sta CGDATA
  1051. rts
  1052. VBlankHandler:
  1053. jsr VBlankCounter
  1054. jsr DMASpriteTables
  1055. rti
  1056. VBlankCounter:
  1057. ; Increment a counter of how many VBlanks we've done.
  1058. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  1059. ; 77 hours to wrap around; that's good enough for me :)
  1060. inc vBlankCounter
  1061. bne +
  1062. inc vBlankCounter + 1
  1063. bne +
  1064. inc vBlankCounter + 2
  1065. +
  1066. rts
  1067. DMASpriteTables:
  1068. ; Store at the base OAM address.
  1069. ldx #$0000
  1070. stx OAMADDR
  1071. ; Default DMA control; destination $2104 (OAM data register).
  1072. stz DMA0CTRL
  1073. lda #$04
  1074. sta DMA0DST
  1075. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  1076. ldx #spriteTableStart
  1077. stx DMA0SRC
  1078. stz DMA0SRCBANK
  1079. ldx #spriteTableSize
  1080. stx DMA0SIZE
  1081. ; Kick off the DMA transfer.
  1082. lda #%00000001
  1083. sta DMAENABLE
  1084. rts
  1085. .ENDS
  1086. ; Bank 1 is used for our graphics assets.
  1087. .BANK 1 SLOT 0
  1088. .ORG 0
  1089. .SECTION "GraphicsData"
  1090. SpriteData:
  1091. .INCBIN "sprites32.pic"
  1092. SpritePalette:
  1093. .INCBIN "sprites32.clr"
  1094. TileData:
  1095. .INCBIN "tiles.pic"
  1096. TilePalette:
  1097. .INCBIN "tiles.clr"
  1098. .ENDS
  1099. ; Fill an entire bank with random numbers.
  1100. .SEED 1
  1101. .BANK 2 SLOT 0
  1102. .ORG 0
  1103. .SECTION "RandomBytes"
  1104. .DBRND 32 * 1024, 0, 255
  1105. .ENDS