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.

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