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.

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