Actions

Difference between revisions of "SPG:Solid Tiles"

From Sonic Retro

m (Introduction)
(Major (and hopefully last) restructuring, putting emphasis on organising by sensor rather than mentioning them randomly)
Line 47: Line 47:
 
First we will look at Sonic's method for detecting his environment, then how this environment is constructed and how Sonic handles it's complexity with such a simple system.
 
First we will look at Sonic's method for detecting his environment, then how this environment is constructed and how Sonic handles it's complexity with such a simple system.
  
Notes:  
+
Note: ''While solidity tiles are used in the original games, these basic collision methods and sensor layouts will still work (with adjustments) with sprite masks, or line intersections, etc, in your own engine.''
* This guide covers how Sonic collides with the permanent solid terrain tiles. For collision with objects (which are handled quite differently) refer to [[SPG:Game_Objects|Game Objects]].
 
* While solidity tiles are used in the original games, these basic collision methods and sensor layouts will still work (with adjustments) with sprite masks, or line intersections, etc, in your own engine.
 
  
 
==Sensors==
 
==Sensors==
The collision with solid tiles is handled using 'sensor lines' that surround Sonic. They will be explained and referenced throughout this section.
+
The collision with solid tiles is handled using 'sensors' that surround Sonic. You can imagine these as lines which Sonic will not allow solid tiles to overlap, along the different axis. For collision with objects (which are handled quite differently) refer to [[SPG:Game_Objects|Game Objects]].
  
 
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]
 
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]
''An approximation of the sensors, though some might move positions or even not exist during certain states or character movements''
+
''An accurate approximation of the sensors''
  
 
   <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision
 
   <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision
Line 62: Line 60:
 
   XY - Sonic's xpos and ypos
 
   XY - Sonic's xpos and ypos
  
===Sprite alignment===
 
As you can see, Sonic's sprite are 1 pixel offset to the left when he faces left, which can result in him appearing to be 1px inside a tile when pushing leftwards. However this won't appear to happen with most objects thanks to their hitboxes sticking out 1px further on their right and bottom (due to their origins being off-centre by 1 in X and Y). So while tiles are collided with more accurately than objects, bizzarely it will appear the opposite in-game, thanks to Sonic's sprite alignment when flipped.
 
  
===Correction of long-standing guide error===
+
Since Sonic's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. Sonic has separate radius values for his <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (his '''Push Radius''') which always remains the same, and for his <span style="color:#00f000; font-weight: bold;">A</span>, <span style="color:#38ffa2; font-weight: bold;">B</span>, <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors there is a width radius (his '''Body Width Radius''') and height radius (his '''Body Height Radius''') both of which will change depending on Sonic's state.
Of importance, is that this guide has always correctly stated that the horizontal sensors stretch from ''xpos''-10 to ''xpos''+10, however '''incorrectly''' noted that Sonic is 20 pixels wide. Sonic's (and other objects for that matter) collision sizes are defined as radius, and a width radius of 10 would NOT result in a total width of 20 as previous versions of the guide have stated, but rather 21. This is due to the origins (''xpos'' and ''ypos'') of objects also being counted in the size.  
+
 
 +
===Construction===
 +
A width in Sonic games isn't simply 2 times the width radius.
  
 
[[Image:SPGHitBoxRadius.png]]
 
[[Image:SPGHitBoxRadius.png]]
  
For example, a radius of 8 would end up being 17 px wide rather than 16, and the sensors work in much the same way. Making his width radius of 10 become a total width of 21, '''not''' 20. This basically results in every collision mask or sensor arrangement always being an odd number in overall size. Sonic is still set 20 pixel above the ground or below a ceiling even being 39 pixels in total height as this includes his origin for every direction. Sonic may only '''appear''' thinner in-game because when pushing against a wall to the left, his sprites are offset by one pixel to the left (as they are when facing left at any time).
+
A radius of 8 would end up being 17 px wide rather than 16, and the sensors work in much the same way. Making his width radius of 10 become a total width of 21, '''not''' 20. This basically results in every collision mask or sensor arrangement always being an odd number in overall size. Sonic is still set 20 pixel above the ground or below a ceiling even being 39 pixels in total height as this includes his origin for every direction.
  
It's a matter of 1 pixel in width and height, but it could make all the difference.
+
It's a matter of 1 pixel in width and height, but it could make all the difference. To avoid confusion, both measurements will be mentioned (otherwise, refer to the image examples).
  
The images and information in this guide are now updated to be correct as of Jan '20.
+
Note: '''Previous versions of this guide have incorrectly shown and stated that Sonic is 20px wide among other related issues. The images and information in this guide are now updated to be correct as of Jan '20.'''
  
===While on the ground===
+
===Sprite alignment===
 +
Sonic's sprite is 1 pixel offset to the left when he faces left, which can result in him appearing to be 1px inside a tile when pushing leftwards. However to a normal person playing this won't appear to happen with most objects thanks to their hitboxes sticking out 1px further on their right and bottom (due to their origins being off-centre by 1 in X and Y). So while tiles are collided with more accurately than objects, bizzarely it will appear the opposite in-game, thanks to Sonic's sprite alignment when flipped. More about object collision in [[SPG:Game_Objects|Game Objects]].
  
====Floor Sensors====
 
Sonic has to check to check for solid tiles beneath him. This can be achieved with two sensor lines, pointing downward. One (<span style="color:#00f000; font-weight: bold;">A</span>) should be on Sonic's left side, at ''xpos''-9. The other (<span style="color:#38ffa2; font-weight: bold;">B</span>) should be on his right, at ''xpos''+9, 19 pixels wide. They should start at ''ypos'' and extend to his feet at ''ypos''+20.
 
  
These sensors should also check at least 16 pixels below his feet while grounded. So even if Sonic ends up slightly above the ground because of a downward slope (and he's already on the ground) this extra space checked is to keep him attached to the ground at all times, until a cliff higher then or equal to 16px turns up, or he jumps.
+
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===
  
 
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]
 
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]
  
