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.

658 lines
14 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 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. bit #%00000111
  194. bne +
  195. ldx #$0002
  196. bit #%10000000
  197. bne +
  198. ldx #$8002 ; Flip vertically.
  199. +
  200. stx VMDATA
  201. iny
  202. ; The tile map is 32x32 (1024 entries).
  203. cpy #1024
  204. bne -
  205. rts
  206. InitializeSpriteTables:
  207. ; This page is a good reference on SNES sprite formats:
  208. ; http://wiki.superfamicom.org/snes/show/SNES+Sprites
  209. ; It uses the same approach we're using, in which we keep a buffer of the
  210. ; sprite tables in RAM, and DMA the sprite tables to the system's OAM
  211. ; during VBlank.
  212. rep #%00100000 ; 16-bit A.
  213. ldx #$0000
  214. ; Fill sprite table 1. 4 bytes per sprite, laid out as follows:
  215. ; Byte 1: xxxxxxxx x: X coordinate
  216. ; Byte 2: yyyyyyyy y: Y coordinate
  217. ; Byte 3: cccccccc c: Starting tile #
  218. ; Byte 4: vhoopppc v: vertical flip h: horizontal flip o: priority bits
  219. ; p: palette #
  220. lda #$01
  221. -
  222. sta spriteTableStart, X
  223. .rept 4
  224. inx
  225. .endr
  226. cpx #spriteTable1Size
  227. bne -
  228. ; Fill sprite table 2. 2 bits per sprite, like so:
  229. ; bits 0,2,4,6 - High bit of the sprite's x-coordinate.
  230. ; bits 1,3,5,7 - Toggle Sprite size: 0 - small size 1 - large size
  231. ; Setting all the high bits keeps the sprites offscreen.
  232. lda #%0101010101010101
  233. -
  234. sta spriteTableStart, X
  235. inx
  236. inx
  237. cpx #spriteTableSize
  238. bne -
  239. sep #%00100000 ; 8-bit A.
  240. rts
  241. InitializeWorld:
  242. ; Start the background color as a dark blue.
  243. lda #4
  244. sta backgroundBlue
  245. ; Player's initial starting location.
  246. lda #(256 / 4)
  247. sta playerX
  248. lda #((224 - 32) / 2)
  249. sta playerY
  250. ; Next shot pointer starts at the beginning.
  251. ldx #shotArray
  252. stx nextShotPtr
  253. rts
  254. MainLoop:
  255. wai ; Wait for interrupt.
  256. jsr JoypadDebug
  257. jsr JoypadHandler
  258. jsr UpdateWorld
  259. jsr SetBackgroundColor
  260. jmp MainLoop
  261. JoypadDebug:
  262. ; Load joypad registers into RAM for easier inspection.
  263. ldx JOY1L
  264. stx joy1
  265. ldx JOY2L
  266. stx joy2
  267. rts
  268. JoypadHandler:
  269. ; TODO(mcmillen): handle joystick using 16-bit loads?
  270. JoypadUp:
  271. lda JOY1H
  272. bit #$08 ; Up
  273. beq JoypadDown ; Button not pressed.
  274. lda playerY
  275. cmp #0
  276. beq JoypadDown ; Value saturated.
  277. dec playerY
  278. dec playerY
  279. JoypadDown:
  280. lda JOY1H
  281. bit #$04 ; Down
  282. beq JoypadLeft ; Button not pressed.
  283. lda playerY
  284. cmp #(224 - 32)
  285. beq JoypadLeft ; Value saturated.
  286. inc playerY
  287. inc playerY
  288. JoypadLeft:
  289. lda JOY1H
  290. bit #$02 ; Left
  291. beq JoypadRight ; Button not pressed.
  292. lda playerX
  293. cmp #0
  294. beq JoypadRight ; Value saturated.
  295. dec playerX
  296. dec playerX
  297. JoypadRight:
  298. lda JOY1H
  299. bit #$01 ; Right
  300. beq JoypadStart ; Button not pressed.
  301. lda playerX
  302. cmp #(256 - 32)
  303. beq JoypadStart ; Value saturated.
  304. inc playerX
  305. inc playerX
  306. JoypadStart:
  307. lda JOY1H
  308. bit #$10 ; Start
  309. beq JoypadSelect ; Button not pressed.
  310. lda backgroundRed
  311. cmp #31
  312. beq JoypadSelect ; Value saturated.
  313. inc backgroundRed
  314. JoypadSelect:
  315. lda JOY1H
  316. bit #$20 ; Select
  317. beq JoypadY ; Button not pressed.
  318. lda backgroundRed
  319. cmp #0
  320. beq JoypadY ; Value saturated.
  321. dec backgroundRed
  322. JoypadY:
  323. lda JOY1H
  324. bit #$40 ; Y
  325. beq JoypadX ; Button not pressed.
  326. lda backgroundGreen
  327. cmp #0
  328. beq JoypadX ; Value saturated.
  329. dec backgroundGreen
  330. JoypadX:
  331. lda JOY1L
  332. bit #$40 ; X
  333. beq JoypadL ; Button not pressed.
  334. lda backgroundGreen
  335. cmp #31
  336. beq JoypadL ; Value saturated.
  337. inc backgroundGreen
  338. JoypadL:
  339. lda JOY1L
  340. bit #$20 ; L
  341. beq JoypadR ; Button not pressed.
  342. lda backgroundBlue
  343. cmp #0
  344. beq JoypadR ; Value saturated.
  345. dec backgroundBlue
  346. JoypadR:
  347. lda JOY1L
  348. bit #$10 ; R
  349. beq JoypadB ; Button not pressed.
  350. lda backgroundBlue
  351. cmp #31
  352. beq JoypadB ; Value saturated.
  353. inc backgroundBlue
  354. JoypadB:
  355. lda JOY1H
  356. bit #$80 ; B
  357. beq JoypadDone
  358. jsr MaybeShoot
  359. JoypadDone:
  360. rts
  361. MaybeShoot:
  362. ; If the cooldown timer is non-zero, don't shoot.
  363. lda shotCooldown
  364. cmp #0
  365. bne ++
  366. ldx nextShotPtr
  367. stx $0060
  368. ; Enable shot; set its position to player position.
  369. lda #1
  370. sta 0, X
  371. lda playerX
  372. sta 1, X
  373. lda playerY
  374. sta 2, X
  375. ; Update nextShotPtr.
  376. .rept 4
  377. inx
  378. .endr
  379. cpx #(shotArray + shotArrayLength * 4)
  380. bne +
  381. ldx #shotArray
  382. +
  383. stx nextShotPtr
  384. ; Set cooldown timer.
  385. lda #16
  386. sta shotCooldown
  387. ++
  388. rts
  389. UpdateWorld:
  390. ; TODO(mcmillen): separate out "update world" from "update sprite table".
  391. ; Update shot cooldown.
  392. lda shotCooldown
  393. cmp #0
  394. beq +
  395. dea
  396. sta shotCooldown
  397. +
  398. ; Copy player coords into sprite table.
  399. lda playerX
  400. sta $0100
  401. lda playerY
  402. sta $0101
  403. ; Set the sprite.
  404. lda #0
  405. sta $0102
  406. ; Set priority bits so that the sprite is drawn in front.
  407. lda #%00110000
  408. sta $0103
  409. ; Clear x-MSB so that the sprite is displayed.
  410. lda spriteTable2Start
  411. and #%11111110
  412. ora #%00000010 ; ... and make it the large size. (32x32)
  413. sta spriteTable2Start
  414. ; Move shot coords and copy into sprite table.
  415. ldx #0
  416. ; To modify sprite table 2 - one bit set for each active shot.
  417. ; These bits will be *removed* from the sprite table entry.
  418. stz $00
  419. UpdateShot:
  420. lsr $00
  421. lsr $00
  422. lda shotArray, X
  423. cmp #1
  424. bne DisableShot
  425. ; Add to the x-coordinate. If the carry bit is set, we went off the edge
  426. ; of the screen, so disable the shot.
  427. lda shotArray + 1, X
  428. clc
  429. adc #6 ; x velocity
  430. bcs DisableShot
  431. sta shotArray + 1, X ; Store new x-coord.
  432. ; Set up shot in sprite table.
  433. lda shotArray + 1, X ; x
  434. ; TODO(mcmillen): document that shots start at $110?
  435. sta $0110, X
  436. lda shotArray + 2, X ; y
  437. sta $0111, X
  438. lda #8 ; which sprite?
  439. sta $0112, X
  440. lda $00
  441. ora #%01000000
  442. sta $00
  443. jmp ShotDone
  444. DisableShot:
  445. ; Disable it by setting x-position to 1 and setting the high x-bit.
  446. lda #1
  447. sta $110, X
  448. ShotDone:
  449. ; TODO(mcmillen): in places where we .rept inx (etc), is it faster to use
  450. ; actual addition?
  451. .rept 4
  452. inx
  453. .endr
  454. cpx #16
  455. bne UpdateShot
  456. ; Set the enable/disable (and size) bits of the shot sprites.
  457. lda #$ff
  458. eor $00
  459. sta $0301
  460. ; Make the background scroll. Horizontal over time; vertical depending on
  461. ; player's y-coordinate.
  462. lda vBlankCounter
  463. sta BG3HOFS
  464. lda vBlankCounter + 1
  465. sta BG3HOFS
  466. lda playerY
  467. .rept 3
  468. lsr
  469. .endr
  470. sta BG3VOFS
  471. stz BG3VOFS
  472. rts
  473. SetBackgroundColor:
  474. ; The background-color bytes are (R, G, B), each ranging from [0-31].
  475. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]
  476. ; Set the background color.
  477. ; Entry 0 corresponds to the SNES background color.
  478. stz CGADDR
  479. ; Compute and the low-order byte and store it in CGDATA.
  480. lda backgroundGreen
  481. .rept 5
  482. asl
  483. .endr
  484. ora backgroundRed
  485. sta CGDATA
  486. ; Compute the high-order byte and store it in CGDATA.
  487. lda backgroundBlue
  488. .rept 2
  489. asl
  490. .endr
  491. sta $00
  492. lda backgroundGreen
  493. .rept 3
  494. lsr
  495. .endr
  496. ora $00
  497. sta CGDATA
  498. rts
  499. VBlankHandler:
  500. jsr VBlankCounter
  501. jsr DMASpriteTables
  502. rti
  503. VBlankCounter:
  504. ; Increment a counter of how many VBlanks we've done.
  505. ; This is a 24-bit counter. At 60 vblanks/second, this will take
  506. ; 77 hours to wrap around; that's good enough for me :)
  507. inc vBlankCounter
  508. bne +
  509. inc vBlankCounter + 1
  510. bne +
  511. inc vBlankCounter + 2
  512. +
  513. rts
  514. DMASpriteTables:
  515. ; Store at the base OAM address.
  516. ldx #$0000
  517. stx OAMADDR
  518. ; Default DMA control; destination $2104 (OAM data register).
  519. stz DMA0CTRL
  520. lda #$04
  521. sta DMA0DST
  522. ; Our sprites start at $0100 in bank 0 and are #$220 bytes long.
  523. ldx #spriteTableStart
  524. stx DMA0SRC
  525. stz DMA0SRCBANK
  526. ldx #spriteTableSize
  527. stx DMA0SIZE
  528. ; Kick off the DMA transfer.
  529. lda #%00000001
  530. sta DMAENABLE
  531. rts
  532. .ENDS
  533. ; Bank 1 is used for our graphics assets.
  534. .BANK 1 SLOT 0
  535. .ORG 0
  536. .SECTION "GraphicsData"
  537. SpriteData:
  538. .INCBIN "sprites32.pic"
  539. SpritePalette:
  540. .INCBIN "sprites32.clr"
  541. TileData:
  542. .INCBIN "tiles.pic"
  543. TilePalette:
  544. .INCBIN "tiles.clr"
  545. .ENDS
  546. ; Fill an entire bank with random numbers.
  547. .SEED 1
  548. .BANK 2 SLOT 0
  549. .ORG 0
  550. .SECTION "RandomBytes"
  551. .DBRND 32 * 1024, 0, 255
  552. .ENDS