- Group:
- Tech Member: Tech Members
- Active Posts:
- 308 (0.11 per day)
- Most Active In:
- Engineering & Reverse Engineering (189 posts)
- Joined:
- 03-October 07
- Profile Views:
- 2568
- Last Active:
Nov 26 2014 09:17 PM- Currently:
- Offline
My Information
- Age:
- 27 years old
- Birthday:
- July 5, 1988
- Gender:
-
Male
Contact Information
- E-mail:
- Private
- Website:
-
http://
Previous Fields
- Project:
- Procrastinating from writing bug-fix guides
- National Flag:
- de
- Wiki edits:
- 52
Latest Visitors
-
Varion Icaria 
30 May 2015 - 23:52 -
Hitaxas 
17 Apr 2015 - 00:58 -
snkenjoi 
17 Nov 2014 - 21:18 -
Xeta 
20 Sep 2014 - 10:56 -
KingofHarts 
09 Sep 2014 - 02:27
Posts I've Made
-
In Topic: Some changes and fixes for Sonic 2
28 May 2013 - 10:29 AM
A new day, a new guide.
... Actually, this one probably could have been a part of yesterday's guide, but oh well.
Today we will be performing an arguably more useful fix than yesterday: we'll make it so that characters can duck under solid objects. I actually already made a fix for this a long time ago, but it was messy, and I never made it public.
Fix incorrect solid object collisions while ducking
In all of the main series 16-bit Sonic games, there's a bug where ducking has no effect on solid object collisions. When checking the lower boundary of a solid object, characters will always behave as if they were standing, unless they are rolling. Here, we are going to fix that.
Fixing the bug
First, locate the label SolidObject_cont(loc_199E8). This is the part of the solid object code that performs the hitbox checks. The relevant code looks like this:
loc_199F0: ; We now perform the x portion of a bounding box check. To do this, we assume a ; coordinate system where the x origin is at the object's left edge. move.w x_pos(a1),d0 ; load Sonic's x position... sub.w x_pos(a0),d0 ; ... and calculate his x position relative to the object add.w d1,d0 ; assume object's left edge is at (0,0). This is also Sonic's distance to the object's left edge. bmi.w loc_19AC4 ; branch, if Sonic is outside the object's left edge move.w d1,d3 add.w d3,d3 ; calculate object's width cmp.w d3,d0 bhi.w loc_19AC4 ; branch, if Sonic is outside the object's right edge ; We now perform the y portion of a bounding box check. To do this, we assume a ; coordinate system where the y origin is at the highest y position relative to the object ; at which Sonic would still collide with it. This point is ; y_pos(object) - width(object)/2 - y_radius(Sonic) - 4, ; where object is stored in (a0), Sonic in (a1), and height(object)/2 in d2. This way ; of doing it causes the object's hitbox to be vertically off-center by -4 pixels. move.b y_radius(a1),d3 ; load Sonic's y radius ext.w d3 add.w d3,d2 ; calculate maximum distance for a top collision move.w y_pos(a1),d3 ; load Sonic's y position... sub.w y_pos(a0),d3 ; ... and calculate his y position relative to the object addq.w #4,d3 ; assume a slightly lower position for Sonic add.w d2,d3 ; assume the highest position where Sonic would still be colliding with the object to be (0,0) bmi.w loc_19AC4 ; branch, if Sonic is above this point andi.w #$7FF,d3 move.w d2,d4 add.w d4,d4 ; calculate minimum distance for a bottom collision cmp.w d4,d3 bhs.w loc_19AC4 ; branch, if Sonic is below this point
Right after the third line from the bottom which calculates the minimum distance for a bottom collision, add the following code:
; Solid_ChkDuck: move.b anim(a1),d5 ; Solid_ChkDuckSonic: cmpi.b #ObjID_Sonic,id(a1) ; is character Sonic? bne.s Solid_ChkDuckTails ; if not, branch subq.b #8,d5 ; is Sonic ducking? beq.s Solid_SonicDuck ; if yes, branch subq.b #1,d5 ; is Sonic spindashing? bne.s Solid_NoDuck ; if not, branch ; Solid_SonicSpindash: subi.w #12,d4 ; reduce object's downward radius by 12 bra.s Solid_NoDuck ; --------------------------------------------------------------------------- Solid_SonicDuck: subq.w #6,d4 ; reduce object's downward radius by 6 cmpi.b #$4D,mapping_frame(a1) ; is sonic fully crouched? bne.s Solid_NoDuck ; if not, branch subq.w #6,d4 ; reduce object's downward radius by a total of 12 bra.s Solid_NoDuck ; --------------------------------------------------------------------------- Solid_ChkDuckTails: subq.b #8,d5 ; is Tails ducking? beq.s Solid_TailsDuck ; if yes, branch subq.b #1,d5 ; is Tails spindashing? bne.s Solid_NoDuck ; if not, branch ; Solid_TailsSpindash: subq.w #6,d4 ; reduce object's downward radius by 6 bra.s Solid_NoDuck ; --------------------------------------------------------------------------- Solid_TailsDuck: subi.w #12,d4 ; reduce object's downward radius by 12 Solid_NoDuck:
With this, the bug is fixed.
Extending functionality
There might be cases when the user wants to add additional animation checks, or characters. Doing this works much like extending the Touch_SetupSize macro, as described here. The only difference is that what you would add to d3 in the other guide, you'd subtract from d4 here. So if for example you wanted to reduce Tails's height while ducking by 12, here you subtract 12 from d4, as seen in Solid_TailsDuck. You also do not need to set d5 to anything, since it's not used here.
Afterthoughts
The extra overhead produced by the additional checks is multiplied by the number of solid objects on screen. This can lead to the game incurring slowdown earlier than it would normally. My proposed solution, as seen in the other guide here, is to calculate character hitbox information inside the character's code, and save it in RAM, thus eliminating the need to calculate it for every solid object's collision check.
End of guide
I know there's a few other solid object routines that I didn't touch. If someone knows what these specific routines are used for, and if they need to be fixed, too, please let me know. -
In Topic: Some changes and fixes for Sonic 2
27 May 2013 - 04:18 PM
-
In Topic: Some changes and fixes for Sonic 2
27 May 2013 - 02:03 PM
People are still hacking Sonic 2, right? Thought so. Anyway, down to business. Over the last few days I've been fixing a number of object-collision-related bugs. I think the first one has already been fixed, but I'll post my version anyway, since the subsequent fixes loosely rely on it.
So, lets start with the first one:
Fix ducking not properly reducing character's height during object collisions
In Sonic 2 and onwards there is a bug in the character-to-object collision checking code. Usually, when the player ducks or spin-dashes, one would expect their character's height to be reduced, but this actually only works for Sonic while he is ducking. Tails's height is never reduced, and spin-dashing doesn't do anything, either. Here, we are going to fix that.
Preparation
Before doing anything else, we'll define a macro that will come in handy later on:
; --------------------------------------------------------------------------- ; Macro to load a character's hitbox data to be used in one of the Touch routines. ; ; The formula to calculate a character's new Y radius when the top edge is moved, but the bottom ; edge should stay in the same place (e.I. when ducking) is as follows: ; R = (D - O)/2 ; where R is the new Y radius, D is the character's original diameter - 3 (32 for Sonic, 24 for ; Tails), and O is the difference between the new and old top edges. O should be an even number. ; --------------------------------------------------------------------------- Touch_SetupSize macro move.w x_pos(a0),d2 ; load position into d2,d3 move.w y_pos(a0),d3 subi.w #8,d2 ; assume X radius to be 8 moveq #0,d5 move.b y_radius(a0),d5 subq.b #3,d5 ; reduce Y radius by 3 sub.w d5,d3 ; set top of hitbox = Y pos - (Y radius - 3) ; Touch_ChkDuck: move.b anim(a0),d0 ; Touch_ChkDuckSonic: cmpi.b #ObjID_Sonic,id(a0) ; is character Sonic? bne.s Touch_ChkDuckTails ; if not, branch subq.b #8,d0 ; is Sonic ducking? beq.s Touch_SonicDuck ; if yes, reduce height subq.b #1,d0 ; is Sonic spindashing? bne.s Touch_NoDuck ; if not, branch ; Touch_SonicSpindash: addi.w #12,d3 ; move Sonic's top edge down 12 pixels... moveq #(32 - 12)/2,d5 ; ... and set his Y radius to 10 bra.s Touch_NoDuck ; --------------------------------------------------------------------------- Touch_SonicDuck: addq.w #6,d3 ; move Sonic's top edge down 6 pixels... moveq #(32 - 6)/2,d5 ; ... and set his Y radius to 13 cmpi.b #$4D,mapping_frame(a0) ; is sonic fully crouched? bne.s Touch_NoDuck ; if not, branch addq.w #6,d3 ; move Sonic's top edge down a total of 12 pixels... moveq #(32 - 12)/2,d5 ; ... and set his Y radius to 10 bra.s Touch_NoDuck ; --------------------------------------------------------------------------- Touch_ChkDuckTails: subq.b #8,d0 ; is Tails ducking? beq.s Touch_TailsDuck ; if yes, reduce height subq.b #1,d0 ; is Tails spindashing? bne.s Touch_NoDuck ; if not, branch ; Touch_TailsSpindash: addq.w #6,d3 ; move Tails's top edge down 6 pixels... moveq #(24 - 6)/2,d5 ; ... and set his Y radius to 9 bra.s Touch_NoDuck ; --------------------------------------------------------------------------- Touch_TailsDuck: addi.w #12,d3 ; move Tails's top edge down 12 pixels... moveq #(24 - 12)/2,d5 ; ... and set his Y radius to 6 Touch_NoDuck: move.w #8*2,d4 ; set X diameter add.w d5,d5 ; set Y diameter endm
The best place to put this macro is in s2.macros.asm. As for what this macro does, it's basically the first part of TouchResponse with a few modifications to fix the character height bug. This adds checks for ducking, and spin-dashing, as well as for Sonic's first ducking frame. What this code does in the original is set up a number of registers with coordinate, and radius values. The registers we're interested in are d3, and d5, which hold the character's bounding box's upper edge, and y-radius, respectively. The interesting part starts at Touch_ChkDuck. Since we'll be potentially checking for a number of animations, we load the current character's anim into d0. Then we check the character's id. For each of the characters, we check for the relevant animation numbers, and reduce their height if a specific animation is playing.
Extending the macro
Some users might want to do a little more with this code, like add extra animation checks, or make it work with more characters. Here I'll provide a few explanations on how to do most of the changes necessary.
To extend this code for even more characters, such as Knuckles, we will add a few lines to Touch_ChkDuckTails. Note how we make a check for Sonic's object-id at the beginning of Touch_ChkDuckSonic? We can do the same right after the label Touch_ChkDuckTails: check for the next character's id, and branch to that character's setup code if necessary. When doing this, place the extra character's code before Touch_NoDuck, as the code following it is expected to be at the end. This can be repeated for any number of characters. Note that after every extension, an unconditional branch (bra) to Touch_NoDuck needs to be added at the end of the previous character's code. For example, if we added Knuckles as a third character, we'd add a check for his id at the beginning of Touch_ChkDuckTails, add code for handling his animations before Touch_NoDuck, and then we'd need to add a branch to Touch_NoDuck at the end of the code after Touch_TailsDuck and before Touch_NoDuck.
To add additional animation checks, go to the beginning of the character-specific code. For Sonic, this would be at Touch_ChkDuckSonic. This is where we check for specific animations. Note how I used a series of subqs. That's only because it's slightly faster than using cmpi, and happens to work in this instance. Unless you understand how this works, I'd suggest you stick to using cmpi, and place your own checks in front of mine, to make sure everything still works afterwards. The animation handlers (Touch_SonicSpindash, Touch_SonicDuck, etc.) all basically work the same way: add something to d3, and overwrite d5. This is also where to do mappings-frame-specific checks, like in Touch_SonicDuck. The value to add to d3 is how many pixels the character's hitbox should be moved down; the value to write to d5 is calculated with the formula provided in the macro's documentation. Note that D is based on what the character's height would normally be, and that this isn't always the character's standing height, like while jumping.
Note
The macro can be changed into a subroutine, if that is more desirable. Note that using a macro, and calling a subroutine work differently.
Fixing the bug
Now that the macro is ready, we can get on with fixing the bug. First, go to TouchResponse(loc_3F554). Note that a portion of the code here looks a lot like our macro, as mentioned earlier. Now replace the following code:
move.w x_pos(a0),d2 ; load Sonic's position into d2,d3 move.w y_pos(a0),d3 subi.w #8,d2 moveq #0,d5 move.b y_radius(a0),d5 subq.b #3,d5 sub.w d5,d3 cmpi.b #$4D,mapping_frame(a0) ; is Sonic ducking? bne.s Touch_NoDuck ; if not, branch addi.w #$C,d3 moveq #$A,d5 ; loc_3F592: Touch_NoDuck: move.w #$10,d4 add.w d5,d5
with the macro Touch_SetupSize. To use a macro, all we have to do is write its name, and indent it like we would with any opcode. Like this:
Label: ; labels are not indented Touch_SetupSize ; macros are indented, like other code ; other code
Now all we have to do is repeat this step for Touch_Boss, Check_CNZ_bumpers, and Touch_Rings. Note that Touch_Rings has two lines inserted before the end of the code we are replacing:
move.w #6,d1 ; set ring radius move.w #12,d6 ; set ring diameter
These two lines should not be removed. Also note that Check_CNZ_bumpers is slightly different in that it assumes a slightly larger x radius. It subtracts 9 from d2 instead of 8, and subsequently sets d4 to 18 instead of 16. The macro can't do this on its own (without a slight rewrite), so for now, add this after the macro in Check_CNZ_bumpers:
subq.w #1,d2 ; X radius is larger by 1 in this routine moveq #18,d4 ; ... which means the diameter is larger by 2
This adjusts d2 and d4 to use the correct values again.
And with that, we are done.
The bug explained
The problem is quite simple. The original collision checking routine was only written with Sonic 1 in mind. In that game, Sonic only had a single ducking frame, so checking only for that frame is the natural solution. Sonic 2 however introduced a second character, Tails, Sonic has a second ducking frame, and both characters can perform the spin-dash. Unfortunately, it looks like the old collision checking code was never updated to account for this, thus making Tails unable to duck under things, Sonic only getting a height reduction during his final ducking frame, and both characters getting hit while spin-dashing by objects that would appear to go over them.
Afterthoughts
The new code can quickly bloat up into monstrous proportions when adding new characters, or animations. Ignoring size, the execution time can also increase. While speed isn't much of an issue here, it can become one when we start using this macro too often, like we will be doing in a later guide. My (as of yet unimplemented) solution to this is to have the character objects calculate their hitbox coordinates and radii inside their own code, and save these values somewhere in RAM. That way, the calculation only needs to be done once per character, as opposed to once per hitbox check. Building on this, we could eliminate the calculation altogether by using a table that maps every mappings frame to a pair of bounding box offsets and radii, but this would be difficult to do without a dedicated editor.
End of guide
As an aside, I recently made an addendum to a guide in my own topic, here. I had to switch two lines around, because there'd be a specific case when it doesn't behave correctly, otherwise. Speaking of my topic, should it just be merged with this one? It was originally meant to become what this topic here is, but forgot to make the suggestion, so now it just looks like a self-glorifying ego trip.
Anyway, there's a few more guides coming, so watch this space. -
In Topic: How to fix speed issues in Sonic 2
19 May 2013 - 07:40 AM
There's a couple other approaches that might be worth looking into. I've been toying with these for a while, but never got around to implementing them:
1.
The first one works a lot like RHS's solution above, except instead of calculating the bit values each time, we do it only once whenever something changes. Basically, we'd be saving the index into the speed stats table somewhere in RAM. In S2 we've still got a few free OST variables for Sonic and Tails, so that would work beautifully. Here's my idea in a little more detail:
We'd add a word variable stat_flags to Sonic and Tails's OST definition. Then, let's say Sonic gets a speed shoes power up. What we do now is call a routine Sonic_GiveShoes:
Sonic_GiveShoes: move.b stat_flags(a0),d0 bset #1+3,d0 ; set speed shoes bit bne.s + ; branch, if Sonic already has speed shoes move.b d0,stat_flags(a0) ; update flags ext.w d0 lea Sonic_SpeedStats(pc,d0.w),a1 move.l (a1)+,(Sonic_top_speed).w move.w (a1),(Sonic_deceleration).w + rts ; =========================================================================== ; Sonic's speed stats data ; top_speed acceleration deceleration blank Sonic_SpeedStats: dc.w $600, $C, $80, 0 ; 000 ; normal dc.w $300, $6, $40, 0 ; 001 ; under water dc.w $C00, $18, $80, 0 ; 010 ; speed shoes dc.w $500, $8, $40, 0 ; 011 ; under water & speed shoes ; Super Sonic's speed stats data dc.w $A00, $30, $100, 0 ; 100 ; normal dc.w $500, $18, $80, 0 ; 101 ; under water dc.w $C00, $30, $100, 0 ; 110 ; speed shoes dc.w $600, $18, $80, 0 ; 111 ; under water & speed shoes ; ===========================================================================
We're back to using blank table entries, but now we're only calculating what we have to (and you could use those blank table entries for something like jump height). To remove a stat, simply do a bclr. Which bits to set or clear is determined as follows: I've arranged the table so that bit 0 corresponds to being under water, bit 1 to having speed shoes, and bit 2 to being super. Since I know in advance that I always need to multiply the bit values with 8, I do so by adding 3 to the bit number (because 3 = log2(8)). You could take this one step further by using a single, non-powerup-specific routine that takes d1 as the bit to be set/cleared.
2.
Building upon the previous approach, if we sacrifice a little more RAM we can save the table address itself. So we'd use stat_addr instead of stat_flags. To change status here, we'd add or subtract 8, 16, or 32 to the saved table address. Here's some usage examples:
; changing stats addi.l #(1<<0)*8,stat_addr(a0) ; character is under water subi.l #(1<<1)*8,stat_addr(a0) ; remove speed shoes addi.l #(1<<2)*8,stat_addr(a0) ; make Sonic super ; reading stats movea.l stat_addr(a0),a1 ; load character's stats
This method is a little more dangerous, since you could end up addressing data outside the table if you're not careful.
3.
Lastly, I've got an idea about how you could optimize the approach (mine, weeee) Tiddles linked to: have two sets of stats in RAM, one normal, and one for under water. Then, when it's time to move your character around, instead of reading the values and then shifting them, we just read the under water set of values. This is even easier in S3K, since all we have to do there is add 6 to a4.
Actually, now that I think about it, the optimal solution could be a combination between 2 and 3. Save the entire stat table in RAM and use stat_addr to load a character's movement stats when they are needed. With this, you're still reading your stats from RAM (faster than reading from ROM), and you only need to use a single word per character to keep track of your stats.
But again, this isn't the best place to look for optimizations. A character's stats don't get changed very often per frame. It's better to optimize things that happen multiple times per frame. -
In Topic: How to fix speed issues in Sonic 2
17 May 2013 - 06:57 PM
I've been sitting on something like this for a while now, too, but never got around to posting it. In fact, our approaches are very similar. A few things, though:
First of all, thanks. Your version has an optimization that I didn't think of (updating two stats with a long operation).
Next, you can get away with a smaller data table without the blank words. Just multiply d0 with 6 instead of 8. Adding to that, I think you can save a few cycles by multiplying the bit-values themselves with 6, instead of doing it at the end. You'll need to change the second and third addq.w to addi.w, but the cycles those two changes add (4 each) are mitigated by not having to do three add.w's at the end (12 cycles, saving 4 cycles total). Admittedly, not much, and this isn't exactly a routine where optimization is very important, but you'll at least save some ROM space while you're at it.
Finally, I think you're going to have to make the routine character-specific, or else Tails is going to use the wrong values when Sonic turns super.

Find My Content
Nov 26 2014 09:17 PM
Male