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.

727 lines
16 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
9 years ago
  1. .INCLUDE "header.asm"
  2. .INCLUDE "init.asm"
  3. .INCLUDE "registers.asm"
  4. ; Memory layout:
  5. ; 0000-000F: scratch space for functions.
  6. ; 0010-0011: controller state of joypad #1.
  7. ; 0012-0013: controller state of joypad #2.
  8. ; 0014-0016: 24-bit counter of vblanks.
  9. ; 0017-0019: RGB color values to use for background color, from [0-31].
  10. ; 001A-001B: 16-bit pointer to next random byte.
  11. ; [gap]
  12. ; 0020-0021: (x, y) coordinates of player.
  13. ; 0022: shot cooldown timer.
  14. ; 0023-0024: index of next shot.
  15. ; [gap]
  16. ; 0030-008F: {enable, x, y, x-velocity} per shot (max 16 shots).
  17. ; I've reserved room so that these can be 6 bytes eventually.
  18. ; [gap]
  19. ; Sprite table buffers -- copied each frame to OAM during VBlank, using DMA.
  20. ; 0100-02FF: table 1 (4 bytes each: x/y coord, tile #, flip/priority/palette)
  21. ; 0300-031F: table 2 (2 bits each: high x-coord bit, size)
  22. .define joy1 $10
  23. .define joy2 $12
  24. .define vBlankCounter $14
  25. .define backgroundRed $17
  26. .define backgroundGreen $18
  27. .define backgroundBlue $19
  28. .define randomBytePtr $1A
  29. .define playerX $20
  30. .define playerY $21
  31. .define shotCooldown $22
  32. .define nextShotPtr $23
  33. .define shotArray $30
  34. .define shotArrayLength 16
  35. .define shotSize 4
  36. ; TODO(mcmillen): verify that we can relocate these without messing things up.
  37. .define numSprites 128
  38. .define spriteTableStart $100
  39. .define spriteTable1Size $200
  40. .define spriteTable2Start $300
  41. .define spriteTableSize $220
  42. .define spriteTableScratchStart $320
  43. ; Sets A to 8-bit (& enables 8-bit "B" register).
  44. .MACRO SetA8Bit
  45. sep #%00100000 ; 8-bit A/B.
  46. .ENDM
  47. ; Sets A to 16-bit.
  48. .MACRO SetA16Bit
  49. rep #%00100000 ; 16-bit A.
  50. .ENDM
  51. ; Sets X/Y to 16-bit.
  52. .MACRO SetXY16Bit
  53. rep #%00010000 ; 16-bit X/Y.
  54. .ENDM
  55. ; Stores result to A.
  56. ; Assumes 16-bit X & 8-bit A.
  57. ; Modifies X.
  58. ; Updates randomBytePtr.
  59. .MACRO GetRandomByte
  60. ldx randomBytePtr
  61. lda $028000, X ; $028000: beginning of ROM bank 2.
  62. inx
  63. cpx #$8000 ; This is the size of the entire ROM bank.
  64. bne +
  65. ldx #0
  66. +
  67. stx randomBytePtr
  68. .ENDM
  69. .BANK 0 SLOT 0
  70. .ORG 0
  71. .SECTION "MainCode"
  72. Start:
  73. InitializeSNES
  74. ; By default we assume 16-bit X/Y and 8-bit A.
  75. ; If any code wants to change this, it's expected to do so itself,
  76. ; and to change them back to the defaults before returning.
  77. SetXY16Bit
  78. SetA8Bit
  79. jsr LoadPaletteAndTileData
  80. jsr InitializeSpriteTables
  81. jsr InitializeWorld
  82. ; Set screen mode: 16x16 tiles for backgrounds, mode 1.
  83. lda #%11000001
  84. sta BGMODE
  85. ; Set sprite size to 16x16 (small) and 32x32 (large).
  86. lda #%01100000
  87. sta OAMSIZE
  88. ; Main screen: enable sprites & BG3.
  89. lda #%00010100
  90. sta MSENABLE
  91. ; Turn on the screen.
  92. ; Format: x000bbbb
  93. ; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0-$F)
  94. lda #%00001111
  95. sta INIDISP
  96. jmp MainLoop
  97. LoadPaletteAndTileData:
  98. ; For more details on DMA, see:
  99. ; http://wiki.superfamicom.org/snes/show/Grog%27s+Guide+to+DMA+and+HDMA+on+the+SNES
  100. ; http://wiki.superfamicom.org/snes/show/Making+a+Small+Game+-+Tic-Tac-Toe
  101. ;
  102. ; A lot of the graphics-related registers are explained in Qwertie's doc:
  103. ; http://emu-docs.org/Super%20NES/General/snesdoc.html
  104. ; ... but be careful, because there are some errors in this doc.
  105. ;
  106. ; bazz's tutorial (available from http://wiki.superfamicom.org/snes/) is
  107. ; quite helpful with palette / sprites / DMA, especially starting at
  108. ; http://wiki.superfamicom.org/snes/show/Working+with+VRAM+-+Loading+the+Palette
  109. ; Initialize the palette memory in a loop.
  110. ; We could also do this with a DMA transfer (like we do with the tile data
  111. ; below), but it seems overkill for just a few bytes. :)
  112. ; TODO(mcmillen): do it with a DMA transfer.
  113. ; First, sprite palette data:
  114. ldx #0
  115. lda #128 ; Palette entries for sprites start at 128.
  116. sta CGADDR
  117. -
  118. lda.l SpritePalette, X
  119. sta CGDATA
  120. inx
  121. cpx #32 ; 32 bytes of palette data.
  122. bne -
  123. ; Now, BG3 palette data.
  124. ; Palette entries for BG3 start at 0.
  125. ldx #0
  126. lda #0
  127. sta CGADDR
  128. -
  129. lda.l TilePalette, X
  130. sta CGDATA
  131. inx
  132. cpx #8 ; 8 bytes of palette data.
  133. bne -
  134. ; TODO(mcmillen): make the "DMA stuff into VRAM" a macro or function.
  135. ; Set VMADDR to where we want the DMA to start. We'll store sprite data
  136. ; at the beginning of VRAM.
  137. ldx #$0000
  138. stx VMADDR
  139. ; DMA 0 source address & bank.
  140. ldx #SpriteData
  141. stx DMA0SRC
  142. lda #:SpriteData
  143. sta DMA0SRCBANK
  144. ; DMA 0 transfer size. Equal to the size of sprites32.pic.
  145. ldx #2048
  146. stx DMA0SIZE
  147. ; DMA 0 control register.
  148. ; Transfer type 001 = 2 addresses, LH.
  149. lda #%00000001
  150. sta DMA0CTRL
  151. ; DMA 0 destination.
  152. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  153. sta DMA0DST
  154. ; Enable DMA channel 0.
  155. lda #%00000001
  156. sta DMAENABLE
  157. ; Store background tile data at byte $2000 of VRAM.
  158. ; (VMADDR is a word address, so multiply by 2 to get the byte address.)
  159. ldx #$1000
  160. stx VMADDR
  161. ; DMA 0 source address & bank.
  162. ldx #TileData
  163. stx DMA0SRC
  164. lda #:TileData
  165. sta DMA0SRCBANK
  166. ; DMA 0 transfer size. Equal to the size of tiles.pic.
  167. ldx #512
  168. stx DMA0SIZE
  169. ; DMA 0 control register.
  170. ; Transfer type 001 = 2 addresses, LH.
  171. lda #%00000001
  172. sta DMA0CTRL
  173. ; DMA 0 destination.
  174. lda #$18 ; The upper byte is assumed to be $21, so this is $2118 & $2119.
  175. sta DMA0DST
  176. ; Enable DMA channel 0.
  177. lda #%00000001
  178. sta DMAENABLE
  179. ; Tell the system that the BG3 tilemap starts at $4000.
  180. lda #%00100000
  181. sta BG3TILEMAP
  182. ; ... and that the background tile data for BG3 starts at $2000.
  183. lda #%00000001
  184. sta BG34NBA
  185. ; Set up the BG3 tilemap.
  186. ; VRAM write mode: increments the address every time we write a word.
  187. lda #%10000000
  188. sta VMAIN
  189. ; Set word address for accessing VRAM.
  190. ldx #$2000 ; BG 3 tilemap starts here. (Byte address $4000.)
  191. stx VMADDR
  192. ; Now write entries into the tile map.
  193. ldy #0
  194. -
  195. GetRandomByte
  196. sta $00
  197. ldx #$0000 ; This is a blank tile.
  198. ; 1 in 8 chance that we choose a non-blank tile.
  199. bit #%00000111
  200. bne +
  201. ldx #$0002
  202. bit #%10000000
  203. bne +
  204. ldx #$8002 ; Flip vertically.
  205. +
  206. stx VMDATA
  207. iny
  208. ; The tile map is 32x32 (1024 entries).
  209. cpy #1024
  210. bne -
  211. rts
  212. InitializeSpriteTables:
  213. ; This page is a good reference on SNES sprite formats:
  214. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  215. ; It uses the same approach we're using, in which we keep a buffer of the
  216. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  217. ; during VBlank.
  218. SetA16Bit
  219. ldx #$0000
  220. ; Fill sprite table 1. 4 bytes per sprite, laid out as follows:
  221. ; Byte 1: xxxxxxxx x: X coordinate
  222. ; Byte 2: yyyyyyyy y: Y coordinate
  223. ; Byte 3: cccccccc c: Starting tile #
  224. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  225. ; p: palette #
  226. lda #$01
  227. -
  228. sta spriteTableStart, X
  229. .rept 4
  230. inx
  231. .endr
  232. cpx #spriteTable1Size
  233. bne -
  234. ; Fill sprite table 2. 2 bits per sprite, like so:
  235. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  236. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  237. ; Setting all the high bits keeps the sprites offscreen.
  238. lda #$FFFF
  239. -
  240. sta spriteTableStart, X
  241. inx
  242. inx
  243. cpx #spriteTableSize
  244. bne -
  245. SetA8Bit
  246. rts
  247. InitializeWorld:
  248. ; Start the background color as a dark blue.
  249. lda #4
  250. sta backgroundBlue
  251. ; Player's initial starting location.
  252. lda #(256 / 4)
  253. sta playerX
  254. lda #((224 - 32) / 2)
  255. sta playerY
  256. ; Next shot pointer starts at the beginning.
  257. ldx #shotArray
  258. stx nextShotPtr
  259. rts
  260. MainLoop:
  261. lda #%10000001 ; Enable NMI interrupt & auto joypad read.
  262. sta NMITIMEN
  263. wai ; Wait for interrupt.
  264. lda #%00000001 ; Disable NMI interrupt while processing.
  265. sta NMITIMEN
  266. jsr JoypadRead
  267. jsr JoypadHandler
  268. jsr UpdateWorld
  269. jsr FillSecondarySpriteTable
  270. jsr SetBackgroundColor
  271. jmp MainLoop
  272. JoypadRead:
  273. ; Load joypad registers into RAM for easy inspection & manipulation.
  274. -
  275. lda HVBJOY
  276. bit #$01 ; If auto-joypad read is happening, loop.
  277. bne -
  278. ldx JOY1L
  279. stx joy1
  280. ldx JOY2L
  281. stx joy2
  282. rts
  283. JoypadHandler:
  284. JoypadUp:
  285. lda joy1 + 1
  286. bit #$08 ; Up
  287. beq JoypadDown ; Button not pressed.
  288. lda playerY
  289. cmp #0
  290. beq JoypadDown ; Value saturated.
  291. dec playerY
  292. dec playerY
  293. JoypadDown:
  294. lda joy1 + 1
  295. bit #$04 ; Down
  296. beq JoypadLeft ; Button not pressed.
  297. lda playerY
  298. cmp #(224 - 32)
  299. beq JoypadLeft ; Value saturated.
  300. inc playerY
  301. inc playerY
  302. JoypadLeft:
  303. lda joy1 + 1
  304. bit #$02 ; Left
  305. beq JoypadRight ; Button not pressed.
  306. lda playerX
  307. cmp #0
  308. beq JoypadRight ; Value saturated.
  309. dec playerX
  310. dec playerX
  311. JoypadRight:
  312. lda joy1 + 1
  313. bit #$01 ; Right
  314. beq JoypadStart ; Button not pressed.
  315. lda playerX
  316. cmp #(256 - 32)
  317. beq JoypadStart ; Value saturated.
  318. inc playerX
  319. inc playerX
  320. JoypadStart:
  321. lda joy1 + 1
  322. bit #$10 ; Start
  323. beq JoypadSelect ; Button not pressed.
  324. lda backgroundRed
  325. cmp #31
  326. beq JoypadSelect ; Value saturated.
  327. inc backgroundRed
  328. JoypadSelect:
  329. lda joy1 + 1
  330. bit #$20 ; Select
  331. beq JoypadY ; Button not pressed.
  332. lda backgroundRed
  333. cmp #0
  334. beq JoypadY ; Value saturated.
  335. dec backgroundRed
  336. JoypadY:
  337. lda joy1 + 1
  338. bit #$40 ; Y
  339. beq JoypadX ; Button not pressed.
  340. lda backgroundGreen
  341. cmp #0
  342. beq JoypadX ; Value saturated.
  343. dec backgroundGreen
  344. JoypadX:
  345. lda joy1
  346. bit #$40 ; X
  347. beq JoypadL ; Button not pressed.
  348. lda backgroundGreen
  349. cmp #31
  350. beq JoypadL ; Value saturated.
  351. inc backgroundGreen
  352. JoypadL:
  353. lda joy1
  354. bit #$20 ; L
  355. beq JoypadR ; Button not pressed.
  356. lda backgroundBlue
  357. cmp #0
  358. beq JoypadR ; Value saturated.
  359. dec backgroundBlue
  360. JoypadR:
  361. lda joy1
  362. bit #$10 ; R
  363. beq JoypadB ; Button not pressed.
  364. lda backgroundBlue
  365. cmp #31
  366. beq JoypadB ; Value saturated.
  367. inc backgroundBlue
  368. JoypadB:
  369. lda joy1 + 1
  370. bit #$80 ; B
  371. beq JoypadDone
  372. jsr MaybeShoot
  373. JoypadDone:
  374. rts
  375. MaybeShoot:
  376. ; If the cooldown timer is non-zero, don't shoot.
  377. lda shotCooldown
  378. cmp #0
  379. bne ++
  380. ldx nextShotPtr
  381. ; Enable shot; set its position to player position.
  382. lda #1
  383. sta 0, X
  384. lda playerX
  385. sta 1, X
  386. lda playerY
  387. sta 2, X
  388. lda #6 ; x-velocity.
  389. sta 3, X
  390. ; Update nextShotPtr.
  391. .rept shotSize
  392. inx
  393. .endr
  394. cpx #(shotArray + shotArrayLength * shotSize)
  395. bne +
  396. ldx #shotArray
  397. +
  398. stx nextShotPtr
  399. ; Set cooldown timer.
  400. lda #10
  401. sta shotCooldown
  402. ++
  403. rts
  404. UpdateWorld:
  405. ; TODO(mcmillen): separate out "update world" from "update sprite table".
  406. ; Zero out the scratch space for the secondary sprite table.
  407. ldx #0
  408. -
  409. stz spriteTableScratchStart, X
  410. inx
  411. cpx #numSprites
  412. bne -
  413. ; Update shot cooldown.
  414. lda shotCooldown
  415. cmp #0
  416. beq +
  417. dea
  418. sta shotCooldown
  419. +
  420. ; Copy player coords into sprite table.
  421. lda playerX
  422. sta $0100
  423. lda playerY
  424. sta $0101
  425. ; Set the sprite.
  426. lda #0
  427. sta $0102
  428. ; Set priority bits so that the sprite is drawn in front.
  429. lda #%00110000
  430. sta $0103
  431. lda #%11000000 ; Enable large sprite.
  432. sta spriteTableScratchStart
  433. ; Move shot coords and copy into sprite table.
  434. ldx #0 ; Position in main sprite table.
  435. ldy #0 ; Position in secondary scratch sprite table.
  436. ; To modify sprite table 2 - one bit set for each active shot.
  437. ; These bits will be *removed* from the sprite table entry.
  438. stz $00
  439. UpdateShot:
  440. lsr $00
  441. lsr $00
  442. lda shotArray, X
  443. cmp #1
  444. bne DisableShot
  445. ; Add to the x-coordinate. If the carry bit is set, we went off the edge
  446. ; of the screen, so disable the shot.
  447. lda shotArray + 3, X ; x-velocity.
  448. sta $01
  449. lda shotArray + 1, X
  450. clc
  451. adc $01
  452. bcs DisableShot
  453. sta shotArray + 1, X ; Store new x-coord.
  454. ; Set up shot in sprite table.
  455. ; TODO(mcmillen): we use X for indexing both into the shots table and the
  456. ; sprites table, which is a problem as it assumes shotSize = 4.
  457. lda shotArray + 1, X ; x
  458. ; TODO(mcmillen): document that shots start at $110?
  459. sta $0110, X
  460. lda shotArray + 2, X ; y
  461. sta $0111, X
  462. lda #8 ; which sprite?
  463. sta $0112, X
  464. ; Update secondary sprite table.
  465. lda #%11000000
  466. sta spriteTableScratchStart + 4, Y
  467. jmp ShotDone
  468. DisableShot:
  469. ; Disable it by setting x-position to 1 and setting the high x-bit.
  470. lda #1
  471. sta $110, X
  472. ShotDone:
  473. ; TODO(mcmillen): in places where we .rept inx (etc), is it faster to use
  474. ; actual addition?
  475. .rept shotSize
  476. inx
  477. .endr
  478. iny
  479. cpx #(shotArrayLength * shotSize)
  480. bne UpdateShot
  481. ; Make the background scroll. Horizontal over time; vertical depending on
  482. ; player's y-coordinate.
  483. lda vBlankCounter
  484. sta BG3HOFS
  485. lda vBlankCounter + 1
  486. sta BG3HOFS
  487. lda playerY
  488. .rept 3
  489. lsr
  490. .endr
  491. sta BG3VOFS
  492. stz BG3VOFS
  493. rts
  494. FillSecondarySpriteTable:
  495. ; The secondary sprite table wants 2 bits for each sprite: one to set the
  496. ; sprite's size, and one that's the high bit of the sprite's x-coordinate.
  497. ; It's annoying to deal with bitfields when thinking about business logic,
  498. ; so the spriteTableScratch array contains one byte for each sprite, in
  499. ; which the two most significant bits are the "size" and "upper x" bits.
  500. ; This function is meant to be called after UpdateWorld, and packs those
  501. ; bytes into the actual bitfield that the OAM wants for the secondary
  502. ; sprite table.
  503. ;
  504. ; The expected format of every byte in the scratch sprite table is:
  505. ; sx------ s = size (0 = small, 1 = large)
  506. ; x = flipped high x-coordinate (so 1 behaves like "enable").
  507. ldx #0 ; Index into input table.
  508. ldy #0 ; Index into output table.
  509. -
  510. stz $00 ; Current byte; filled out by a set of 4 input table entries.
  511. .rept 4
  512. ; For each byte, the lower-order bits correspond to the lower-numbered
  513. ; sprites; therefore we insert the current sprite's bits "at the top"
  514. ; and shift them right for each successive sprite.
  515. lsr $00
  516. lsr $00
  517. lda spriteTableScratchStart, X
  518. ora $00
  519. sta $00
  520. inx
  521. .endr
  522. lda $00
  523. eor #%01010101
  524. sta spriteTable2Start, Y
  525. iny
  526. cpx #numSprites
  527. bne -
  528. rts
  529. SetBackgroundColor:
  530. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  531. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  532. ; Set the background color.
  533. ; Entry 0 corresponds to the SNES background color.
  534. stz CGADDR
  535. ; Compute and the low-order byte and store it in CGDATA.
  536. lda backgroundGreen
  537. .rept 5
  538. asl
  539. .endr
  540. ora backgroundRed
  541. sta CGDATA
  542. ; Compute the high-order byte and store it in CGDATA.
  543. lda backgroundBlue
  544. .rept 2
  545. asl
  546. .endr
  547. sta $00
  548. lda backgroundGreen
  549. .rept 3
  550. lsr
  551. .endr
  552. ora $00
  553. sta CGDATA
  554. rts
  555. VBlankHandler:
  556. jsr VBlankCounter
  557. jsr DMASpriteTables
  558. rti
  559. VBlankCounter:
  560. ; Increment a counter of how many VBlanks we've done.
  561. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  562. ; 77 hours to wrap around; that's good enough for me :)
  563. inc vBlankCounter
  564. bne +
  565. inc vBlankCounter + 1
  566. bne +
  567. inc vBlankCounter + 2
  568. +
  569. rts
  570. DMASpriteTables:
  571. ; Store at the base OAM address.
  572. ldx #$0000
  573. stx OAMADDR
  574. ; Default DMA control; destination $2104 (OAM data register).
  575. stz DMA0CTRL
  576. lda #$04
  577. sta DMA0DST
  578. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  579. ldx #spriteTableStart
  580. stx DMA0SRC
  581. stz DMA0SRCBANK
  582. ldx #spriteTableSize
  583. stx DMA0SIZE
  584. ; Kick off the DMA transfer.
  585. lda #%00000001
  586. sta DMAENABLE
  587. rts
  588. .ENDS
  589. ; Bank 1 is used for our graphics assets.
  590. .BANK 1 SLOT 0
  591. .ORG 0
  592. .SECTION "GraphicsData"
  593. SpriteData:
  594. .INCBIN "sprites32.pic"
  595. SpritePalette:
  596. .INCBIN "sprites32.clr"
  597. TileData:
  598. .INCBIN "tiles.pic"
  599. TilePalette:
  600. .INCBIN "tiles.clr"
  601. .ENDS
  602. ; Fill an entire bank with random numbers.
  603. .SEED 1
  604. .BANK 2 SLOT 0
  605. .ORG 0
  606. .SECTION "RandomBytes"
  607. .DBRND 32 * 1024, 0, 255
  608. .ENDS