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.

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