rchoudhary wrote:That sounds more complicated to me than setting up and updating some sprites :?
Not to me. A single DMA transfer (to copy from ROM to PPU RAM) every 50 or 60 frames is a lot less effort, IMO, than managing batches of sprites. I think you'll find OAM/OBJ quite daunting once you get into it, plus sprites get their own palettes, so now you have to manage two separate palette sets in CGRAM.
Step back for a moment and think about what you're depicting in the animated GIF: literally 2 frames of animation, once a second. Ask yourself: why do you need sprites to accomplish this task? Unless, of course, you have other desires than whats depicted?
rchoudhary wrote:What programs do I use to convert this into 4BPP interleaved sprite data? And how do I take color palettes into account? For example, the three balloons use the same tiles (the brown ones in the tilesets) but they would use 3 different palettes.
I looked at superfamiconv and I'm really confused as to what exactly the inputs are and what the outputs will be... Are there any tutorials or examples?
SuperFamiconv is what you want, I assure you. Most all other graphical tools for working with SNES graphics are junk; don't let anyone tell you YY-CHR will do the trick for you, because it won't (it does not manage tile map/screen layout data, only actual CHR). There is an "Examples" entry in the SuperFamiconv README.md which I suspect you overlooked.
I've thought about this a bit. The cheapest/easiest way to do this, particularly since there's so little animation, is to not even bother with putting the confetti on its own BG -- just sort of "brute force" the entire thing: put all the graphics/data into PPU RAM, stick one frame of animation on BG1, and the other on BG2, then "flip" between the two by turning one off and the other on, etc..
I recommend your process be this:
1. Save frame 0 to its own file: frame0.png
2. Save frame 1 to its own file: frame1.png
These images need to share the same indexed palette. I can't tell how many colours there are, so I'll assume 64. Remember colour #0 is to be treated as the transparent colour. Make sure your PNGs are 256x224.
3. Use SuperFamiconv to convert the graphics appropriately. You'll only need the palette file from one, since all the graphics share the same palette:
Code: Select all
superfamiconv -B 4 -i frame0.png -p palette.bin -t frame0.chr -t frame0.map
superfamiconv -B 4 -i frame1.png -t frame1.chr -t frame1.map
You now have 7 files to use for your graphics: 3 files per frame (thus 6 files), and 1 file for the palette.
All below code assumes A = 8-bit, X/Y = 16-bit, and use of the ca65 assembler. If you're using a different assembler then this code will not work.
4. Initialise SNES registers per official documentation. You can find routines to do this, and hopefully properly. This is a one-time thing you do ONCE at reset, so don't let anyone push you around about "the most optimised reset routine" -- it's nonsense. A standard/simple reset routine is about 92-93 lines of code. This is literally copy-paste type of stuff, seriously.
Also:
make sure the screen is OFF as well (write $8F to $2100). Bit 7 being set is the important part. If you fail to do this, all your DMA transfers will probably result in gobbledegook. You'll turn the screen on later.
5. Add the raw data files into your ROM using
.incbin or equivalent like so. You may need to use different segments (banks) since these files may be too large to fit into a single 32KByte bank (mode 20 uses 32KB banks, more or less). Also add a byte of zero data to the ROM so that you can use it to zero PPU RAM via DMA:
Code: Select all
palette: .incbin "palette.bin"
palettelen = * - palette
frame0map: .incbin "frame0.map"
frame0maplen = * - frame0map
frame0chr: .incbin "frame0.chr"
frame0chrlen = * - frame0chr
frame1map: .incbin "frame1.map"
frame1maplen = * - frame1map
frame1chr: .incbin "frame1.chr"
frame1chrlen = * - frame1chr
zerobyte: .byte $00
6. Zero PPU RAM (all 64KBytes of it) using a DMA transfer, something like
ldx #$0000 / stx $2116 / lda #%00001001 / sta $4300 / lda #$18 / sta $4301 / ldx #.loword(zerobyte) / stx $4302 / lda #.bankbyte(zerobyte) ; Source bank / sta $4304 / ldx #$0000 / stx $4305 / lda #$01 / sta $420B
7. Set up PPU RAM for tile map and CHR data. Tile map data requires "1K-word" (2048 bytes) alignment, and CHR data requires "4K-word" (8192 bytes) alignment. Remember: SNES PPU RAM addresses tend to work in WORDS, not in BYTES, and this can cause a lot of confusion/pain. GET FAMILIAR WITH YOUR EMULATOR'S DEBUGGER. If it doesn't have one, GET ONE (hint: bsnes-plus or Mesen-S). A PPU RAM layout might be something like this (and these offsets are listed IN BYTES):
Code: Select all
; PPU RAM map
; -----------
; $0000-07FF = BG1 map data (32x32, single screen) -- frame 0
; $1000-17FF = BG2 map data (32x32, single screen) -- frame 1
; $2000-???? = BG1 CHR data -- frame 0
; $8000-???? = BG2 CHR data -- frame 1
The code for this setup:
Code: Select all
lda #$00 ; BG1 tile map = PPU RAM $0000-07FF, single 32x32 screen
sta $2107
lda #$08 ; BG2 tile map = PPU RAM $1000-17FF, single 32x32 screen
sta $2108
lda #$41 ; BG1 CHR = PPU RAM $2000 ($2000/2 = $1), BG2 CHR = PPU RAM $8000 ($8000/2=$4)
sta $210b
8. DMA transfer the tile map data and CHR data from ROM into PPU RAM:
Code: Select all
lda #$0000 ; BG1 tile map (PPU RAM $0000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame0map)
stx $4302
ldx #.bankbyte(frame0map)
sta $4304
ldx #frame0maplen
stx $4305
lda #$01
sta $420B
lda #$0800 ; BG2 tile map (PPU RAM $1000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame1map)
stx $4302
ldx #.bankbyte(frame1map)
sta $4304
ldx #frame1maplen
stx $4305
lda #$01
sta $420B
lda #$1000 ; BG1 CHR (PPU RAM $2000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame0chr)
stx $4302
ldx #.bankbyte(frame0chr)
sta $4304
ldx #frame0chrlen
stx $4305
lda #$01
sta $420B
lda #$4000 ; BG2 CHR (PPU RAM $8000)
stx $2116
ldx #$1801
stx $4300
ldx #.loword(frame1chr)
stx $4302
ldx #.bankbyte(frame1chr)
sta $4304
ldx #frame1chrlen
stx $4305
lda #$01
sta $420B
9. DMA transfer the palette data into CGRAM:
Code: Select all
lda #$00 ; start at palette entry #0
sta $2121
ldx #$2200
stx $4300
ldx #.loword(palette)
stx $4302
lda #.bankbyte(palette)
sta $4304
ldx #palettelen
stx $4305
lda #$01
sta $420B
10. You'll need one variable in direct page or RAM, and a couple equates. The equates control what goes into $212c, which is what controls which BGs are visible. The goal is to toggle between BG1 and BG2, with 60 frames (1 second) delay in between.
Code: Select all
showbg: .res 1 ; This needs to go in your ZEROPAGE or RAM segment or whatever you call it; 1-byte variable
showbg1 = %00000001
showbg2 = %00000010
11. Enable display of BG1 (frame 0), enable VBlank-on-NMI and automatic joypad reading (if you ever want it), and turn on the screen. Also set up a couple equates which we'll use later on:
Code: Select all
lda #showbg1
sta showbg
sta $212c
lda #$81
sta $4200
lda #$0f
sta $2100
12. At this point, frame 0 should be visible. Time to do the animation. This is pretty easy: you just flip-flop between two values written to $212c. You need to make sure that your NMI vector points to the code at the
NMI label below.
Code: Select all
mainloop:
ldx #0
waitnmi:
wai
inx
cpx #60 ; wait 60 frames
bne waitnmi
; Flip-flop $212c between values showbg1 and showbg2
lda showbg
cmp #showbg1
beq show_frame_2
lda #showbg1
sta $212c
bra next
show_frame_2:
lda #showbg2
sta $212c
next:
bra mainloop
NMI:
pha
lda showbg
sta $212c
pla
rti
There are several things omitted from this example which are for you to figure out/do on your own, such as setting up the RESET vector, ensuring register sizes are proper (via sep/rep) and so on. But the overall model should work fine.
Edit: fixing up some stuff. Probably mistakes in here too. Oh well :D