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.

1155 lines
24 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 #2048
  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 #20
  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)
  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: x-coordinate of shot's upper-left.
  605. ; $03: y-coordinate of shot's upper-left.
  606. ; $04: half of the shot's size.
  607. ;
  608. ; Modifies:
  609. ; $05
  610. ; A: set to non-zero if there was a collision, zero otherwise.
  611. CheckCollision:
  612. lda $02
  613. clc
  614. adc $04 ; 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 $03
  629. clc
  630. adc $04 ; 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. ; Store player position statically.
  647. clc
  648. lda playerX
  649. adc #16 ; Can't overflow.
  650. sta $00 ; Store the center.
  651. lda playerY
  652. ; Store the center. Our ship is actually 31 pixels tall, so offsetting by
  653. ; 15 feels more "fair": a shot that hits the invisible bottom edge of the
  654. ; ship won't count as a hit.
  655. adc #15
  656. sta $01
  657. ldx #0
  658. --
  659. lda enemyShotArray, X
  660. cmp #0 ; Check whether it's active.
  661. beq ++
  662. lda enemyShotArray + 1, X ; x.
  663. sta $02
  664. lda enemyShotArray + 2, X ; y.
  665. sta $03
  666. jsr CheckCollision
  667. cmp #0
  668. beq ++
  669. ; OK, we got a hit!
  670. ; Disable the shot.
  671. lda #0
  672. sta enemyShotArray, X
  673. ; And decrement the player's life.
  674. lda playerHealth
  675. cmp #0
  676. beq ++
  677. dec playerHealth
  678. ++
  679. .rept shotSize
  680. inx
  681. .endr
  682. cpx #(enemyShotArrayLength * shotSize)
  683. bne --
  684. rts
  685. CheckCollisionsWithEnemies:
  686. rts
  687. UpdateBackgroundScroll:
  688. ; Make the background scroll. Horizontal over time; vertical depending on
  689. ; player's y-coordinate.
  690. lda vBlankCounter
  691. sta BG3HOFS
  692. lda vBlankCounter + 1
  693. sta BG3HOFS
  694. lda playerY
  695. .rept 3
  696. lsr
  697. .endr
  698. sta BG3VOFS
  699. stz BG3VOFS
  700. rts
  701. UpdateSprites: ; TODO: refactor into smaller pieces.
  702. ; This page is a good reference on SNES sprite formats:
  703. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  704. ; It uses the same approach we're using, in which we keep a buffer of the
  705. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  706. ; during VBlank.
  707. ; Sprite table 1 has 4 bytes per sprite, laid out as follows:
  708. ; Byte 1: xxxxxxxx x: X coordinate
  709. ; Byte 2: yyyyyyyy y: Y coordinate
  710. ; Byte 3: cccccccc c: Starting tile #
  711. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  712. ; p: palette #
  713. ; Sprite table 2 has 2 bits per sprite, like so:
  714. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  715. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  716. ; Setting all the high bits keeps the sprites offscreen.
  717. ; Zero out the scratch space for the secondary sprite table.
  718. ldx #0
  719. -
  720. stz spriteTableScratchStart, X
  721. inx
  722. cpx #numSprites
  723. bne -
  724. ldx #0 ; Index into sprite table 1.
  725. ldy #0 ; Index into sprite table 2.
  726. ; Copy player coords into sprite table.
  727. lda playerX
  728. sta spriteTableStart, X
  729. lda playerY
  730. sta spriteTableStart + 1, X
  731. lda #0
  732. sta spriteTableStart + 2, X
  733. ; Set priority bits so that the sprite is drawn in front.
  734. lda #%00010000
  735. sta spriteTableStart + 3, X
  736. lda #%11000000 ; Enable large sprite.
  737. sta spriteTableScratchStart, Y
  738. .rept 4
  739. inx
  740. .endr
  741. iny
  742. ; Now add enemy ships.
  743. sty $00 ; Save sprite table 2 index.
  744. ldy #0 ; Index into enemyShipArray.
  745. -
  746. lda enemyShipArray, Y
  747. cmp #0 ; If not enabled, skip to next ship.
  748. beq +
  749. ; Update sprite table 1.
  750. sta spriteTableStart + 2, X ; sprite number
  751. lda enemyShipArray + 1, Y
  752. sta spriteTableStart, X ; x
  753. lda enemyShipArray + 2, Y
  754. sta spriteTableStart + 1, X ; y
  755. lda #%01000000 ; flip horizontally.
  756. sta spriteTableStart + 3, X
  757. ; Update secondary sprite table.
  758. phy ; Save enemyShipArray index.
  759. ldy $00
  760. lda #%11000000 ; Enable large sprite.
  761. sta spriteTableScratchStart, Y
  762. iny
  763. sty $00
  764. ply ; Restore enemyShipArray index.
  765. .rept 4
  766. inx
  767. .endr
  768. +
  769. .rept enemyShipSize
  770. iny
  771. .endr
  772. cpy #(enemyShipArrayLength * enemyShipSize)
  773. bne -
  774. ldy $00 ; Restore Y to its rightful self.
  775. ; Now add shots.
  776. sty $00 ; Save sprite table 2 index.
  777. ldy #0 ; Index into playerShotArray.
  778. -
  779. lda playerShotArray, Y
  780. cmp #0
  781. beq + ; If not enabled, skip to next shot.
  782. ; Update sprite table 1.
  783. sta spriteTableStart + 2, X ; sprite number
  784. lda playerShotArray + 1, Y
  785. sta spriteTableStart, X ; x
  786. lda playerShotArray + 2, Y
  787. sta spriteTableStart + 1, X ; y
  788. ; Update secondary sprite table.
  789. phy ; Save playerShotArray index.
  790. ldy $00
  791. lda #%01000000 ; Enable small sprite.
  792. sta spriteTableScratchStart, Y
  793. iny
  794. sty $00
  795. ply ; Restore playerShotArray index.
  796. .rept 4
  797. inx
  798. .endr
  799. +
  800. .rept shotSize
  801. iny
  802. .endr
  803. cpy #((playerShotArrayLength + enemyShotArrayLength) * shotSize)
  804. bne -
  805. ldy $00 ; Restore Y to its rightful self.
  806. ; Now add sprites to show player health.
  807. ; TODO: why aren't they in front?
  808. stz $01
  809. lda #4
  810. sta $02
  811. -
  812. lda $01
  813. cmp playerHealth
  814. beq + ; All done?
  815. lda #10
  816. sta spriteTableStart + 2, X ; sprite number
  817. lda $02
  818. sta spriteTableStart, X ; x
  819. clc
  820. adc #7
  821. sta $02
  822. lda #212
  823. sta spriteTableStart + 1, X ; y
  824. ; Set priority bits so that the sprite is drawn in front.
  825. lda #%00110000
  826. sta spriteTableStart + 3, X
  827. lda #%01000000 ; Enable small sprite.
  828. sta spriteTableScratchStart, Y
  829. .rept 4
  830. inx
  831. .endr
  832. iny
  833. inc $01
  834. bra -
  835. +
  836. ; Now clear out the unused entries in the sprite table.
  837. -
  838. cpx #spriteTable1Size
  839. beq +
  840. lda #1
  841. sta spriteTableStart, X
  842. .rept 4
  843. inx
  844. .endr
  845. bra -
  846. +
  847. rts
  848. FillSecondarySpriteTable:
  849. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  850. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  851. ; It's annoying to deal with bitfields when thinking about business logic,
  852. ; so the spriteTableScratch array contains one byte for each sprite, in
  853. ; which the two most significant bits are the "size" and "upper x" bits.
  854. ; This function is meant to be called after UpdateWorld, and packs those
  855. ; bytes into the actual bitfield that the OAM wants for the secondary
  856. ; sprite table.
  857. ;
  858. ; The expected format of every byte in the scratch sprite table is:
  859. ; sx------ s = size (0 = small, 1 = large)
  860. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  861. ldx #0 ; Index into input table.
  862. ldy #0 ; Index into output table.
  863. -
  864. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  865. .rept 4
  866. ; For each byte, the lower-order bits correspond to the lower-numbered
  867. ; sprites; therefore we insert the current sprite's bits "at the top"
  868. ; and shift them right for each successive sprite.
  869. lsr $00
  870. lsr $00
  871. lda spriteTableScratchStart, X
  872. ora $00
  873. sta $00
  874. inx
  875. .endr
  876. lda $00
  877. eor #%01010101
  878. sta spriteTable2Start, Y
  879. iny
  880. cpx #numSprites
  881. bne -
  882. rts
  883. SetBackgroundColor:
  884. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  885. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  886. ; Set the background color.
  887. ; Entry 0 corresponds to the SNES background color.
  888. stz CGADDR
  889. ; Compute and the low-order byte and store it in CGDATA.
  890. lda backgroundGreen
  891. .rept 5
  892. asl
  893. .endr
  894. ora backgroundRed
  895. sta CGDATA
  896. ; Compute the high-order byte and store it in CGDATA.
  897. lda backgroundBlue
  898. .rept 2
  899. asl
  900. .endr
  901. sta $00
  902. lda backgroundGreen
  903. .rept 3
  904. lsr
  905. .endr
  906. ora $00
  907. sta CGDATA
  908. rts
  909. VBlankHandler:
  910. jsr VBlankCounter
  911. jsr DMASpriteTables
  912. rti
  913. VBlankCounter:
  914. ; Increment a counter of how many VBlanks we've done.
  915. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  916. ; 77 hours to wrap around; that's good enough for me :)
  917. inc vBlankCounter
  918. bne +
  919. inc vBlankCounter + 1
  920. bne +
  921. inc vBlankCounter + 2
  922. +
  923. rts
  924. DMASpriteTables:
  925. ; Store at the base OAM address.
  926. ldx #$0000
  927. stx OAMADDR
  928. ; Default DMA control; destination $2104 (OAM data register).
  929. stz DMA0CTRL
  930. lda #$04
  931. sta DMA0DST
  932. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  933. ldx #spriteTableStart
  934. stx DMA0SRC
  935. stz DMA0SRCBANK
  936. ldx #spriteTableSize
  937. stx DMA0SIZE
  938. ; Kick off the DMA transfer.
  939. lda #%00000001
  940. sta DMAENABLE
  941. rts
  942. .ENDS
  943. ; Bank 1 is used for our graphics assets.
  944. .BANK 1 SLOT 0
  945. .ORG 0
  946. .SECTION "GraphicsData"
  947. SpriteData:
  948. .INCBIN "sprites32.pic"
  949. SpritePalette:
  950. .INCBIN "sprites32.clr"
  951. TileData:
  952. .INCBIN "tiles.pic"
  953. TilePalette:
  954. .INCBIN "tiles.clr"
  955. .ENDS
  956. ; Fill an entire bank with random numbers.
  957. .SEED 1
  958. .BANK 2 SLOT 0
  959. .ORG 0
  960. .SECTION "RandomBytes"
  961. .DBRND 32 * 1024, 0, 255
  962. .ENDS