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.

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