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.

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