In the original Genesis games, every ring and sparkle animation is loaded into VRAM at once. While it's serviceable enough, this does make it hard to implement a smoother animation with extra frames. However, you might realize that each ring is synchronized with each other. Each stage ring uses the same animation frame, and even the lost rings that use a separate animation handler are still synchronized with each other. So, how about we just allocated 2 spaces in VRAM, one space to load the animation frame for the stage rings, and the other for lost rings? That's what this guide is going to help you out with. You will need to have the DMA queue ported over, because that will make this whole thing a lot simpler. Part 3 of the spindash porting guide will have you covered. Step 1: Preparing the graphics For this to work, the ring graphics will need to be decompressed, because the DMA queue directly loads the graphics into VRAM. We also aren't going to do the treatment for the sparkle frames, because those are not synchronized. So, what is needed to be done is separate the ring frames from the sparkle frames, with the ring frames being decompressed, and the sparkles still compressed in Nemesis. The ring frames will also need to be reformatted in a way that will fit this new system. The idea will be to allocate 4 tiles, and have a single sprite frame display it, and just have each frame set up to fit in it. I went and ahead and did this. Put "Ring Frames.bin" in the artunc folder, put "Ring Sparkles.bin" in the artnem folder, and replace "Rings.asm" in the _maps folder. Delete "artnem/Rings.bin", since that's being replaced. Also delete "_maps/Rings (JP1).asm", because that will no longer be used. Step 2: Adding the graphics into your disassembly In sonic.asm, change Code (Text): Nem_Ring: binclude "artnem/Rings.bin" even into Code (Text): Art_Ring: binclude "artunc/Ring Frames.bin" even Nem_Sparkles: binclude "artnem/Ring Sparkles.bin" even Then change Code (Text): if Revision=0 Map_Ring: include "_maps/Rings.asm" else Map_Ring: include "_maps/Rings (JP1).asm" endif into Code (Text): Map_Ring: include "_maps/Rings.asm" Step 3: Fixing the PLCs and frame IDs With the ring frames separated from the sparkles, we must now fix up the PLCs to offset the sparkles into the correct place. The original ring frames took up $140 bytes, so that's the amount we will add to the VRAM address in the PLCs. Go into "_inc/Pattern Load Cues.asm", and change Code (Text): plcm Nem_Ring, $F640 ; rings into Code (Text): plcm Nem_Sparkles, $F780 ; ring sparkles Now, because the mappings have been reduced to only have 1 frame that displays dynamically changing graphics, we'll need to fix up some frame IDs. In "_incObj/25 & 37 Rings.asm", remove this line: Code (Text): move.b (v_ani1_frame).w,obFrame(a0) ; set frame and this line: Code (Text): move.b (v_ani3_frame).w,obFrame(a0) Those lines set the frame ID for the rings. Again, since we are only using 1 frame to display a changing graphic, we won't be using that. We will use v_ani1_frame and v_ani3_frame for the ring frame loading later. In the same file, under .makerings in the RingLoss object, change Code (Text): move.w #$27B2,obGfx(a1) to Code (Text): move.w #$27B6,obGfx(a1) The lost rings will use a different area of VRAM, since they are not synced with the other stage rings, so the tile ID is pushed down 4 tiles. However, this will need to be set back when it turns into a sparkle, so under RLoss_Sparkle, add this line: Code (Text): move.w #$27B2,obGfx(a0) In "_anim/Rings.asm", change Code (Text): .ring: dc.b 5, 4, 5, 6, 7, afRoutine into Code (Text): .ring: dc.b 5, 1, 2, 3, 4, afRoutine This changes the ring sparkle animation to use the updated frame IDs. In "_inc/Special Stage Mappings & VRAM Pointers.asm", change Code (Text): dc.l Map_Ring+$4000000 dc.w $27B2 dc.l Map_Ring+$5000000 dc.w $27B2 dc.l Map_Ring+$6000000 dc.w $27B2 dc.l Map_Ring+$7000000 dc.w $27B2 to Code (Text): dc.l Map_Ring+$1000000 dc.w $27B2 dc.l Map_Ring+$2000000 dc.w $27B2 dc.l Map_Ring+$3000000 dc.w $27B2 dc.l Map_Ring+$4000000 dc.w $27B2 This updates the ring sparkle frame IDs in the special stage. In sonic.asm, remove this line under loc_1B2C8: Code (Text): move.b (v_ani1_frame).w,$1D0(a1) This line set the frame ID for the rings, much like with the 2 removed lines in the regular ring objects. We will use v_ani1_frame for the ring frame loading later. Finally, if you are using REV01, in _inc/DebugList.asm, go to .Ending and change the 8 to a 5 in this line Code (Text): dbug Map_Ring, id_Rings, 0, 8, $27B2 This fixes the frame ID used in the ending debug object list in REV01. Step 4: Loading the ring frame graphics into VRAM Now comes the part where a ring frame is chosen to be loaded into VRAM. Add this function into your disassembly somewhere: Code (Text): ; --------------------------------------------------------------------------- ; Queue ring frame graphics loading ; --------------------------------------------------------------------------- LoadRingFrame: moveq #0,d1 ; Get ring frame offset for regular rings move.b (v_ani1_frame).w,d1 lsl.w #7,d1 ; Each ring frame takes $80 bytes, so multiply by $80 add.l #Art_Ring,d1 ; Queue a DMA transfer for this ring frame move.w #$F640,d2 move.w #$80/2,d3 jsr QueueDMATransfer cmpi.b #id_Special,(v_gamemode).w ; Are we in a special stage? beq.s .noringloss ; If so, branch moveq #0,d1 ; Get ring frame offset for lost rings move.b (v_ani3_frame).w,d1 lsl.w #7,d1 ; Each ring frame takes $80 bytes, so multiply by $80 add.l #Art_Ring,d1 ; Queue a DMA transfer for this ring frame move.w #$F640+$80,d2 move.w #$80/2,d3 jmp QueueDMATransfer .noringloss: rts Now, go to SynchroAnimate. Under SyncEnd, replace the rts with Code (Text): jmp LoadRingFrame Then, go to Level_SkipTtlCard, and place this line under the label Code (Text): jsr LoadRingFrame Then, go to SS_WaitForDMA and place that same line under Code (Text): moveq #plcid_SpecialStage,d0 bsr.w QuickPLC ; load special stage patterns Finally, go to SS_MainLoop, place that same line under Code (Text): jsr (SS_ShowLayout).l And, tada! You now have implemented a more dynamic system for ring animations. Step 5: Fixing the giant rings Now, there's one last issue to address. The giant rings at the end of the stage when you have 50 or more rings is affected by these changes. That's because it uses the same animation as the regular rings. Luckily, there is an unused set of animation variables we can use to circumvent this. Go to Sync3 under SynchroAnimate and change Code (Text): subq.b #1,(v_ani2_time).w bpl.s Sync4 move.b #7,(v_ani2_time).w addq.b #1,(v_ani2_frame).w cmpi.b #6,(v_ani2_frame).w blo.s Sync4 move.b #0,(v_ani2_frame).w to Code (Text): subq.b #1,(v_ani2_time).w bpl.s Sync4 move.b #7,(v_ani2_time).w addq.b #1,(v_ani2_frame).w andi.b #3,(v_ani2_frame).w Which is basically the original ring animation code, but applied to the unused variables, instead. Now, go to GRing_Animate in "_incObj/4B Giant Ring.asm" and change Code (Text): move.b (v_ani1_frame).w,obFrame(a0) to Code (Text): move.b (v_ani2_frame).w,obFrame(a0) This makes the giant ring object actually use the new frame variable. Step 6: (OPTIONAL) Implementing a smoother ring animation Now that we have this system, we can easily add in additional frames of animation, since only the shown frame of animation is loaded into VRAM at once, therefore completely eliminating the risk of eating up VRAM. First, download this new set of ring frames that include inbetween frames from the 2013 remake of Sonic 1 and replace the old set in the artunc folder. Now, we need to update the animation handlers to make use of the extra frames. First, let's go to SynchroAnimate. First, let's update the handler for regular rings. The code for that is right here: Code (Text): Sync2: subq.b #1,(v_ani1_time).w bpl.s Sync3 move.b #7,(v_ani1_time).w addq.b #1,(v_ani1_frame).w andi.b #3,(v_ani1_frame).w First, let's change v_ani1_time to reset to 3 instead of 7. This will halve the duration that a frame is displayed, which is needed with the doubled set of frames. Then, change the value of the "andi" instruction from 3 to 7. This change will add an extra bit in the AND mask, limiting it to bits 0-2 instead of 0-1. Basically, it'll limit the bits to use values 0-7, which exactly fits with the new set of frames. For the lost rings, there's this: Code (Text): Sync4: tst.b (v_ani3_time).w beq.s SyncEnd moveq #0,d0 move.b (v_ani3_time).w,d0 add.w (v_ani3_buf).w,d0 move.w d0,(v_ani3_buf).w rol.w #7,d0 andi.w #3,d0 move.b d0,(v_ani3_frame).w subq.b #1,(v_ani3_time).w This one is bit more complicated, since it's made to slow down the ring animation over time. Let's start with the simple change. There's another "andi" instruction. Like with the previous change, change the 3 to a 7. Now, to make it so that it doesn't animate as slowly, change the "rol" instruction value from 7 to 8. The math involved in slowing down the ring animation is that there's a counter (v_ani3_time) that ticks from 255 to 0. As it ticks down, it adds itself into another value (v_ani3_buf). The result of the addition is used to calculate the frame ID to display. The lower the counter is, the lesser the additions, and thus the slower the animation. Originally, it used bits 9 and 10 (which the rol instruction is used to rotate them to the bottom) as the ring frame ID. With the change, it basically adds bit 8 into the mix, which will change more often than the other 2 bits. This is exactly how it halves the frame duration. Now, the special stage rings. Head on over to SS_AniWallsRings and you'll see: Code (Text): subq.b #1,(v_ani1_time).w bpl.s loc_1B2C8 move.b #7,(v_ani1_time).w addq.b #1,(v_ani1_frame).w andi.b #3,(v_ani1_frame).w loc_1B2C8: Looks familiar? It's the same code as the animation handler for the regular stage rings. So, apply the same changes here. And, tada, you should have a smoother ring animation!
Interesting. I know Sonic Delta did this, so seeing this is cool. I think frame 8 of the old frame definitions is used by debug mode in Sonic 1 REV01, so that should be corrected somehow. It's blank, so I think you could just use frame 0 of Sonic's mappings instead.
Right, though it's used in the ending. Regardless, go to .Ending in _inc/DebugList.asm and change the 8 to a 5 in this line Code (Text): dbug Map_Ring, id_Rings, 0, 8, $27B2
Added an extra step, because I forgot to account for the giant rings using the same animation handler as the regular rings.