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.

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