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.

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