Assuming the ground level to be at a Y position of 736 ($02E0), Sonic stands atop it at a ''ypos'' of 716 ($02CC), which is 20 pixels above ground level.
+
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> stretch down from Sonic's ''ypos'' to his feet at ''ypos''+'''Body Height Radius'''.
 +
 
 +
====Movement====
 +
Typically, Sonic's '''Body Width Radius''' is 9, placing <span style="color:#00f000; font-weight: bold;">A</span> on Sonic's left side, at ''xpos''-9. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on his right, at ''xpos''+9, 19 pixels wide.
 +
His '''Body Height Radius''' is usually 19, making him 39 pixels tall in total.
 +
 
 +
However while rolling or jumping, his '''Body Width Radius''' becomes 7, placing <span style="color:#00f000; font-weight: bold;">A</span> at ''xpos''-7. With <span style="color:#38ffa2; font-weight: bold;">B</span> at ''xpos''+7, 15 pixels wide. This is to ensure Sonic doesn't appear too far away from the floor at steep angles while in curled up. Crouching does not affect his '''Body Width Radius'''.
 +
 
 +
While rolling, jumping, ''or crouching'', Sonic's '''Body Height Radius''' becomes smaller at a value of 14, making him 29 pixels tall. Because of this, in the step in which Sonic rolls or jumps or otherwise becomes shorter, the engine adds 5 to his ''ypos'' so that his bottom point will remain unchanged despite him getting shorter and his center changing position. 5 also has to be subtracted from ''ypos'' when he unrolls or lands from a jump. The camera system also has to keep this offset in mind, otherwise, the view will jump when Sonic changes height.
 +
 
 +
These sensors check 16 extra pixels below his feet while grounded. So if Sonic ends up slightly above the ground because of a downward slope (and he's already on the ground) this extra space checked is to keep him attached to the ground at all times, until a cliff higher then or equal to 16px turns up, or he jumps.
 +
 
 +
====Activation====
 +
While grounded, the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors are always active, and are always checking for the ground beneath Sonic.
 +
 
 +
While airborne, the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors are only active when Sonic's ''ysp'' is positive (moving down) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".
 +
 
 +
ABSensors = ((ysp > 0) or (abs(xsp) > abs(ysp)))
 +
 
 +
 
 +
This results in Sonic's floor sensors activating when Sonic is moving fast horizontally, which helps if he is approaching a floor at a sheer horizontal angle but still moving up.
 +
 
 +
====Method====
 +
 
 +
Any time they detect a solid tile Sonic should be "popped out" to the pixel above the tile's top edge minus '''Body Height Radius'''. However, there are 2 sensors and they need to detect slopes - so greater depth will be explored after establishing where these sensors are. The following applies for flat ground while not curled up.
 +
 
 +
Assuming the ground level to be at a Y position of 736 ($02E0), while standing Sonic is atop it at a ''ypos'' of 716 ($02CC), which is 20 pixels above ground level.
  
 
=====Ledges=====
 
=====Ledges=====
Line 93: Line 116:
 
If both sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> detect no solid tiles, Sonic will "fall" - a flag will be set telling the engine he is now in the air.
 
If both sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> detect no solid tiles, Sonic will "fall" - a flag will be set telling the engine he is now in the air.
  
======Balancing On Edges======
+
=====Balancing On Edges=====
  
 
One nice touch is that Sonic goes into a balancing animation when near to the edge of a ledge. This only happens when he is stopped (his ground speed is 0).
 
One nice touch is that Sonic goes into a balancing animation when near to the edge of a ledge. This only happens when he is stopped (his ground speed is 0).
Line 111: Line 134:
 
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindashing, etc). In Sonic 3 & Knuckles, the player is still allowed to duck and spindash (not to look up, though) when balancing on the ground but not when balancing on an object.
 
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindashing, etc). In Sonic 3 & Knuckles, the player is still allowed to duck and spindash (not to look up, though) when balancing on the ground but not when balancing on an object.
  
====Pushing====
 
 
Sonic has to stop when he bumps into walls. This can be achieved by checking for a collision with horizontal sensor lines <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>.
 
 
How wide should these sensor lines be?
 
 
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]
 
 
Assuming the wall's left side to be at an X position of 704 ($02C0), Sonic cannot get closer than an ''xpos'' of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), Sonic cannot get closer than an ''xpos'' of 842 ($034A). Thus both sensor lines combined should be 21 pixels wide, stretching from Sonic's ''xpos''-10 to ''xpos''+10.
 
  
Any time it detects a solid tile, Sonic should be "popped out", set to the edge of the tile minus (or plus) 11, and his ground speed set to 0. (He cannot be popped out by only 10, because then a point at ''xpos''+10 would still lie within the edge pixel of the tile. This would register a continuous collision, and he would stick to the wall.) This will also set his '''gsp''' to 0 if he is moving in the direction of the wall, not away from it. If it were otherwise, he would stick to walls if he tried to move away.
+
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===
  
Though the tile's edge minus Sonic's ''xpos'' might be 11, there are 10 free pixels between Sonic's ''xpos'' and the tile's edge. The eleventh pixel away is the tile's edge itself. This would be the same for a tile on the left. So Sonic is effectively 21 pixels wide when including ''xpos''.
+
Sonic's <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors are always an exact mirror image of Sonic's floor sensors, they have the same X positions and length but are flipped upside down.  
  
You may remember that sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are only 19 pixels apart but Sonic is 21 pixels wide when pushing into walls. This means that Sonic is skinnier by 2 pixels when running off of ledges than when bumping into walls.
+
They perform in the exact same way simply up instead of down.
  
===While in the air===
+
However, they aren't active at the same times.
  
====Horizontal Sensor====
+
====Activation====
  
Sonic's wall sensors, <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>, behave mostly the same while in the air.
+
While grounded they are never active, and Sonic won't check for collision with solid tiles above himself while on the floor.
  
Much like when grounded, when Sonic detects a collision with his horizontal sensor lines, his ''xsp'' is only set to 0 if he is moving in the direction of the wall, not away from it.
+
While airborne, Sonic's <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors are only active when Sonic's ''ysp'' is negative (moving up) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".
  
====Vertical Sensors====
+
CDSensors = ((ysp < 0) or (abs(xsp) > abs(ysp)))
  
Sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are very much the same in the air as they are on the ground. The difference here is, when they detect a solid tile, however, Sonic isn't immediately set to the height found in the tile minus 20. Instead, he is only set to it if he is ''already'' lower than that height. Otherwise, he would stick to the floor when he gets close to it, so effectively, there is no 16px sensor extension in the air.
 
  
Because Sonic can move both up and down while in the air, there have to be two more sensors checking upward, Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>, so that he can bump into ceilings and curves that are above him. <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> always mirror <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> perfectly - they have the same X position and length but are flipped upside down) Sonic will detect ceilings and be pushed out of them whether he is moving up or down, unlike floors which are only detected when moving down. It ''is'' possible to hit a "ceiling" (which is just the bottom side of a block) while moving down - by pressing toward a wall with a gap in it, or jumping toward the side of an upper curve both with a high absolute ''xsp''.
+
This results in Sonic's ceiling sensors activating when Sonic is moving fast horizontally, which helps if he is approaching a ceiling at a sheer horizontal angle but still moving down.
  
===Sensor Activation & Movement===
+
====Method====
We've gone over what the sensors do, however sensors don't always show up or stay in the same place, depending on what Sonic is doing. This is probably done to avoid too much processing for collision detection when certain sensors aren't needed - so it's not entirely important to deactivate sensors, but it does optimise things.
 
  
==== On the ground ====
+
Sonic will detect ceilings and be pushed out of them whether he is moving up or down, unlike floors which are only detected when moving down. It ''is'' possible to hit a "ceiling" (which is just the bottom side of a block) while moving down - by pressing toward a wall with a gap in it, or jumping toward the side of an upper curve both with a high absolute ''xsp''.
While grounded, Sonic's <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors are always active, and are always checking for the ground beneath Sonic.
 
  
Sonic's <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors are always inactive while on the ground and will not detect and ceilings.
 
  
When Sonic is in a ball, he is only 29 pixels tall and his ''ypos'' is set 15 pixels above the ground (and 15 pixels below the ceiling, etc). This also applies while in the air. Aditionally sensor <span style="color:#00f000; font-weight: bold;">A</span>, <span style="color:#38ffa2; font-weight: bold;">B</span>, <span style="color:#00aeef; font-weight: bold;">C</span>, and <span style="color:#fff238; font-weight: bold;">D</span> are at ''xpos''-7 and ''xpos''+7 rather than ''xpos''-9 and ''xpos''+9. Effectively making his floor collision only 15 pixels wide. However, his horizontal sensor lines remain the same length (21 pixels) whether curled up or not. This also applies in the air.
+
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===
  
When crouching, he is 15 pixels high but <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> remain at ''xpos''-9 and ''xpos''+9.
+
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]
  
Because of this, in the step in which Sonic rolls or jumps, the engine adds 5 to his ''ypos'' so that his bottom point will remain unchanged despite him getting shorter and his center changing position. 5 also has to be subtracted from ''ypos'' when he unrolls or lands from a jump. The camera system also has to keep this offset in mind, otherwise, the view will jump when Sonic changes height.
+
<span style="color:#ff38ff; font-weight: bold;">E</span> spans from Sonic's ''xpos'' to his left at ''xpos''-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> spans from Sonic's ''xpos'' to his right at ''xpos''+'''Push Radius'''.
  
Otherwise, Sonic remains 39 pixels tall. He stands 20 pixels above the ground (and 20 pixels below ceilings when he hits into them, etc). His ''ypos'' is always his center
+
====Movement====
  
Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when Sonic is walking in that direction. For example, while standing still Sonic isn't checking with his wall sensors at all, but while ''gsp'' is positive, Sonic's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while ''gsp'' is negative, Sonic's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.
+
'''Push Radius''' is always 10, giving Sonic a total width of 21 pixels.
  
 
Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> Spend most of their time at Sonic's ''ypos'' however while Sonic's ''ang'' is 0 (on totally flat ground) both wall sensors will move to his ''ypos''+8 so that he can push against low steps and not just snap up ontop of them.
 
Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> Spend most of their time at Sonic's ''ypos'' however while Sonic's ''ang'' is 0 (on totally flat ground) both wall sensors will move to his ''ypos''+8 so that he can push against low steps and not just snap up ontop of them.
  
Here's an example of this:
+
The horizontal sensor lines are always positioned at ''ypos'' while airborne.
  
<nowiki>
+
====Activation====
    ESensor = (gsp < 0)
 
    FSensor = (gsp > 0)
 
    CDSensors = 0
 
    ABSensors = 1
 
</nowiki>
 
  
==== In the air ====
+
When grounded, Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when Sonic is walking in that direction. For example, while standing still Sonic isn't checking with his wall sensors at all, but while ''gsp'' is positive, Sonic's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while ''gsp'' is negative, Sonic's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.
  
While airborne, things get a little more complicated.
+
=====While airborne =====
  
Sonic's <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors are only active when Sonic's ''ysp'' is positive (moving down) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".
+
Sensor <span style="color:#ff38ff; font-weight: bold;">E</span> is not active when Sonic's ''xsp'' is greater than his absolute ''ysp'', so basically it only deactivates if we are moving very fast right, and not too fast up or down.
  
Sonic's <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors are only active when Sonic's ''ysp'' is negative (moving up) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".
+
Sensor <span style="color:#ff5454; font-weight: bold;">F</span> is not active when Sonic's ''xsp'' is less than the negative of his absolute ''ysp'', so basically it only deactivates if we are moving very fast left, and not too fast up or down.
  
This results in both Sonic's vertical sensor pairs activating when Sonic is moving fast horizontally, which helps if he is approaching a ceiling at a sheer angle but still moving down, or the opposite.
+
ESensor = !(xsp > abs(ysp))
 +
FSensor = !(-xsp > abs(ysp))
  
Again, when Sonic is in a ball, he is only 29 pixels tall, and sensor <span style="color:#00f000; font-weight: bold;">A</span>, <span style="color:#38ffa2; font-weight: bold;">B</span>, <span style="color:#00aeef; font-weight: bold;">C</span>, and <span style="color:#fff238; font-weight: bold;">D</span> are at ''xpos''-7 and ''xpos''+7 rather than ''xpos''-9 and ''xpos''+9. However, his horizontal sensor lines remain the same length whether curled up or not. This means that there is 3 exposed pixels of horizontal sensor on each side while curled (compared to only 1 each side when standing), making it is possible for the horizontal sensor lines to detect a block that Sonic is falling past, even if he has no ''xsp'' at all. This causes him to slip around solids by a pixel or two when he hits them at their extreme edges.
 
  
Sensor <span style="color:#ff38ff; font-weight: bold;">E</span> is not active when Sonic's ''xsp'' is greater than his absolute ''ysp'', so basically it only deactivates if we are moving very fast right, and not too fast up or down.
+
This results in both Sonic's wall sensors activating when Sonic is moving fast vertically, which helps if he is approaching a wall to the right at a sheer angle but still moving left, or the opposite.
  
Sensor <span style="color:#ff5454; font-weight: bold;">F</span> is not active when Sonic's ''xsp'' is less than the negative of his absolute ''ysp'', so basically it only deactivates if we are moving very fast left, and not too fast up or down.
+
====Method====
  
The horizontal sensor lines are always positioned at ''ypos'' while airborne.
+
Assuming the wall's left side to be at an X position of 704 ($02C0), Sonic cannot get closer than an ''xpos'' of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), Sonic cannot get closer than an ''xpos'' of 842 ($034A). Thus both sensor lines combined should be 21 pixels wide, stretching from Sonic's ''xpos''-10 to ''xpos''+10.  
  
This results in both Sonic's wall sensors activating when Sonic is moving fast vertically, which helps if he is approaching a wall to the right at a sheer angle but still moving left, or the opposite.
+
Any time it detects a solid tile, Sonic should be "popped out", set to the edge of the tile minus (or plus) 11, and his ground speed set to 0. (He cannot be popped out by only 10, because then a point at ''xpos''+10 would still lie within the edge pixel of the tile. This would register a continuous collision, and he would stick to the wall.) This will also set his '''gsp''' to 0 if he is moving in the direction of the wall, not away from it. If it were otherwise, he would stick to walls if he tried to move away.
  
Here's an example of this:
+
Though the tile's edge minus Sonic's ''xpos'' might be 11, there are 10 free pixels between Sonic's ''xpos'' and the tile's edge. The eleventh pixel away is the tile's edge itself. This would be the same for a tile on the left. So Sonic is effectively 21 pixels wide when including ''xpos''.
 
 
<nowiki>
 
    ESensor = !(xsp > abs(ysp))
 
    FSensor = !(-xsp > abs(ysp))
 
    CDSensors = ((ysp < 0) || (abs(xsp) > abs(ysp)))
 
    ABSensors = ((ysp > 0) || (abs(xsp) > abs(ysp)))
 
</nowiki>
 
  
 +
You may remember that sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are only 19 pixels apart but Sonic is 21 pixels wide when pushing into walls. This means that Sonic is skinnier by 2 pixels when running off of ledges than when bumping into walls.
  
 
====Summary====
 
====Summary====
  
Here's a rough handmade visualisation of how sensors interact with solid tiles. You can notice how the sensor lines are pushing Sonic from the ground, and nothing much else creates the system. The <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensors lower when on flat ground. You can also notice the sensor lines snap in 90 degree rotations resulting in four modes, this will be covered further down this guide.
+
Here's a handmade visualisation of how sensors interact with solid tiles (here highlighted in bright blue, green, and cyan). You can notice how the sensor lines are pushing Sonic from the ground tiles, and is overall rather simple. The <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensors lower when on flat ground. You can also notice the sensor lines snap in 90 degree rotations resulting in four modes, this will be covered further down the guide.
  
[[Image:SPGCollisionDemo.gif|link=Special:FilePath/SPGCollisionDemo.gif]] Keep in mind, while on the ground the upper <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors would not exist, and while ''gsp'' is positive the left wall sensor would also not appear. These sensors are only included for illustration purposes.
+
[[Image:SPGCollisionDemo.gif|link=Special:FilePath/SPGCollisionDemo.gif]] ''Keep in mind, while on the ground the upper <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> sensors would not exist, and while gsp is positive the left wall sensor would also not appear. These sensors are only included for illustration purposes.''
  
 
==Slopes And Curves==
 
==Slopes And Curves==
Line 230: Line 229:
 
If the height value found is $10, then the sensor has to check for another tile above the first one found, and search for that one's height value.
 
If the height value found is $10, then the sensor has to check for another tile above the first one found, and search for that one's height value.
  
Whichever sensor finds the highest height, Sonic's ''ypos'' is set to that height minus 20 pixels. His ''ang'' is also set to the angle of the solid tile that returned the highest height.
+
Whichever sensor finds the highest height, Sonic's ''ypos'' is set to that height minus 20 pixels when standing. His ''ang'' is also set to the angle of the solid tile that returned the highest height.
  
 
When no solid tile is found by a sensor, foot level (''ypos''+20) is returned by default.
 
When no solid tile is found by a sensor, foot level (''ypos''+20) is returned by default.
Line 242: Line 241:
 
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]
 
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]
  
Sonic raises up with sensor B sensor as he moves right. When B drops off the ledge, Sonic defaults to the level of sensor A. Then he raises up with sensor A as he moves further right. So he will move up, drop down, and move up again as he runs off the ledge.
+
Sonic raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as he moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, Sonic defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then he raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as he moves further right. So he will move up, drop down, and move up again as he runs off the ledge.
  
 
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.
 
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.
Line 250: Line 249:
 
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]
 
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]
  
Sensor B starts climbing down the ramp on the right, but Sonic still defaults to the level of the previous ramp found by sensor A. Because these ramps are usually shallow, this only causes him to dip down in the middle by about 1 pixel.
+
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but Sonic still defaults to the level of the previous ramp found by sensor <span style="color:#00f000; font-weight: bold;">A</span>. Because these ramps are usually shallow, this only causes him to dip down in the middle by about 1 pixel.
  
 
But that is not all. Because the highest sensor is the one Sonic gets the angle from, even though it looks like he should be considered to be at the angle of the ramp on the right (because he is closer to it), he will still have the angle of the ramp on the left. When you jump, he will jump at that angle, moving backward, not forward like you would expect.
 
But that is not all. Because the highest sensor is the one Sonic gets the angle from, even though it looks like he should be considered to be at the angle of the ramp on the right (because he is closer to it), he will still have the angle of the ramp on the left. When you jump, he will jump at that angle, moving backward, not forward like you would expect.

Revision as of 21:06, 3 January 2020

Notes: The research applies to all four of the Sega Mega Drive games and Sonic CD.

Following only describes how Sonic collides and interacts with solid tiles. Solid objects, such as Monitors, Moving Platforms, and Blocks each have their own collision routines with Sonic and don't necessarily behave exactly the same as the tiles do.

Degree angles are counterclockwise with 0 facing right (flat floor).

SPGAngles2.png Dark blue represents ground, light blue represents air.

Different calculations may be needed if using the hex angle values.

Variables

The following variables/constants will be referenced frequently in this section.

//Variables
xpos: The X-coordinate of Sonic's center.
ypos: The Y-coordinate of Sonic's center.
xsp: The speed at which Sonic is moving horizontally.
ysp: The speed at which Sonic is moving vertically.
gsp: The speed at which Sonic is moving on the ground.
slope: The current slope factor (slp) value being used.
ang: Sonic's angle on the ground.
	
//Constants
acc: 0.046875
dec: 0.5
frc: 0.046875 (same as acc)
top: 6
jmp: 6.5 (6 for Knuckles)
slp: 0.125
slprollup: 0.078125
slprolldown: 0.3125
fall: 2.5

Introduction

What are solid tiles? While there are often solid objects in Sonic zones, the zone itself would require far too much object memory if the environment were constructed entirely of solid objects, each with their own 64 bytes of RAM. A clever short-cut is used - the zone is constructed out of tiles anyway, so all that needs be done is have each tile know whether or not it is solid.

You may know that zones are broken down into 128x128 pixel chunks (or 256x256 pixel chunks in Sonic 1 and Sonic CD), which are in turn broken into 16x16 pixels tiles, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 tiles, so those are the only ones we will be interested in throughout this guide.

Sonic's collisions and interactions with these solid tiles are what make up his basic engine. They dictate how he handles floors, walls, ceilings, slopes, and loops. Because this is such a large subject, and so complex, this guide is more proximate than other Sonic Physics Guides, but I have kept speculation to a minimum.

First we will look at Sonic's method for detecting his environment, then how this environment is constructed and how Sonic handles it's complexity with such a simple system.

Note: While solidity tiles are used in the original games, these basic collision methods and sensor layouts will still work (with adjustments) with sprite masks, or line intersections, etc, in your own engine.

Sensors

The collision with solid tiles is handled using 'sensors' that surround Sonic. You can imagine these as lines which Sonic will not allow solid tiles to overlap, along the different axis. For collision with objects (which are handled quite differently) refer to Game Objects.

SPGSensors.png An accurate approximation of the sensors

 A and B - Floor collision
 C and D - Ceiling collision (only used mid-air)
 E and F - Wall collision (shifting by 8px depending on certain factors, which will be explained)
 XY - Sonic's xpos and ypos


Since Sonic's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. Sonic has separate radius values for his E and F sensor pair (his Push Radius) which always remains the same, and for his A, B, C and D sensors there is a width radius (his Body Width Radius) and height radius (his Body Height Radius) both of which will change depending on Sonic's state.

Construction

A width in Sonic games isn't simply 2 times the width radius.

SPGHitBoxRadius.png

A radius of 8 would end up being 17 px wide rather than 16, and the sensors work in much the same way. Making his width radius of 10 become a total width of 21, not 20. This basically results in every collision mask or sensor arrangement always being an odd number in overall size. Sonic is still set 20 pixel above the ground or below a ceiling even being 39 pixels in total height as this includes his origin for every direction.

It's a matter of 1 pixel in width and height, but it could make all the difference. To avoid confusion, both measurements will be mentioned (otherwise, refer to the image examples).

Note: Previous versions of this guide have incorrectly shown and stated that Sonic is 20px wide among other related issues. The images and information in this guide are now updated to be correct as of Jan '20.

Sprite alignment

Sonic's sprite is 1 pixel offset to the left when he faces left, which can result in him appearing to be 1px inside a tile when pushing leftwards. However to a normal person playing this won't appear to happen with most objects thanks to their hitboxes sticking out 1px further on their right and bottom (due to their origins being off-centre by 1 in X and Y). So while tiles are collided with more accurately than objects, bizzarely it will appear the opposite in-game, thanks to Sonic's sprite alignment when flipped. More about object collision in Game Objects.


Floor Sensors (A and B)

SPGStandingAnimated.gif

A and B stretch down from Sonic's ypos to his feet at ypos+Body Height Radius.

Movement

Typically, Sonic's Body Width Radius is 9, placing A on Sonic's left side, at xpos-9. While B should be on his right, at xpos+9, 19 pixels wide. His Body Height Radius is usually 19, making him 39 pixels tall in total.

However while rolling or jumping, his Body Width Radius becomes 7, placing A at xpos-7. With B at xpos+7, 15 pixels wide. This is to ensure Sonic doesn't appear too far away from the floor at steep angles while in curled up. Crouching does not affect his Body Width Radius.

While rolling, jumping, or crouching, Sonic's Body Height Radius becomes smaller at a value of 14, making him 29 pixels tall. Because of this, in the step in which Sonic rolls or jumps or otherwise becomes shorter, the engine adds 5 to his ypos so that his bottom point will remain unchanged despite him getting shorter and his center changing position. 5 also has to be subtracted from ypos when he unrolls or lands from a jump. The camera system also has to keep this offset in mind, otherwise, the view will jump when Sonic changes height.

These sensors check 16 extra pixels below his feet while grounded. So if Sonic ends up slightly above the ground because of a downward slope (and he's already on the ground) this extra space checked is to keep him attached to the ground at all times, until a cliff higher then or equal to 16px turns up, or he jumps.

Activation

While grounded, the A and B sensors are always active, and are always checking for the ground beneath Sonic.

While airborne, the A and B sensors are only active when Sonic's ysp is positive (moving down) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".

ABSensors = ((ysp > 0) or (abs(xsp) > abs(ysp)))


This results in Sonic's floor sensors activating when Sonic is moving fast horizontally, which helps if he is approaching a floor at a sheer horizontal angle but still moving up.

Method

Any time they detect a solid tile Sonic should be "popped out" to the pixel above the tile's top edge minus Body Height Radius. However, there are 2 sensors and they need to detect slopes - so greater depth will be explored after establishing where these sensors are. The following applies for flat ground while not curled up.

Assuming the ground level to be at a Y position of 736 ($02E0), while standing Sonic is atop it at a ypos of 716 ($02CC), which is 20 pixels above ground level.

Ledges

Sonic has to be able to run off of ledges. It would not do to just keep walking like Wile E. Coyote, not noticing that there is nothing beneath him.

If both sensor A and B detect no solid tiles, Sonic will "fall" - a flag will be set telling the engine he is now in the air.

Balancing On Edges

One nice touch is that Sonic goes into a balancing animation when near to the edge of a ledge. This only happens when he is stopped (his ground speed is 0).

How does the engine know? It is simple - any time only one of the ground sensors is activated, Sonic must be near a ledge. If A is active and B is not the ledge is to his right and vice versa.

But if Sonic began balancing the instant one of the sensors found nothing, he would do it too "early", and it would look silly. So it only happens when only one sensor is active, and xpos is greater than the edge of the solid tile found by the active sensor.

SPGBalancingAnimated.gif

Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), Sonic will only start to balance at an xpos of 2656 ($0A60) (edge pixel+1). He'll fall off at an xpos of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.

In Sonic 2 and Sonic CD, if the ledge is the opposite direction than he is facing, he has a second balancing animation.

In Sonic 2, Sonic 3, and Sonic & Knuckles, Sonic has yet a third balancing animation, for when he's even further out on the ledge. Assuming the same values as above, this would start when he is at an xpos of 2662 ($0A66).

Note: While balancing, certain abilities are not allowed (ducking, looking up, spindashing, etc). In Sonic 3 & Knuckles, the player is still allowed to duck and spindash (not to look up, though) when balancing on the ground but not when balancing on an object.


Ceiling Sensors (C and D)

Sonic's C and D sensors are always an exact mirror image of Sonic's floor sensors, they have the same X positions and length but are flipped upside down.

They perform in the exact same way simply up instead of down.

However, they aren't active at the same times.

Activation

While grounded they are never active, and Sonic won't check for collision with solid tiles above himself while on the floor.

While airborne, Sonic's C and D sensors are only active when Sonic's ysp is negative (moving up) or Sonic's absolute "xsp" is larger than Sonic's absolute "ysp".

CDSensors = ((ysp < 0) or (abs(xsp) > abs(ysp)))


This results in Sonic's ceiling sensors activating when Sonic is moving fast horizontally, which helps if he is approaching a ceiling at a sheer horizontal angle but still moving down.

Method

Sonic will detect ceilings and be pushed out of them whether he is moving up or down, unlike floors which are only detected when moving down. It is possible to hit a "ceiling" (which is just the bottom side of a block) while moving down - by pressing toward a wall with a gap in it, or jumping toward the side of an upper curve both with a high absolute xsp.


Wall Sensors (E and F)

SPGPushingAnimated.gif

E spans from Sonic's xpos to his left at xpos-Push Radius, while F spans from Sonic's xpos to his right at xpos+Push Radius.

Movement

Push Radius is always 10, giving Sonic a total width of 21 pixels.

Sensors E and F Spend most of their time at Sonic's ypos however while Sonic's ang is 0 (on totally flat ground) both wall sensors will move to his ypos+8 so that he can push against low steps and not just snap up ontop of them.

The horizontal sensor lines are always positioned at ypos while airborne.

Activation

When grounded, Sensors E and F only activate when Sonic is walking in that direction. For example, while standing still Sonic isn't checking with his wall sensors at all, but while gsp is positive, Sonic's E sensor is inactive, and while gsp is negative, Sonic's F sensor is inactive.

While airborne

Sensor E is not active when Sonic's xsp is greater than his absolute ysp, so basically it only deactivates if we are moving very fast right, and not too fast up or down.

Sensor F is not active when Sonic's xsp is less than the negative of his absolute ysp, so basically it only deactivates if we are moving very fast left, and not too fast up or down.

ESensor = !(xsp > abs(ysp))
FSensor = !(-xsp > abs(ysp))


This results in both Sonic's wall sensors activating when Sonic is moving fast vertically, which helps if he is approaching a wall to the right at a sheer angle but still moving left, or the opposite.

Method

Assuming the wall's left side to be at an X position of 704 ($02C0), Sonic cannot get closer than an xpos of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), Sonic cannot get closer than an xpos of 842 ($034A). Thus both sensor lines combined should be 21 pixels wide, stretching from Sonic's xpos-10 to xpos+10.

Any time it detects a solid tile, Sonic should be "popped out", set to the edge of the tile minus (or plus) 11, and his ground speed set to 0. (He cannot be popped out by only 10, because then a point at xpos+10 would still lie within the edge pixel of the tile. This would register a continuous collision, and he would stick to the wall.) This will also set his gsp to 0 if he is moving in the direction of the wall, not away from it. If it were otherwise, he would stick to walls if he tried to move away.

Though the tile's edge minus Sonic's xpos might be 11, there are 10 free pixels between Sonic's xpos and the tile's edge. The eleventh pixel away is the tile's edge itself. This would be the same for a tile on the left. So Sonic is effectively 21 pixels wide when including xpos.

You may remember that sensors A and B are only 19 pixels apart but Sonic is 21 pixels wide when pushing into walls. This means that Sonic is skinnier by 2 pixels when running off of ledges than when bumping into walls.

Summary

Here's a handmade visualisation of how sensors interact with solid tiles (here highlighted in bright blue, green, and cyan). You can notice how the sensor lines are pushing Sonic from the ground tiles, and is overall rather simple. The E and F sensors lower when on flat ground. You can also notice the sensor lines snap in 90 degree rotations resulting in four modes, this will be covered further down the guide.

SPGCollisionDemo.gif Keep in mind, while on the ground the upper C and D sensors would not exist, and while gsp is positive the left wall sensor would also not appear. These sensors are only included for illustration purposes.

Slopes And Curves

Sonic the Hedgehog was one of the first games to use curved surfaces and even entire 360-degree loops. Most other games of the era composed their environments entirely of blocks (and the occasional ramp).

The ability to cope with smoothly shaped environments is one of the fundamental aspects of the Sonic games' novelty and appeal. Unfortunately, it is also perhaps the most difficult and complex aspect to recreate in a fan game.

So how does it work?

Height Masks

Any time Sensor A or B find a solid tile, they return the height of that tile.

How is the height of the tile found?

Each tile has a value associated with it that references a mask stored in memory. Each mask is simply an array of 16 height values that range from $00 to $10 and an angle value.

SPGHeightMask.PNG

This height mask, for example, has the height array $00 00 01 02 02 03 04 05 05 06 06 07 08 09 09 09, and the angle $E8.

Which value of the height array is used? Subtract the tile's X position from the sensor's X position. The result is the index of the height array to use.

If the height value found is $10, then the sensor has to check for another tile above the first one found, and search for that one's height value.

Whichever sensor finds the highest height, Sonic's ypos is set to that height minus 20 pixels when standing. His ang is also set to the angle of the solid tile that returned the highest height.

When no solid tile is found by a sensor, foot level (ypos+20) is returned by default.

Bugs Using This Method

Unfortunately, there are a couple of annoying bugs in the original engine because of this method.

If Sonic stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes Sonic to be set to the wrong position.

SPGSlopeBug1Animated.gif

Sonic raises up with sensor B sensor as he moves right. When B drops off the ledge, Sonic defaults to the level of sensor A. Then he raises up with sensor A as he moves further right. So he will move up, drop down, and move up again as he runs off the ledge.

There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.

The second form of it occurs when two opposing ramp tiles abut each other, as in some of the low hills in Green Hill Zone and Marble Zone.

SPGSlopeBug2Animated.gif

Sensor B starts climbing down the ramp on the right, but Sonic still defaults to the level of the previous ramp found by sensor A. Because these ramps are usually shallow, this only causes him to dip down in the middle by about 1 pixel.

But that is not all. Because the highest sensor is the one Sonic gets the angle from, even though it looks like he should be considered to be at the angle of the ramp on the right (because he is closer to it), he will still have the angle of the ramp on the left. When you jump, he will jump at that angle, moving backward, not forward like you would expect.

Moving At Angles

Well, that is all very well and good for having Sonic move smoothly over terrain with different heights, but that is not all there is to the engine. Sonic's speed has to be attenuated by angled ground in order to be realistic.

There are two ways in which Sonic's speed is affected on angles. The first will make sure that he does not traverse a hill in the same amount of time as walking over flat ground of an equal width. The second will slow him down when going uphill and speed him up when going downhill. Let's look at each of these in turn.

The Three Speed Variables

If Sonic were a simple platformer that required nothing but blocks, you would only need two speed variables: X speed (xsp) and Y speed (xsp), the horizontal and vertical components of Sonic's velocity. Acceleration (acc), deceleration (dec), and friction (frc) are added to xsp; jump/bounce velocity and gravity (grv) are added to ysp (when Sonic is in the air).

But when slopes are involved, while Sonic moves along a slope, he's moving both horizontally and vertically. This means that both xsp and xsp have a non-zero value. Simply adding acc, dec, or frc to xsp no longer works; imagine Sonic was trying to run up a wall - adding to his horizontal speed would be useless because he needs to move upward.

The trick is to employ a third speed variable (as the original engine does), so let's call it Ground speed (gsp). This is the speed of Sonic along the ground, disregarding ang altogether. acc, dec, and frc are applied to gsp, not xsp or ysp.

While on the ground, xsp and xsp are derived from gsp every step before Sonic is moved. Perhaps a pseudo-code example is in order:

 xsp = gsp*cos(angle);
 ysp = gsp*-sin(angle);
 
 xpos += xsp;
 ypos += ysp;

No matter what happens to the ang, gsp is preserved, so the engine always knows what speed Sonic is "really" moving at.

Slope Factor

By this point, Sonic should be able to handle any hills with an accurate velocity but he still needs to slow down when going uphill and speed up when going downhill.

Fortunately, this is simple to achieve - with something called the Slope Factor (slope). Just subtract slope*sin(ang) from gsp at the beginning of every step.

 gsp -= slope*sin(ang);

The value of slope is always slp when running, but not so when rolling. When Sonic is rolling uphill (the sign of gsp is equal to the sign of sin(ang)), slope is slprollup ($001E). When Sonic is rolling downhill (the sign of gsp is not equal to the sign of sin(ang)), slope is slprolldown ($0050).

Note: In Sonic 1, it appears that slope doesn't get added if Sonic is stopped and in his standing/waiting cycle. But in Sonic 3 & Knuckles, slope seems to be added even then, so that Sonic can't stand on steep slopes - they will force him to walk them down.

Jumping At Angles

Jumping is also affected by the angle Sonic is at when he does it. He can't simply set ysp to negative jmp - he needs to jump away from the angle he's standing on. Instead, both xsp and ysp must have jmp subtracted from them, using cos() and sin() to get the right values.

More pseudo-code:

 xsp -= jmp*sin(angle);
 ysp -= jmp*cos(angle);

Notice how the jump values are subtracted from the xsp and ysp. This means his speeds on the ground are preserved, meaning running up fast on a steep hill and jumping gives you the jump speeds and the speeds you had on the hill, resulting in a very high jump.

Switching Mode

So Sonic can run over hills and ramps and ledges, and all that is great. But it is still not enough. He cannot make his way from the ground to walls and ceilings without more work.

Why not? Well, because sensor A and B check straight downward, finding the height of the ground. There is just no way they can handle the transition to walls when everything is built for moving straight up and down on the Y-axis.

How can we solve this? By using four different modes of movement. This will take a little explaining.

The Four Modes

It seems pretty reasonable to assume that, because Sonic can traverse ground in 360 degrees, the engine handles all 360 degrees in much the same way. But, in fact, the engine splits the angles into four quadrants, greatly simplifying things.

To better understand what I am talking about, imagine a simpler platformer without full loops, just a few low hills and ramps. All the character would need to do is, after moving horizontally, move up or down until they met the level of the floor. The angle of the floor would then be measured. The angle would be used to attenuate gsp, but nothing more. The character would still always move horizontally and move straight up and down to adhere to floor level.

This is much like how Sonic does things. Only, when ang gets too steep, Sonic switches "quadrant", moving from Floor mode to Right Wall mode (to Ceiling mode, to Left Wall mode, and back around to Floor mode, etc). At any one time, in any one mode, Sonic behaves like a simpler platformer. The magic happens by combining all four modes, and cleverly switching between them smoothly.

So how and when does Sonic switch mode?

When in Floor mode, and ang is steeper than 45° ($E0), the engine switches into Right Wall mode. Everything is basically the same, only the sensors check to the right instead of downward, and Sonic is moved to "floor" level horizontally instead of vertically.

Now that he's in Right Wall mode, if ang is shallower than 45° ($E0), the engine switches back into Floor mode.

The other transitions work in exactly the same way, with the switch angles relative to the current mode.

A quick calculation can be made using ang of the floor, which will place Sonic in the correct mode:

 mode = round(angle/90) % 4;


This would return Sonic's Floor mode at 0, Right Wall at 1, Ceiling at 2, and Left Wall at 3.

You might rightly ask where the ground sensors are when in Right Wall mode. They're in exactly the same place, only rotated 90 degrees. Sensor A is now at Sonic's ypos+9 instead of xpos-9. Sensor B is now at Sonic's ypos-9, instead of xpos+9. Instead of vertical sensor lines, they are now horizontal, stretching 16 pixels beyond his foot level (which is now 20 pixels "below" him, at xpos+20).

Yes, because the sensors move so far, it is possible for Sonic to be "popped" out to a new position in the step in which he switches mode. However, this is hardly ever more than a few pixels and really isn't noticeable at all during normal play. To adjust for this in a new engine, an alternative method to switch mode would be to check for solid ground using a 90 degree rotated mask. For example, standing upright on flat ground, the left side would check rotated 90 degrees for steep slopes to switch to Left Wall Mode, and the right would check rotated -90 degrees for steep slopes to switch to Right Wall Mode. Only the lower ground sensor of the rotated mask would need to check for ground. This would have to exclude walls so Sonic doesn't begin walking on a wall when he gets near one, but would mean Sonic switched mode sooner on a slope which means less "popping".

One more thing: I said that solid tiles were made of height arrays. Operative word: height. How do they work when in Right Wall mode? Well, rather gobsmackingly, it turns out that in the original engine, each solid tile has two complementary height arrays, one used for when moving horizontally, the other for when moving vertically.

What about Left Wall and Ceiling mode? Wouldn't there need to be four height arrays? No, because tiles of those shapes simply use normal height arrays, just inverted. When in Ceiling mode, Sonic knows that the height value found should be used to move him down and not up.

With these four modes, Sonic can go over all sorts of shapes. Inner curves, outer curves, you name them. Here are some example images with their angle values to help give you some idea of what I've been talking about:

SPGInnerCurve.PNG SPGInnerCurveChart.PNG

SPGOuterCurve.PNG SPGOuterCurveChart.PNG

Falling and Sliding Off Of Walls And Ceilings

When in Right Wall, Left Wall, or Ceiling mode and Sonic's ang is between 90 and 270, Sonic will fall any time absolute gsp falls below fall ($0280) (gsp is set to 0 at this time, but xsp and ysp are unaffected, so Sonic will continue his trajectory through the air). This happens even if there is ground beneath him. If Sonic is in Right Wall, Left Wall, or Ceiling Mode but Sonic's ang is not between 90 and 270 then the horizontal control lock timer described below will still be set to 30 but Sonic will not enter a falling state remaining in his current state.

Horizontal Control Lock

When Sonic falls or slides off in the manner described above, the horizontal control lock timer is set to 30 ($1E) (it won't begin to count down until Sonic lands back on the ground). While this timer is non-zero and Sonic is on the ground, it prevents the player from adjusting Sonic's speed with the left or right buttons. The timer counts down by one every step, so the lock lasts about half a second. During this time only slp and the speed Sonic fell back on the ground with is in effect, so Sonic will slip back down the slope.

 if (abs(gsp) < 2.5 && floor_mode != 0) 
 {
   if (angle >= 90 && angle <= 270) 
   {
     floor_mode = 0;
     gsp = 0;
   }
   horizontal_lock_timer = 30;
 }

The Air State

Any time Sonic is in the air, he doesn't have to worry about angles, gsp, slp, or any of that jazz. All he has to do is move using xsp and ysp until he detects the ground, at which point he re-enters the ground state.

Jumping "Through" Floors

There are some ledges that Sonic can jump up "through". These are often in the hilly, green zones such as Green Hill Zone, Emerald Hill Zone, Palmtree Panic Zone, and so on. The solid tiles that make up these ledges are flagged by the engine as being a certain type that should only be detected by Sonic's A and B sensors. They are ignored entirely by C and D as well as the horizontal sensor line. Finally, sensor A and B (mostly) only detect the floor when Sonic is moving downwards (but always while on the ground). So with a slightly shorter jump, you will see Sonic 'pop' upwards onto a jump through surface once he begins to fall.

Reacquisition Of The Ground

Both xsp and ysp are derived from gsp while Sonic is on the ground. When he falls or otherwise leaves the ground, xsp and ysp are already the proper values for him to continue his trajectory through the air. But when Sonic lands back on the ground, gsp must be calculated from the xsp and ysp that he has when it happens. You might think that they would use cos() and sin() to get an accurate value, but that is not the case. In fact, something much more basic happens, and it is different when hitting into a curved ceiling as opposed to landing on a curved floor, so I will cover them separately.

As you land the angle of the ground you touch is read (ang). The following covers the angle (ang) of the ground (floor or ceiling) that Sonic touches as he lands, and only happens the frame when he lands when changing from in air to on ground.

When Falling Downward

SPGLandFloor.png

Shallow: When ang is in the range of 22.5°~0° ($F0~$FF) (and their mirror images, 360°~337.5° ($00~$0F)), gsp is set to the value of xsp.

Half Steep: When ang is in the range of 22.5°~45° ($E0~$EF) (and mirrored 337.5°~315° ($10~$1F)), gsp is set to xsp but only if the absolute of xsp is greater than ysp. Otherwise, gsp is set to ysp*0.5*-sign(sin(ang)).

Full Steep: When ang is in the range of 45°~90° ($C0~$DF) (and mirrored 315°~270° ($20~$3F)), gsp is set to xsp but only if the absolute of xsp is greater than ysp. Otherwise, gsp is set to ysp*-sign(sin(ang)).

When Going Upward

SPGLandCeiling.png

Slope: When the ceiling ang detected is in the range of 135°~90° ($A0~$BF) (and mirrored 270°~225° ($40~$5F)), Sonic reattaches to the ceiling and gsp is set to ysp*-sign(sin(ang)).

Note: This does not allow you to connect to a sloped ceiling and follow it downwards when you are moving horizontally in the air. One example of this is the secret route in CPZ act 2. In your engine, you may wish to use a method similar to when going downwards. If absolute xsp is greater than negative ysp, set gsp to negative xsp. Otherwise, use the calculation above.

Ceiling: When the ceiling ang is in the range of 225°~135° ($60~$9F), Sonic hits his head like with any ceiling, and doesn't reattach to it. ysp is set to 0, and xsp is unaffected.


Reference: Converting Hex Angles

The Mega Drive games use angles in hex, $00 through $FF, meaning that there are only 256 divisions of a circle, not 360 like we're used to. Worse, the direction is anti-clockwise compared to other languages like GML, so $20 isn't 45° like it should be - it's 315°.

In order to convert the original hex angles into angles you can use in GML, use this calculation (rendered here in pseudo-code):

 return (256-hex_angle)*1.40625;

Notes

  • Sonic can only brake ("screech to a halt") in Floor mode.
  • Sonic cannot jump when there is a low ceiling above him. If there is a collision detected with a sensor line stretching from Sonic's xpos-9 to xpos+9, at ypos-25, Sonic won't bother jumping at all.