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.

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