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.

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