Insert Labyrinth Zone water ripple effect in Sonic 2
From Sonic Retro
(Original guide by MoDule)WARNING: This guide is from a forum post dating back to June 22nd, 2008. This guide may have some inaccuracies, or may use methods to attain certain effects that are obsolete (for lack of a better word). This guide may need to be revised to be more effective and efficient for use in hacks. Any Members or Tech Members that have the knowledge to make said fixes, please feel free to do so.
I will now show you how to add the water ripple effect from Labyrinth Zone to theoretically any zone in Sonic 2. Don't get too excited yet, though. If you've played any of the beta versions, you might remember the effect being there. So why was it taken out of the final? Because things can get really slow, that's why.
If you try to include this effect in your Sonic 2 hack, you may want to browse through the SCHG How-To section, and search for redhotsonic & MoDule's various guides for speeding up various aspects of the game... in particular, porting the S3K Object Manager to Sonic 2.
First, let me begin by explaining how the ripple effect is achieved. All you really need to know is that horizontal scrolling is handled by an array of longwords beginning at $FFFFE000. Each entry holds the scroll factor of a single horizontal line of pixels going from top to bottom whereas the high word is for the foreground and the low word for the background. The water ripple basically just manipulates the array so that some lines are shifted slightly to one side or the other. How far each line is shifted is stored in a table. For the foreground it's stored in 'Deform_LZ_Data1' and the background uses the wobble data from the bubble object.
So let's begin, shall we? First, just so you know, I'm using Xenowhirl's 2007 disassembly. Now, we'll be adding to the software scrolling engine, so it would make sense to put our code in there. Since Chemical Plant Zone and Aquatic Ruin Zone are the only two zones that use water in Sonic 2, it would be best to put it somewhere in between 'SwScrl_CPZ' and 'SwScrl_ARZ'.
SwScrl_Water: ; this adds the LZ water ripple effect to any level lea (Deform_LZ_Data1).l,a3 lea (Obj0A_WobbleData).l,a2 move.b ($FFFFF7D8).w,d2 move.b d2,d3 addi.w #$80,($FFFFF7D8).w ; '€' add.w (Camera_Bg_Y_pos).w,d2 andi.w #$FF,d2 add.w (Camera_Y_pos).w,d3 andi.w #$FF,d3 lea (Horiz_Scroll_Buf).w,a1 move.w #$DF,d1 ; 'ß' move.w (Water_Level_1).w,d4 move.w (Camera_Y_pos).w,d5
I'll stop here real quick because I need to tell you that I added an equate to the disassembly to enhance readability. More specifically, it's 'Camera_Bg_Y_pos' which is located at $FFFFEE0C. You'll need to add that equate for it to work. Best put it right underneath 'Camera_Y_pos' so it looks something like this:
Camera_RAM = ramaddr( $FFFFEE00 ) Camera_X_pos = ramaddr( $FFFFEE00 ) Camera_Y_pos = ramaddr( $FFFFEE04 ) Camera_Bg_Y_pos = ramaddr( $FFFFEE0C )
So, what does this first segment do? We load the two tables containing the deformation data into a3 and a2. $FFFFF7D8 stores the 'phase' of the the ripple. Without this there'd still be a ripple, but it wouldn't move. We add $80 to it every frame which means the phase increases every second frame (level coordinates are stored as words and $80 is 1/2 of $100). The phase is copied once to d2 which is for the background and once to d3 which is for the foreground. These two registers will be used later for vertical position of the ripple plus it's phase. Both our tables of ripple data are 256 bytes long, so to avoid flowing into other data we need to use something like x mod 256. Since 256 is a power of 2 we can just and with $FF. We store the Hscroll buffer in a1. If we didn't do that all would be for nothing as we couldn't even make the ripple show without it. We move $DF (223) to d1 which is how many lines there are minus 1 (consider it a do-while statement, where the code is always executed at least once even when the condition isn't true). Moving on...
- ; as long as the camera is above the water cmp.w d4,d5 ; is camera below water? bge.s SwScrl_Water_doRipple ; if yes, branch addq.w #4,a1 ; increment pointer addq.w #1,d5 ; increment camera y pos addq.b #1,d2 addq.b #1,d3 dbf d1,- rts
Here we start checking all 224 lines from to to bottom. As long as the line we're checking isn't under water we just increment the the pointer in a1 (to the Hscroll buffer. This basically means we're not changing the horizontal position of the current line and moving on to the next). d4 and d5 are the current water level and the camera y position, respectively. We increment all of our y position counters so that in the next loop we'll be looking at the line below the current one. If there's no water in sight we've basically done nothing (except waste valuable processor time). Once we reach a line that's below the water surface we branch to the next part of the program.
; does the LZ water ripple effect once the camera is below the water SwScrl_Water_doRipple: move.b (a3,d3.w),d4 ; FG ripple effect ext.w d4 add.w d4,(a1)+ move.b (a2,d2.w),d4 ; BG ripple effect ext.w d4 add.w d4,(a1)+ addq.b #1,d2 addq.b #1,d3 dbf d1,SwScrl_Water_doRipple rts
Here we finally make the water ripple happen. Using a3 and a2 as indexes for our tables we retrieve the appropriate values for the current line (remember how we added 1/2 of $100 to the phase earlier? That becomes significant here, because we're just using the high byte of the y position, which only increases every second frame). These values are then added to the current scroll factor of the line we're focusing on. We then increment our counters like before and repeat until we reach the last line.
Deform_LZ_Data1: dc.b 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0; 0 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 16 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 32 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 48 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 64 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 80 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 96 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 112 dc.b -1, -1, -2, -2, -3, -3, -3, -3, -2, -2, -1, -1, 0, 0, 0, 0; 128 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 144 dc.b 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0; 160 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 176 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 192 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 208 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 224 dc.b 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 240
Here's the ripple data so you don't have to look for it in Sonic 1.
There, now that you understand how the water ripple effect works you might be wondering how to use it. Simple. All you have to do is locate the code for the zone you want to add the ripple to (they all start with 'SwScrl') and replace all the 'rts's with branches to our code (bra, not bsr, jmp if it's too far away). Done.
At least, I think that's all you have to do. There might be some zones that branch to somewhere outside the main code and don't return. Note that CPZ only has water in act 2, so you'll need to add a short test for that (see below).
I'd like to warn you again that this will slow down your hack noticeably if you have lots of objects on screen. As long as you are playing alone it should be okay, but if you're playing as Sonic and Tails the slowdown will be pretty bad. I have no idea how Sonic 3 manages to do that with collapsing ledges, breakable floors, animated tiles, Sonic and Tails and generally lots of sprites on screen without any slowdown at all.
(Addition by Tailsguy24)
If you've tried to add this effect to Chemical Plant Zone, you may notice strange things happening in act 1. To fix this, replace the original 'rts's with this:
tst.b (Current_act).w ; is this act 1? bne.w SwScrl_Water ; if not, proceed to initiate the Labyrinth Zone water ripple effect (this ensures that the effect does not take place in act 1) rts
What this does is check for if the current act is act 1, and if it is, does not produce the ripple effect, so now act 1 should work properly!
This brings me to a point I want to make: If you intend to create a new branch, make sure that the label isn't already being used. Otherwise, things will start to mess up.