Actions

SCHG How-to

Improve the fade in\fade out progression routines in Sonic 2

From Sonic Retro

Revision as of 08:51, 30 December 2011 by MarkeyJester (talk | contribs) (Created page with "{{GuideBy|MarkeyJester}} The palette fading routines in Sonic 1 and Sonic 2 (and possibly to a degree Sonic 3 & Knuckles) known as "Pal_FadeTo" and "Pal_FadeFrom", are not 100% ...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

(Original guide by MarkeyJester)

The palette fading routines in Sonic 1 and Sonic 2 (and possibly to a degree Sonic 3 & Knuckles) known as "Pal_FadeTo" and "Pal_FadeFrom", are not 100% true to their name. Here is a screenshot of Sonic 2's title screen 6 frames during fade in:

FadeInS2Old.png

From Sonic Team's point of view, it may not be incorrect and is possibly intentional, however, from a logical point of view, this is incorrect fading, what happens here is it fades the blue parts in first, then fades the green parts in, and then finally fades the red parts in, the same goes for fading out, red first, then green, finally blue. Strictly speaking for a the nearest possible perfect fade, all colours need to be fading in and out simultaneously (At the correct timing of course), so for example the colour 06E4 should fade in the pattern of:

0020 0040 0060 0080 02A0 04C2 06E4

And fade out in the pattern of:

04C2 02A0 0080 0060 0040 0020 0000

To make the above function for Sonic 2, there are two main places to look at:

The Main Routine

The main palette fading routines are known as "Pal_FadeTo" and "Pal_FadeFrom", you'll need to go to the routine "Pal_FadeTo" and replace everything from there down to "; End of function Pal_DecColor" with this:

<asm>Pal_FadeTo: move.w #$3F,($FFFFF626).w moveq #0,d0 lea (Normal_palette).w,a0 move.b ($FFFFF626).w,d0 adda.w d0,a0 moveq #0,d1 move.b ($FFFFF627).w,d0

loc_23DE

Pal_ToBlack: move.w d1,(a0)+ dbf d0,Pal_ToBlack ; fill palette with $000 (black) moveq #$0E,d4 ; MJ: prepare maximum colour check moveq #$00,d6 ; MJ: clear d6

- bsr.w RunPLC_RAM move.b #$12,(Delay_Time).w bsr.w DelayProgram bchg #$00,d6 ; MJ: change delay counter beq - ; MJ: if null, delay a frame bsr.s Pal_FadeIn subq.b #$02,d4 ; MJ: decrease colour check bne - ; MJ: if it has not reached null, branch move.b #$12,(Delay_Time).w ; MJ: wait for V-blank again (so colours transfer) bra DelayProgram ; MJ:

End of function Pal_FadeTo
---------------------------------------------------------------------------
Palette fade-in subroutine
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_23FE

Pal_FadeIn: moveq #0,d0 lea (Normal_palette).w,a0 lea (Second_palette).w,a1 move.b ($FFFFF626).w,d0 adda.w d0,a0 adda.w d0,a1 move.b ($FFFFF627).w,d0

- bsr.s Pal_AddColor dbf d0,- tst.b (Water_flag).w beq.s return_243C moveq #0,d0 lea (Underwater_palette).w,a0 lea (Underwater_palette_2).w,a1 move.b ($FFFFF626).w,d0 adda.w d0,a0 adda.w d0,a1 move.b ($FFFFF627).w,d0

- bsr.s Pal_AddColor dbf d0,-

return_243C: rts

End of function Pal_FadeIn


||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_243E

Pal_AddColor: move.b (a1),d5 ; MJ: load blue move.w (a1)+,d1 ; MJ: load green and red move.b d1,d2 ; MJ: load red lsr.b #$04,d1 ; MJ: get only green andi.b #$0E,d2 ; MJ: get only red move.w (a0),d3 ; MJ: load current colour in buffer cmp.b d5,d4 ; MJ: is it time for blue to fade? bhi FCI_NoBlue ; MJ: if not, branch addi.w #$0200,d3 ; MJ: increase blue

FCI_NoBlue: cmp.b d1,d4 ; MJ: is it time for green to fade? bhi FCI_NoGreen ; MJ: if not, branch addi.b #$20,d3 ; MJ: increase green

FCI_NoGreen: cmp.b d2,d4 ; MJ: is it time for red to fade? bhi FCI_NoRed ; MJ: if not, branch addq.b #$02,d3 ; MJ: increase red

FCI_NoRed: move.w d3,(a0)+ ; MJ: save colour rts ; MJ: return

End of function Pal_AddColor


||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_246A

Pal_FadeFrom: move.w #$3F,($FFFFF626).w moveq #$07,d4 ; MJ: set repeat times moveq #$00,d6 ; MJ: clear d6

- bsr.w RunPLC_RAM move.b #$12,(Delay_Time).w bsr.w DelayProgram bchg #$00,d6 ; MJ: change delay counter beq - ; MJ: if null, delay a frame bsr.s Pal_FadeOut dbf d4,- rts

End of function Pal_FadeFrom
---------------------------------------------------------------------------
Palette fade-out subroutine
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_248A

Pal_FadeOut: moveq #0,d0 lea (Normal_palette).w,a0 move.b ($FFFFF626).w,d0 adda.w d0,a0 move.b ($FFFFF627).w,d0

- bsr.s Pal_DecColor dbf d0,- moveq #0,d0 lea (Underwater_palette).w,a0 move.b ($FFFFF626).w,d0 adda.w d0,a0 move.b ($FFFFF627).w,d0

- bsr.s Pal_DecColor dbf d0,- rts

End of function Pal_FadeOut


||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_24B8

Pal_DecColor: move.w (a0),d5 ; MJ: load colour move.w d5,d1 ; MJ: copy to d1 move.b d1,d2 ; MJ: load green and red move.b d1,d3 ; MJ: load red andi.w #$0E00,d1 ; MJ: get only blue beq FCO_NoBlue ; MJ: if blue is finished, branch subi.w #$0200,d5 ; MJ: decrease blue

FCO_NoBlue: andi.b #$E0,d2 ; MJ: get only green beq FCO_NoGreen ; MJ: if green is finished, branch subi.b #$20,d5 ; MJ: decrease green

FCO_NoGreen: andi.b #$0E,d3 ; MJ: get only red beq FCO_NoRed ; MJ: if red is finished, branch subq.b #$02,d5 ; MJ: decrease red

FCO_NoRed: move.w d5,(a0)+ ; MJ: save new colour rts ; MJ: return

End of function Pal_DecColor</asm>

Title Screen Specific Routine

Unfortunately, because of how unique the title screen is, the above change will not apply very well to it, hence, we have to change the specific routines responsible for its special fading/control.

At routine "ObjC9", you'll find this code below:

<asm>ObjC9: moveq #0,d0 move.b routine(a0),d0 move.w loc_132FE(pc,d0.w),d1 jmp loc_132FE(pc,d1.w)

===========================================================================

loc_132FE: ori.b #$46,d4 addq.b #2,routine(a0) moveq #0,d0 move.b subtype(a0),d0 lea (PaletteChangerDataIndex).l,a1 adda.w (a1,d0.w),a1 move.l (a1)+,objoff_3A(a0) movea.l (a1)+,a2 move.b (a1)+,d0 move.w d0,objoff_34(a0) lea (Second_palette).w,a3 adda.w d0,a3 move.b (a1)+,d0 move.w d0,objoff_36(a0)

loc_1332E: move.w (a2)+,(a3)+ dbf d0,loc_1332E move.b (a1)+,d0 move.b d0,objoff_30(a0) move.b d0,objoff_31(a0) move.b (a1)+,objoff_32(a0) rts

===========================================================================

; unused/dead code ; a0=object

subq.b #1,objoff_30(a0) bpl.s return_1337A move.b objoff_31(a0),objoff_30(a0) subq.b #1,objoff_32(a0) bmi.w DeleteObject movea.l objoff_3A(a0),a2 movea.l a0,a3 move.w objoff_36(a0),d0 move.w objoff_34(a0),d1 lea (Normal_palette).w,a0 adda.w d1,a0 lea (Second_palette).w,a1 adda.w d1,a1

- jsr (a2) ; dynamic call! to Pal_AddColor, loc_1344C, or loc_1348A, assuming the PaletteChangerData pointers haven't been changed dbf d0,- movea.l a3,a0

return_1337A: rts</asm>

There's a problem here however, that first ori.b #$46,d4 is infact two words of pointer data being 0004 and 0046, these are the routines address minus the table start address, this means that the "; unused/dead code" isn't actually unused, the table was just disassembled incorrectly, below I have provided a fix for this mistake, along with the necessary changes required for the fading to perform correctly:

<asm>ObjC9:

       moveq   #0,d0
       move.b  routine(a0),d0
       move.w  loc_132FE(pc,d0.w),d1
       jmp     loc_132FE(pc,d1.w)
===========================================================================

loc_132FE:

       dc.w    loc_13302-loc_132FE
       dc.w    NewRoutine-loc_132FE

loc_13302:

       addq.b  #2,routine(a0)
       moveq   #0,d0
       move.b  subtype(a0),d0
       lea     (PaletteChangerDataIndex).l,a1
       adda.w  (a1,d0.w),a1
       move.l  (a1)+,objoff_3A(a0)
       movea.l (a1)+,a2
       move.b  (a1)+,d0
       move.w  d0,objoff_34(a0)
       lea     (Second_palette).w,a3
       adda.w  d0,a3
       move.b  (a1)+,d0
       move.w  d0,objoff_36(a0)
               move.b  #$0E,objoff_33(a0)                      ; MJ: set fade counter

loc_1332E:

       move.w  (a2)+,(a3)+
       dbf     d0,loc_1332E
       move.b  (a1)+,d0
       move.b  d0,objoff_30(a0)
       move.b  d0,objoff_31(a0)
       move.b  (a1)+,objoff_32(a0)
       rts
===========================================================================
       ; unused/dead code ; a0=object

NewRoutine:

       subq.b  #1,objoff_30(a0)
       bpl.s   return_1337A
       move.b  objoff_31(a0),objoff_30(a0)
       subq.b  #1,objoff_32(a0)
       bmi.w   DeleteObject
               moveq   #$00,d4                                 ; MJ: clear d4
               move.b  objoff_33(a0),d4                        ; MJ: load fade counter
               subq.b  #$02,objoff_33(a0)                      ; MJ: decrease fade counter
       movea.l objoff_3A(a0),a2                                ; load routine to run
       movea.l a0,a3                                           ; store current object
       move.w  objoff_36(a0),d0                                ; load length
       move.w  objoff_34(a0),d1                                ; load loadtooffset
       lea     (Normal_palette).w,a0
       adda.w  d1,a0
       lea     (Second_palette).w,a1
       adda.w  d1,a1

- jsr (a2)  ; dynamic call! to Pal_AddColor, loc_1344C, or loc_1348A, assuming the PaletteChangerData pointers haven't been changed

       dbf     d0,-
       movea.l a3,a0

return_1337A:

       rts</asm>

This will ensure the object uses our new fading routines correctly, the next thing to do is at:

<asm>off_1338C: C9PalInfo Pal_AddColor, Pal_1342C, $60, $F,2,$15</asm>

Because of the nature of the new fade in routine being faster in the transition, we need to change the end value $15 to something smaller, say $07:

<asm>off_1338C: C9PalInfo Pal_AddColor, Pal_1342C, $60, $F,2,$07 ; MJ: 15 to 7</asm>

This ensures the fading in routine is used correctly by the object, now there's one more fix to look at, if the player presses start to skip the transition halfway through fading in, the object continues fading in even though the entire full palette is loaded, to fix this goto "loc_135D6:":

<asm>loc_135D6: move.l (a1)+,(a2)+ dbf d6,loc_135D6 tst.b objoff_30(a0) bne.s return_135E8 moveq #$19+$80,d0 ; title music bsr.w JmpTo4_PlayMusic</asm>

And place this instruction just after the "dbf d6,loc_135D6" instruction:

<asm> sf.b (Object_RAM+($100+$32)).w ; set fade counter to 00 (finish)</asm>

This will set the palette fading objects fade counter to 0 and set the object to delete itself, thus preventing it from messing with the palette if the transition was skipped.

If the above instructions have been followed carefully, then this will ensure that the colour fading scheme fades normally and smoothly, here is the new fading routine in action, 6 frames after fading in:

FadeInS2New.png