https://info.sonicretro.org/api.php?action=feedcontributions&user=Lapper2&feedformat=atomSonic Retro - User contributions [en]2024-03-28T08:14:58ZUser contributionsMediaWiki 1.30.2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=324271SPG:Game Objects2021-04-12T12:40:32Z<p>Lapper2: Information on checkpoint trigger</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
===Triggers===<br />
<br />
Sometimes, but rarely, an object will forgo the hitbox system entirely and instead perform it's own custom checks on the Player's position, checking if it is within some kind of rectangle. This serves the same purpose as hitboxes, but is fundamentally different. For one, it happens on the object side while hitboxes are checked at the end of the Player's code. Secondly, a trigger can be a rectangle of any size, without using radius values. So they will be described as having a top left position, and a complete width and complete height. Just a normal rectangle. What you see is what you get with these.<br />
<br />
Because these are totally separate and do not involve the Player's hitbox at all, they will be differentiated as "Triggers" or trigger areas.<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
[[Image:SPGItemMonitorHitbox.png]]<br />
<br />
Item Monitors have a Width Radius of 15 and a Height Radius of 15, resulting in a 31 x 31 rectangle, this is their solid size you can push against. <br />
<br />
The hitbox is 1 pixel larger on every side, with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
The mechanics of breaking an item box can be found in [[SPG:Solid Objects#Item Monitor|Solid Objects]].<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
===Checkpoints===<br />
<br />
[[Image:SPGCheckpointTrigger.png]]<br />
<br />
Checkpoints appear to have a hitbox which will trigger when Sonic's touches is... but it doesn't. The checkpoint does it's own evaluation on Sonic's position to trigger a reaction. This is one of the "triggers" I mentioned at the top of this guide.<br />
<br />
The trigger top left is Checkpoint X Position - 18 and Checkpoint Y Position - 64, the trigger size is 16 x 104. <br />
<br />
Of course, the Y Position of a checkpoint is not centred on the entire thing, it's more closely centred on the pole, excluding the part at the top. This is why the trigger is larger above the Y Position than below it.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians. Because the original game uses 256 angles rather than 360, there may be slight differences with the sine function causing some logs to be a pixel off (never the logs being stood on, mainly the logs towards the sides). It's tiny and unnoticeable, but can be corrected with extra work.<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// the above can be done upon bridge creation, getting the max depression for all segments and placing them in an array for later use<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it. It takes 16 frames for bridge to return itself to its original position from full tension, resulting in a step of 5.625. <br />
As noted above, original uses 256 angles, so the actual angle range in the controller object is 0~64, with step of 4.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
The head segment has a Width Radius of 8 and a Height Radius of 7, resulting in a 17 x 15 rectangle. The hitboxes of all segments are just a little bit bigger, with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGCheckpointTrigger.png&diff=324270File:SPGCheckpointTrigger.png2021-04-12T12:38:32Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=324258SPG:Solid Objects2021-04-11T14:35:08Z<p>Lapper2: /* Standing On Solid Objects */ Note about standing on certain objects</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if they are overlapping the object.<br />
* The Player will decide which side of the object they are nearest to on both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels they are to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether they're closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* The Player will then be pushed out towards that side on that axis by the distance they overlap. <br />
<br />
Now, let's get into the details.<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
Note:<br />
*As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
*You may notice that the Player is 1px inside objects like Switches or GHZ Rocks when he stands on them, regardless of the object's Width Radius and Height Radius being correct. This is thanks to the standing on object code missing the part where it is supposed to subtract 1 extra pixel from the position the Player stands on the object (the Player is supposed stand at object Y Position - object Y Radius - player Y Radius - 1). ''Most'' objects "correct" this by actually adding 1 to the height radius used when acting solid. This corrects the issue but means the Player's initial collision with an object will be slightly different. All they had to do was add 1 line of code to fix this and never have to do anything weird with the object heights. You never have to be concerned about this if you ensure the Player will be repositioned correctly according to the object's Height Radius when standing on an object, but it may mean the way Sonic stands on things like switches will differ.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item monitor solidity differs from normal solid objects. For one, item monitors will never push the Player out downward - they will only push the Player up to land on it, or left and right. This is so that when monitors land on the Player, they won't be crushed. Secondly, rather than only allowing the Player to land if their X Position is directly over it, there are 4 pixels of extra room either side for landing on the top.<br />
<br />
Item Monitors do not always seem solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
On any given frame, after the Player moves towards the item monitor (but before the Item Monitor runs it's solid object code to push the Player back out) the Player can check for and touch the hitbox (for sizes see [[SPG:Game Objects#Item Monitors|Game Objects]]).<br />
<br />
The reaction the hitbox has upon contact is as follows:<br />
<br />
If the Player is not moving up (Y Speed >= 0) and in their roll animation, the monitor will break;<br />
Otherwise, if the Player is moving up (Y Speed < 0) and the Player's Y Position - 16 is greater than or equal to than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards and the Player's Y Speed is reversed.<br />
<br />
If the item box breaks it will no longer act solid and its hitbox will be inactive, but if it instead remains intact the item box won't explode and then can continue to act solid as if nothing happened.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=324257SPG:Game Objects2021-04-11T14:03:32Z<p>Lapper2: /* Caterkillers */ Size corrections</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
[[Image:SPGItemMonitorHitbox.png]]<br />
<br />
Item Monitors have a Width Radius of 15 and a Height Radius of 15, resulting in a 31 x 31 rectangle, this is their solid size you can push against. <br />
<br />
The hitbox is 1 pixel larger on every side, with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
The mechanics of breaking an item box can be found in [[SPG:Solid Objects#Item Monitor|Solid Objects]].<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians. Because the original game uses 256 angles rather than 360, there may be slight differences with the sine function causing some logs to be a pixel off (never the logs being stood on, mainly the logs towards the sides). It's tiny and unnoticeable, but can be corrected with extra work.<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// the above can be done upon bridge creation, getting the max depression for all segments and placing them in an array for later use<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it. It takes 16 frames for bridge to return itself to its original position from full tension, resulting in a step of 5.625. <br />
As noted above, original uses 256 angles, so the actual angle range in the controller object is 0~64, with step of 4.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
The head segment has a Width Radius of 8 and a Height Radius of 7, resulting in a 17 x 15 rectangle. The hitboxes of all segments are just a little bit bigger, with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=324256SPG:Game Objects2021-04-11T12:35:12Z<p>Lapper2: /* Item Monitors */ Corrected item monitor sizes and added image example</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
[[Image:SPGItemMonitorHitbox.png]]<br />
<br />
Item Monitors have a Width Radius of 15 and a Height Radius of 15, resulting in a 31 x 31 rectangle, this is their solid size you can push against. <br />
<br />
The hitbox is 1 pixel larger on every side, with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
The mechanics of breaking an item box can be found in [[SPG:Solid Objects#Item Monitor|Solid Objects]].<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians. Because the original game uses 256 angles rather than 360, there may be slight differences with the sine function causing some logs to be a pixel off (never the logs being stood on, mainly the logs towards the sides). It's tiny and unnoticeable, but can be corrected with extra work.<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// the above can be done upon bridge creation, getting the max depression for all segments and placing them in an array for later use<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it. It takes 16 frames for bridge to return itself to its original position from full tension, resulting in a step of 5.625. <br />
As noted above, original uses 256 angles, so the actual angle range in the controller object is 0~64, with step of 4.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
Each segment has a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle. The hitboxes are the same.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=324255SPG:Solid Objects2021-04-11T12:35:05Z<p>Lapper2: /* Item Monitor */ Corrections</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if they are overlapping the object.<br />
* The Player will decide which side of the object they are nearest to on both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels they are to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether they're closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* The Player will then be pushed out towards that side on that axis by the distance they overlap. <br />
<br />
Now, let's get into the details.<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
====Things to Note====<br />
As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item monitor solidity differs from normal solid objects. For one, item monitors will never push the Player out downward - they will only push the Player up to land on it, or left and right. This is so that when monitors land on the Player, they won't be crushed. Secondly, rather than only allowing the Player to land if their X Position is directly over it, there are 4 pixels of extra room either side for landing on the top.<br />
<br />
Item Monitors do not always seem solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
On any given frame, after the Player moves towards the item monitor (but before the Item Monitor runs it's solid object code to push the Player back out) the Player can check for and touch the hitbox (for sizes see [[SPG:Game Objects#Item Monitors|Game Objects]]).<br />
<br />
The reaction the hitbox has upon contact is as follows:<br />
<br />
If the Player is not moving up (Y Speed >= 0) and in their roll animation, the monitor will break;<br />
Otherwise, if the Player is moving up (Y Speed < 0) and the Player's Y Position - 16 is greater than or equal to than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards and the Player's Y Speed is reversed.<br />
<br />
If the item box breaks it will no longer act solid and its hitbox will be inactive, but if it instead remains intact the item box won't explode and then can continue to act solid as if nothing happened.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGItemMonitorHitbox.png&diff=324254File:SPGItemMonitorHitbox.png2021-04-11T12:32:17Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=324246SPG:Solid Objects2021-04-10T17:14:38Z<p>Lapper2: /* Item Monitor */ Solidity specifics</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if they are overlapping the object.<br />
* The Player will decide which side of the object they are nearest to on both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels they are to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether they're closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* The Player will then be pushed out towards that side on that axis by the distance they overlap. <br />
<br />
Now, let's get into the details.<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
====Things to Note====<br />
As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item monitor solidity differs from normal solid objects. For one, item monitors will never push the Player out downward - they will only push the Player up to land on it, or left and right. This is so that when monitors land on the Player, they won't be crushed. Secondly, rather than only allowing the Player to land if their X Position is directly over it, there are 4 pixels of extra room either side for landing on the top.<br />
<br />
Item Monitors do not always seem solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
The item monitor's hitbox is 2px larger than the solid box size (see [[SPG:Game Objects#Item Monitors|Game Objects]]), meaning that on any given frame, after the Player moves towards the item monitor (but before the Item Monitor runs it's solid object code to push the Player back out) the Player can touch the hitbox.<br />
<br />
The reaction the hitbox has upon contact is as follows:<br />
<br />
If the Player is not moving up (Y Speed >= 0) and in their roll animation, the monitor will break;<br />
Otherwise, if the Player is moving up (Y Speed < 0) and the Player's Y Position - 16 is greater than or equal to than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards and the Player's Y Speed is reversed.<br />
<br />
If the item box breaks it will no longer act solid and its hitbox will be inactive, but if it instead remains intact the item box won't explode and then can continue to act solid as if nothing happened.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Main_Game_Loop&diff=324245SPG:Main Game Loop2021-04-10T17:10:58Z<p>Lapper2: Hitbox info</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
==Introduction==<br />
<br />
In order to gain a full picture of how Sonic games work, as well as knowing what physically happens the order of events is just as important. <br />
<br />
==Characters==<br />
Characters are the first objects to run their code.<br />
<br />
Their main events depend on their current state.<br />
<br />
Note: ''While the main lists below are in order, the sub lists, though also numbered, are merely descriptive.''<br />
<div class="large-12 columns"><br />
<div class="large-4 columns"><br />
===While Normal===<br />
<br />
"Normal" means when Sonic is not airborne or rolling.<br />
<br />
# Check for special animations that prevent control (such as balancing).<br />
# Check for starting a spindash.<br />
# Adjust Ground Speed based on current Ground Angle ([[SPG:Slope_Physics#Slope_Factor|Slope Factor]]).<br />
# Check for starting a jump.<br />
# Update Ground Speed based on directional input and apply friction/deceleration.<br />
# Check for starting ducking, balancing on ledges, etc.<br />
# [[SPG:Solid_Tiles#Wall_Sensors_.28E_and_F.29|Wall sensor collision]] occurs.<br />
## Which sensors are used varies based on the the [[SPG:Solid_Tiles#While_Grounded|sensor activation]].<br />
## Note: ''This occurs before Sonic's position physically moves, meaning he might not actually be touching the wall yet, the game accounts for this by adding Sonic's X Speed and Y Speed to the sensor's position.<br />
# Check for starting a roll.<br />
# Handle camera boundaries (keep Sonic inside the view and kill Sonic if he touches the kill plane).<br />
# Move Sonic <br />
## Update X Position and Y Position based on X Speed and Y Speed.<br />
# [[SPG:Solid_Tiles#Floor_Sensors_.28A_and_B.29|Floor sensor collision]] occurs.<br />
## Update Sonic's Ground Angle & position.<br />
## Adhere to level of terrain or become airborne if none found/too low.<br />
# Check for falling when Ground Speed is too low on walls/ceilings.<br />
</div><br />
<div class="large-4 columns"><br />
===While Rolling===<br />
<br />
# Adjust Ground Speed based on current Ground Angle ([[SPG:Slope_Physics#Slope_Factor|Rolling Slope Factors]]).<br />
# Check for starting a jump.<br />
# Update Ground Speed based on directional input and apply [[SPG:Running#Friction|friction]].<br />
# [[SPG:Solid_Tiles#Wall_Sensors_.28E_and_F.29|Wall sensor collision]] occurs.<br />
## Which sensors are used varies based on the the [[SPG:Solid_Tiles#While_Grounded|sensor activation]].<br />
## Note: ''This occurs before Sonic's position physically moves, meaning he might not actually be touching the wall yet, the game accounts for this by adding Sonic's X Speed and Y Speed to the sensor's position. <br />
# Handle camera boundaries (keep Sonic inside the view and kill Sonic if he touches the kill plane).<br />
# Move Sonic <br />
## Update X Position and Y Position based on X Speed and Y Speed.<br />
# [[SPG:Solid_Tiles#Floor_Sensors_.28A_and_B.29|Floor sensor collision]] occurs.<br />
## Update Sonic's Ground Angle & position.<br />
## Adhere to level of terrain or become airborne if none found/too low.<br />
# Check for falling when Ground Speed is too low on walls/ceilings.<br />
</div><br />
<div class="large-4 columns"><br />
===While Airborne===<br />
<br />
"Airborne" means when Sonic is falling or jumping or otherwise not grounded.<br />
<br />
# Check for jump button release ([[SPG:Jumping#Jump_Velocity|variable jump velocity]]).<br />
# Check for turning Super.<br />
# Update X Speed based on directional input.<br />
# Apply [[SPG:Jumping#Air_Drag|air drag]].<br />
# Move Sonic <br />
## Update X Position and Y Position based on X Speed and Y Speed.<br />
# Apply [[SPG:Jumping#Gravity|gravity]].<br />
## Update Y Speed by adding ''grv'' to it.<br />
## Note: ''this happens after Sonic's position was updated. This is an important detail for ensuring Sonic's jump height is correct.''<br />
# Check underwater for reduced gravity.<br />
# Rotate angle [[SPG:Slope_Physics#Air_Rotation|back to 0]].<br />
# All collision checks occurs here.<br />
## The sensors used depend on the [[SPG:Solid_Tiles#While_Airborne|sensor activation]].<br />
## Wall collision occurs first.<br />
</div><br />
</div><br />
<br />
After moving the Player will check for nearby object hitboxes to trigger an overlap with. It it important to note this happens from Sonic's side, so before any objects run their code or act solid.<br />
<br />
After this, the special objects are executed.<br />
<br />
==Special objects==<br />
Next, objects executed are those which setup certain special events, like title cards.<br />
<br />
After this, general objects are executed.<br />
<br />
==General Objects==<br />
<br />
Object movement and collision occurs here. <br />
Player collision with objects happens here too. As stated in [[SPG:Game_Objects|Game Objects]], solid objects push the Player out rather than the Player object itself detecting them.<br />
Every object is different and execute their code in various orders.<br />
<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=324207SPG:Game Objects2021-04-07T21:40:11Z<p>Lapper2: /* Item Monitors */ Correcting item box information</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
Item Monitors have a Width Radius of 16 and a Height Radius of 16, resulting in a 33 x 33 rectangle, this is their solid size you can push against. <br />
<br />
The hitbox is 2 pixels larger on every side, with a width radius of 18 and a height radius of 18, resulting in a 35 x 35 rectangle.<br />
<br />
The mechanics of breaking an item box can be found in [[SPG:Solid Objects#Item Monitor|Solid Objects]].<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians. Because the original game uses 256 angles rather than 360, there may be slight differences with the sine function causing some logs to be a pixel off (never the logs being stood on, mainly the logs towards the sides). It's tiny and unnoticeable, but can be corrected with extra work.<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// the above can be done upon bridge creation, getting the max depression for all segments and placing them in an array for later use<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it. It takes 16 frames for bridge to return itself to its original position from full tension, resulting in a step of 5.625. <br />
As noted above, original uses 256 angles, so the actual angle range in the controller object is 0~64, with step of 4.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
Each segment has a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle. The hitboxes are the same.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=324206SPG:Solid Objects2021-04-07T21:40:05Z<p>Lapper2: /* Item Monitor */ Correcting item box information</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if they are overlapping the object.<br />
* The Player will decide which side of the object they are nearest to on both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels they are to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether they're closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* The Player will then be pushed out towards that side on that axis by the distance they overlap. <br />
<br />
Now, let's get into the details.<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
====Things to Note====<br />
As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item Monitors do not always seem solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
The item monitor's hitbox is 2px larger than the solid box size (see [[SPG:Game Objects#Item Monitors|Game Objects]]), meaning that on any given frame, after the Player moves towards the item monitor (but before the Item Monitor runs it's solid object code to push the Player back out) the Player can touch the hitbox.<br />
<br />
The reaction the hitbox has upon contact is as follows:<br />
<br />
If the Player is not moving up (Y Speed >= 0) and in their roll animation, the monitor will break;<br />
Otherwise, if the Player is moving up (Y Speed < 0) and the Player's Y Position - 16 is greater than or equal to than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards and the Player's Y Speed is reversed.<br />
<br />
If the item box breaks it will no longer act solid and its hitbox will be inactive, but if it instead remains intact the item box won't explode and then can continue to act solid as if nothing happened.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=324026SPG:Solid Objects2021-04-06T16:16:01Z<p>Lapper2: /* General Solid Object Collision */</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if they are overlapping the object.<br />
* The Player will decide which side of the object they are nearest to on both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels they are to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether they're closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* The Player will then be pushed out towards that side on that axis by the distance they overlap. <br />
<br />
Now, let's get into the details.<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
====Things to Note====<br />
As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item Monitors, as you may have noticed, are not always solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
While not curled up in a ball, the Item Monitor acts as solid. The Item Monitor's hitbox isn't accessible at this time.<br />
<br />
While curled, however, the item box will no longer have any solidity at all, and its hitbox is active and accessible. The hitbox collision is what destroys the Item Box and bounces the Player off (Details in [[SPG:Rebound#Monitors|Monitors Rebound]]).<br />
<br />
However, there is an exception. If the Player is moving up (Y Speed < 0) while curled, the Item Box will in fact still act solid. Additionally, if the Player's Y Position-16 is smaller than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards, and the Player's Y Speed will be reversed.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323993SPG:Characters2021-04-06T14:17:02Z<p>Lapper2: /* Insta-Shield */ Fixing hierarchy</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
The sizes of characters are important, they determine how the characters will collide with Solid Tiles, Solid Objects and more. <br />
<br />
These are not hitboxes, hitboxes are separate and aren't related to the solid size of objects. Hitboxes will be covered in [[SPG:Solid Objects|Solid Objects]]<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
[[Image:SPGSonicSizes.gif|link=Special:FilePath/SPGSonicSizes.gif]]<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force (''jmp'') is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
===Insta-Shield===<br />
<br />
The Insta-Shield expands Sonic's hitbox giving it a width radius of 24 and a height radius of 24, resulting in an overall height of 49 x 49 (more about [[SPG:Game_Objects#Hitboxes|Hit Boxes]]). This lasts for 13 frames.<br />
<br />
The Insta-Shield does nothing to Sonic's X Speed or Y Speed. It does however allow him to control a rolling jump.<br />
<br />
==Tails==<br />
[[Image:SPGTailsSizes.gif|link=Special:FilePath/SPGTailsSizes.gif]]<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force (''jmp'') is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
[[Image:SPGKnucklesSizes.gif|link=Special:FilePath/SPGKnucklesSizes.gif]]<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
<br />
Well that is except for when he is gliding, climbing and sliding. <br />
<br />
[[Image:SPGKnucklesMoveSizes.gif|link=Special:FilePath/SPGKnucklesMoveSizes.gif]]<br />
<br />
Here, his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force (''jmp'') is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X Speed is set to '''4''' in the opposite direction of the wall, and his Y Speed is set to '''-4'''.<br />
<br />
Interestingly, because of a pixel offset when sprites are flipped, Knuckles' feet poke 1px out from the side of his size when he is on a wall to the left. This means his feet should be inside the wall a bit. Well, when on a left wall there is actually a 1px gap between knuckles X - Push Radius and the wall, purely to make it look correct.<br />
<br />
====Falling====<br />
When climbing, Knuckles will fall off the bottom of a wall if no wall is found at his Y Position + Height Radius (checking horizontally into the wall).<br />
<br />
====Clambering====<br />
Knuckles will clamber atop a ledge if it no wall is found at his Y position - Height Radius (checking horizontally into the wall). <br />
<br />
[[File:SPGKnucklesClamber.gif|link=Special:FilePath/File:SPGKnucklesClamber.gif]]<br />
<br />
When clambering, Knuckles plays a 3 sub-image animation. Each sub-image of the animation lasts 6 frames, and after the 3rd sub-image knuckles is standing on the ledge, where his X Position is the ledge X. Each frame of the animation moves knuckles to a new position as shown.<br />
<br />
His position moves back and forth a bit as he climbs so that his sprite aligns. If your camera is set up correctly, usually the first 2 sub-images of motion will push the camera forward into place and the 3rd sub-image's backward motion won't move the camera at all, which makes it look smooth enough.<br />
<br />
====Stopping at Floors and Ceilings====<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius).<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323992SPG:Solid Tiles2021-04-06T14:16:02Z<p>Lapper2: /* Sensor Regression & Extension */ Added line to help reading</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how the Player object collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for the Player and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of the Player's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
the Player's collisions and interactions with these solid tiles are what make up their basic engine. They dictate how they handles floors, walls, ceilings, slopes, and loops. <br />
<br />
First we will look at how the environment is constructed from tiles, and then the Player's method for detecting their environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of the Player's sensors.''<br />
<br />
In this example, the sensor to the Player's mid right points right, and those at the Player's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking neighbouring tiles if certain conditions are met. <br />
<br />
<br />
Note:<br />
*The following is an example in the case of a sensor which is pointing down looking for solids below (like a floor sensor while standing upright).<br />
*The current height array value of a tile at the sensor's X position will be referred to as the ''tile height''.<br />
<br />
<br />
'''Normal:'''<br />
<br />
When the '''anchor point''' finds a Solid Tile and the ''tile height'' of the first tile is between 1 and 15 (inclusive), the game can be sure the surface of the terrain has been found without needing to check extra tiles at all. <br />
<br />
Otherwise, one of the following 2 things will happen.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
When the '''anchor point''' finds a Solid Tile and the ''tile height'' of the first tile is 16 (meaning the tile is completely filled in that position), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
<br />
If a regression occurs and ''tile height'' of the second tile is 0 (or the tile is empty), it will just default to processing the first tile, as the first tile ''must'' be the terrain surface.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
When the '''anchor point''' just finds an empty tile (or the ''tile height'' of the first tile is 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
<br />
If an extension occurs and just finds an empty second tile (or the ''tile height'' of the second tile is 0), the game knows that no terrain or terrain surface has been found, and will return a distance of 0.<br />
</div><br />
</div><br />
<hr><br />
If a solid tile was found to be processed, it will calculate the distance between that ''tile height'' and the sensor.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another).<br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including the Player, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for the Player.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, the Player will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, the Player will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over the Player while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==The Player's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround the Player. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - the Player's X Position and Y Position<br />
<br />
<br />
Since the Player's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. The Player has separate radius values for their <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (their '''Push Radius''') which always remains the same, and for their <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 and Height Radius both of which will change depending on the Player's state. For these sizes see [[SPG:Characters|Characters]].<br />
<br />
Note on sprite alignment:<br />
* The Player's sprite is 1 pixel offset to the left when they faces left, which can result in them appearing to be 1px inside a tile when pushing leftwards. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at their feet at Y Position + Height Radius.<br />
<br />
====Movement====<br />
These sensors are <span style="color:#00f000; font-weight: bold;">A</span> on the Player's left side, at X Position - Width Radius, Y Position + Y Radius. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on their right, at X Position + Width Radius, Y Position + Height Radius.<br />
<br />
These radius values change depending on the character and action (see [[SPG:Characters|Characters]]).<br />
<br />
====Method====<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition the Player. The result is the Player will stand atop the floor at the floor's surface Y level - (the Player's Height Radius + 1)<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If the Player's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want them to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, the Player won't collide.<br />
In Sonic 2 onward however the positive limit depends on the Player's current speed - in this case, (for when the Player is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then they won't collide. So the faster the Player moves, the greater the distance the Player can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If the Player was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) the Player won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
The Player 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 them.<br />
<br />
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, the Player will "fall" - a flag will be set telling the engine they is now in the air.<br />
<br />
=====Balancing On Edges=====<br />
<br />
One nice touch is that the Player goes into a balancing animation when near to the edge of a ledge. This only happens when they is stopped (their Ground Speed is 0).<br />
<br />
How does the engine know? It is simple - any time only one of the ground sensors is activated, the Player must be near a ledge. If <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to their right and vice versa.<br />
<br />
But if the Player began balancing the instant one of the sensors found nothing, they would do it too "early", and it would look silly. So it only happens when only one sensor is active, and X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), the Player will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). they'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than they is facing, they has a second balancing animation.<br />
<br />
In ''[[Sonic 2]]'', ''[[Sonic 3]]'', and ''[[Sonic & Knuckles]]'', the Player has yet a ''third'' balancing animation, for when they's even further out on the ledge. Assuming the same values as above, this would start when they is at an X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, etc). In the Player 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
the Player'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 the Player's floor sensors, they have the same X positions but are flipped upside down and face upwards. They perform in the exact same way, competing against eachother, simply up instead of down.<br />
<br />
However, they aren't active at the same times as the floor sensors, only while airborne.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from the Player's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at their left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at their right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to the Player's left side, at X Position - 10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to their right, at X Position + 10, giving the Player a total width of 21 pixels when pushing.<br />
<br />
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 the Player's Y Position however while the Player's Ground Angle is 0 (on totally flat ground) both wall sensors will move to their Y Position + 8 so that they can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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 the Player is 21 pixels wide when pushing into walls. This means that the Player is skinnier by 2 pixels when running off of ledges than when bumping into walls.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before the Player's position physically moves anywhere, so they wont actually be in a wall when they tries to collide with it. The game accounts for this by actually adding their X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if the Player had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), the Player cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), the Player cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from the Player's X Position-10 to X Position+10. <br />
<br />
When the Player collides with a wall, this will set their Ground Speed to 0 if they is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop the Player at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because they will not collide. If the sensor's distance is negative, this means that when the Player's position actually does change, they will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of the Player (where they will be after they moves) instead of using the distance to reposition the Player by directly changing their position, the game smartly uses the fact that the Player has still yet to move within the current frame. All it has to do is add the distance to the Player's X Speed (if moving right, or ''subtract'' the distance from the Player's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in the Player moving when their position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 the Player will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, they will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
the Player cannot jump when there is a low ceiling above them. If there is a collision detected with sensors at the Player's X Position-9 and X Position+9, at Y Position-25, the Player won't bother jumping at all.<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will actively search for new floor below the Player's feet. <br />
<br />
When grounded, Wall Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when the Player is walking in that direction. For example, while standing still the Player isn't checking with their wall sensors at all, but while Ground Speed is positive, the Player's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when the Player's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when the Player's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and the Player won't check for collision with solid tiles above themself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach the Player to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
the Player is going mostly right<br />
else<br />
the Player is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
the Player is going mostly down<br />
else<br />
the Player is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors are pushing the Player 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
If the Player stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes the Player to be set to the wrong position.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
The Player raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as they moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, the Player defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then they raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as they moves further right. So they will move up, drop down, and move up again as they runs off the ledge.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (the Player the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but the Player 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 them to dip down in the middle by about 1 pixel.<br />
<br />
But that is not all. Because the highest sensor is the one the Player gets the angle from, even though it looks like they should be considered to be at the angle of the ramp on the right (because they is closer to it), they will still have the angle of the ramp on the left. When you jump, they will jump at that angle, moving backward, not forward like you would expect.<br />
<br />
==Notes==<br />
* Find information on how the Player's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323991SPG:Solid Tiles2021-04-06T14:14:24Z<p>Lapper2: /* Sensor Regression & Extension */ Much more understandable regression and extension</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how the Player object collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for the Player and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of the Player's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
the Player's collisions and interactions with these solid tiles are what make up their basic engine. They dictate how they handles floors, walls, ceilings, slopes, and loops. <br />
<br />
First we will look at how the environment is constructed from tiles, and then the Player's method for detecting their environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of the Player's sensors.''<br />
<br />
In this example, the sensor to the Player's mid right points right, and those at the Player's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking neighbouring tiles if certain conditions are met. <br />
<br />
<br />
Note:<br />
*The following is an example in the case of a sensor which is pointing down looking for solids below (like a floor sensor while standing upright).<br />
*The current height array value of a tile at the sensor's X position will be referred to as the ''tile height''.<br />
<br />
<br />
'''Normal:'''<br />
<br />
When the '''anchor point''' finds a Solid Tile and the ''tile height'' of the first tile is between 1 and 15 (inclusive), the game can be sure the surface of the terrain has been found without needing to check extra tiles at all. <br />
<br />
Otherwise, one of the following 2 things will happen.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
When the '''anchor point''' finds a Solid Tile and the ''tile height'' of the first tile is 16 (meaning the tile is completely filled in that position), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
<br />
If a regression occurs and ''tile height'' of the second tile is 0 (or the tile is empty), it will just default to processing the first tile, as the first tile ''must'' be the terrain surface.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
When the '''anchor point''' just finds an empty tile (or the ''tile height'' of the first tile is 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
<br />
If an extension occurs and just finds an empty second tile (or the ''tile height'' of the second tile is 0), the game knows that no terrain or terrain surface has been found, and will return a distance of 0.<br />
</div><br />
</div><br />
<br />
If a solid tile was found to be processed, it will calculate the distance between that ''tile height'' and the sensor.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another).<br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including the Player, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for the Player.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, the Player will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, the Player will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over the Player while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==The Player's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround the Player. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - the Player's X Position and Y Position<br />
<br />
<br />
Since the Player's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. The Player has separate radius values for their <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (their '''Push Radius''') which always remains the same, and for their <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 and Height Radius both of which will change depending on the Player's state. For these sizes see [[SPG:Characters|Characters]].<br />
<br />
Note on sprite alignment:<br />
* The Player's sprite is 1 pixel offset to the left when they faces left, which can result in them appearing to be 1px inside a tile when pushing leftwards. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at their feet at Y Position + Height Radius.<br />
<br />
====Movement====<br />
These sensors are <span style="color:#00f000; font-weight: bold;">A</span> on the Player's left side, at X Position - Width Radius, Y Position + Y Radius. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on their right, at X Position + Width Radius, Y Position + Height Radius.<br />
<br />
These radius values change depending on the character and action (see [[SPG:Characters|Characters]]).<br />
<br />
====Method====<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition the Player. The result is the Player will stand atop the floor at the floor's surface Y level - (the Player's Height Radius + 1)<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If the Player's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want them to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, the Player won't collide.<br />
In Sonic 2 onward however the positive limit depends on the Player's current speed - in this case, (for when the Player is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then they won't collide. So the faster the Player moves, the greater the distance the Player can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If the Player was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) the Player won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
The Player 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 them.<br />
<br />
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, the Player will "fall" - a flag will be set telling the engine they is now in the air.<br />
<br />
=====Balancing On Edges=====<br />
<br />
One nice touch is that the Player goes into a balancing animation when near to the edge of a ledge. This only happens when they is stopped (their Ground Speed is 0).<br />
<br />
How does the engine know? It is simple - any time only one of the ground sensors is activated, the Player must be near a ledge. If <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to their right and vice versa.<br />
<br />
But if the Player began balancing the instant one of the sensors found nothing, they would do it too "early", and it would look silly. So it only happens when only one sensor is active, and X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), the Player will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). they'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than they is facing, they has a second balancing animation.<br />
<br />
In ''[[Sonic 2]]'', ''[[Sonic 3]]'', and ''[[Sonic & Knuckles]]'', the Player has yet a ''third'' balancing animation, for when they's even further out on the ledge. Assuming the same values as above, this would start when they is at an X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, etc). In the Player 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
the Player'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 the Player's floor sensors, they have the same X positions but are flipped upside down and face upwards. They perform in the exact same way, competing against eachother, simply up instead of down.<br />
<br />
However, they aren't active at the same times as the floor sensors, only while airborne.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from the Player's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at their left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at their right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to the Player's left side, at X Position - 10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to their right, at X Position + 10, giving the Player a total width of 21 pixels when pushing.<br />
<br />
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 the Player's Y Position however while the Player's Ground Angle is 0 (on totally flat ground) both wall sensors will move to their Y Position + 8 so that they can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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 the Player is 21 pixels wide when pushing into walls. This means that the Player is skinnier by 2 pixels when running off of ledges than when bumping into walls.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before the Player's position physically moves anywhere, so they wont actually be in a wall when they tries to collide with it. The game accounts for this by actually adding their X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if the Player had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), the Player cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), the Player cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from the Player's X Position-10 to X Position+10. <br />
<br />
When the Player collides with a wall, this will set their Ground Speed to 0 if they is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop the Player at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because they will not collide. If the sensor's distance is negative, this means that when the Player's position actually does change, they will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of the Player (where they will be after they moves) instead of using the distance to reposition the Player by directly changing their position, the game smartly uses the fact that the Player has still yet to move within the current frame. All it has to do is add the distance to the Player's X Speed (if moving right, or ''subtract'' the distance from the Player's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in the Player moving when their position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 the Player will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, they will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
the Player cannot jump when there is a low ceiling above them. If there is a collision detected with sensors at the Player's X Position-9 and X Position+9, at Y Position-25, the Player won't bother jumping at all.<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will actively search for new floor below the Player's feet. <br />
<br />
When grounded, Wall Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when the Player is walking in that direction. For example, while standing still the Player isn't checking with their wall sensors at all, but while Ground Speed is positive, the Player's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when the Player's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when the Player's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and the Player won't check for collision with solid tiles above themself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach the Player to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
the Player is going mostly right<br />
else<br />
the Player is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
the Player is going mostly down<br />
else<br />
the Player is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors are pushing the Player 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
If the Player stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes the Player to be set to the wrong position.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
The Player raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as they moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, the Player defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then they raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as they moves further right. So they will move up, drop down, and move up again as they runs off the ledge.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (the Player the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but the Player 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 them to dip down in the middle by about 1 pixel.<br />
<br />
But that is not all. Because the highest sensor is the one the Player gets the angle from, even though it looks like they should be considered to be at the angle of the ramp on the right (because they is closer to it), they will still have the angle of the ramp on the left. When you jump, they will jump at that angle, moving backward, not forward like you would expect.<br />
<br />
==Notes==<br />
* Find information on how the Player's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Basics&diff=323947SPG:Basics2021-04-04T18:50:11Z<p>Lapper2: /* Positions */ Clarification regarding small values</p>
<hr />
<div>''Notes: Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.''<br />
<br />
==Introduction==<br />
The aim of this guide is to accurately describe the mechanics of classic Sonic games, while explaining the concepts well enough to allow for different ways to implement the same ideas. All that is described can be implemented in many different ways with the same or very similar results, and nothing needs to be done the exact way the Genesis hardware did.<br />
<br />
Before we dive into how the game works, it is first important to know some basic attributes about how the games are structured which will apply to multiple aspects throughout this guide. This is relevant mostly for those looking for extremely close accuracy, and to provide context for some of the game's more subtle behaviour involving collision or motion.<br />
<br />
==Positions==<br />
Game engines these days can effortlessly use real decimal values to move objects around and perform precise calculations. In the original Genesis games, they work a bit differently.<br />
<br />
===Pixel and Subpixel===<br />
Positions and Speeds in the Genesis games are in 2 parts, these being the '''pixel''' and the '''subpixel'''. The pixel is quite plainly what you would expect, it's what you would see on screen. Think of each pixel as a cell in a grid and this is which cell to look at. The subpixel, however, acts as the fractional part of the position.<br />
<br />
Because decimal numbers aren't used, this subpixel actually a positive number up to 256. <br />
<br />
Each pixel is effectively split into 256 slices along both axis and this means the finest fidelity available for movement is 1/256th of a pixel (aka 0.00390625). Think of this as where in that grid cell the real position currently is.<br />
<br />
All decimal values touted in this guide can be translated into a subpixel amount simply by multiplying them by 256. Sonic's acceleration (''acc'') for example, is equal to 12 sub pixels. His gravity (''grv'') is 56 subpixels. <br />
<br />
If, for example, Sonic's X pixel position is 50, and his X subpixel position is 48, the real/decimal X position is 50 + (subpixel / 256) which results in a final ''xpos'' of 50.1875. Even when the pixel portion is negative, the subpixel is always relative to the left within that pixel, and is added to the pixel position. So you can easily think of the pixel value to be the object's position, but floored (rounded down). This applies to all positions for any object.<br />
<br />
Lastly, this exact same principle is applied to speeds, like an X speed or an acceleration, where they all have a pixel and/or subpixel component. If the value, like acceleration or gravity for example, is less than 1 pixel per frame then it does not need to have a pixel component, of course, and is just the subpixel amount as a single variable.<br />
<br />
===Using Decimals===<br />
Decimal values can be used just fine to emulate this in a modern game engine, but keeping this pixel/subpixel mechanic in mind is important for absolute accuracy.<br />
<br />
==Angles==<br />
<br />
Degree angles are counter-clockwise with 0 facing right (flat floor).<br />
<br />
[[Image:SPGAngles2.png|link=Special:FilePath/SPGAngles2.png]]<br />
Dark blue represents ground, light blue represents air.<br />
<br />
===Hex Angles===<br />
The Mega Drive games use angles in hex, 0 ($00) through 255 ($FF), meaning that there are only 256 divisions of a circle, not 360 like we're used to. <br />
<br />
Note: ''In this case, 256 would be the same angle as 0, much like an angle of 360 is the same as 0.''<br />
<br />
Throughout the guides, angles will be presented in 3 forms. Converted degrees, Inverted Decimal (the real hex values, converted to decimal, and reversed to be anti clockwise for comparison with the degrees), and the original hex value. Degrees are fine to use and are far more intuitive, however if you want pinpoint accuracy to the originals, you would need to create and use your own angle system using 256 clockwise angles.<br />
<br />
====Reference: Converting Hex Angles====<br />
Of course, if for some reason you only have access to the Hex angle, the 256 slices will not help you if you desire using degrees. Worse, the direction is anti-clockwise compared to other languages like GML, so $20 isn't 45° like it should be - it's 315°.<br />
In order to convert the original hex angles into angles you can use in GML, use this calculation:<br />
<br />
real_angle = (256-hex_angle)*1.40625<br />
<br />
This reverses the hex angle, then multiplies it to fit within the 360 range.<br />
<br />
==Objects==<br />
Objects are the building blocks of each Sonic game (ignoring the Terrain Tiles). Sonic is an object, items are objects, and so on. <br />
<br />
===Variables===<br />
<br />
The following variables/constants will be referenced frequently in this guide.<br />
<br />
<nowiki>//General Object Variables/Attributes<br />
X Position: The X-coordinate of the object's centre.<br />
Y Position: The Y-coordinate of the object's centre.<br />
X Speed: The speed at which the object is moving horizontally.<br />
Y Speed: The speed at which the object is moving vertically.<br />
Ground Speed: The speed at which the object is moving on the ground.<br />
Ground Angle: The object's angle on the ground.<br />
Width Radius: The object's width from the origin pixel left and right.<br />
Height Radius: The object's height from the origin pixel up and down.<br />
<br />
//Sonic's Variables<br />
Push Radius: Sonic's width from the origin pixel left and right (for pushing).<br />
Slope Factor: The current slope factor value being used.<br />
<br />
//Sonic's Speed constants<br />
acc: 0.046875 ;acceleration<br />
dec: 0.5 ;deceleration<br />
frc: 0.046875 ;friction (same as acc)<br />
top: 6 ;top horizontal speed<br />
slp: 0.125 ;slope factor when walking/running<br />
slprollup: 0.078125 ;slope factor when rolling uphill<br />
slprolldown: 0.3125 ;slope factor when rolling downhill<br />
fall: 2.5 ;tolerance ground speed for sticking to walls and ceilings<br />
<br />
//Sonic's Airborne Speed Constants<br />
air: 0.09375 ;air acceleration (2x acc)<br />
jmp: 6.5 ;jump force (6 for knuckles)<br />
grv: 0.21875 ;gravity<br />
</nowiki><br />
<br />
===Set up===<br />
Each game object follows the same or similar conventions. They all have X and Y positions, X and Y speeds, a Width Radius, a Height Radius, animation variables, an angle, and more.<br />
<br />
For the most part, the width and Height Radius of an object describes it's solid size, so it's the box size if you can push against it or how big it is when touching solid tiles if it can move around. Or both, as the case may be.<br />
<br />
====Sizes====<br />
Objects use 2 values to define their size for collision. A Width Radius, and a Height Radius. These are centred outward on the object's origin, forming a box around it. <br />
In a game with small sprites and pixel art, a pixel difference with worth acknowledging. A size in Sonic games ''isn't'' simply 2 times the radius.<br />
<br />
[[Image:SPGHitBoxRadius.png]]<br />
<br />
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 pushing Width Radius of 10 become a total width of 21, '''not''' 20. This applies for all heights as well as all widths. This basically results in every collision mask, box, or sensor arrangement always being an odd number in overall size. This means they also won't perfectly match up with the evenly sized pixel art.<br />
<br />
It's a matter of 1 pixel in width and height, but it could make all the difference in accuracy. To avoid confusion, both measurements will be mentioned (otherwise, refer to image examples).<br />
<br />
====Hitboxes====<br />
Hitbox sizes are not the same thing as the object's Width Radius and Height Radius, though they do follow the same format. They are defined separately and can differ greatly from the normal object size. For example, a ring's Width/Height Radius would be 8, but for it's hitbox it is 6.<br />
<br />
====Characters====<br />
Characters follow the same format as any other object for the most part. But there are some specific character traits that can help you get started.<br />
<br />
Sonic's Width Radius is usually 9, and his Height Radius is usually 19. These radiuses determine where Sonic's floor and ceiling tile sensors will be. These can change depending on Sonic's action, and how they behave will be described in Solid Tiles, as they are most relevant to collision specifically.<br />
Sonic also has another radius that he uses for pushing against objects and tiles. We will call this his '''Push Radius''' which is always 10. This radius is implied via being used for the sensor positions and during object collision rather than actually defined as a variable in-game, though it simply makes more sense here to describe it as a constant.<br />
<br />
===Colliding===<br />
The main thing to keep in mind with any object/tile collision is that it typically only concerns itself with any object's ''whole pixel'' position, totally ignoring any subpixel amount. So when you perform any collision calculations, you can emulate this by just comparing/testing/using a floored position instead of the real position.<br />
<br />
[[Category:Sonic Physics Guide|Basics]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Basics&diff=323946SPG:Basics2021-04-04T18:48:26Z<p>Lapper2: /* Variables */</p>
<hr />
<div>''Notes: Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.''<br />
<br />
==Introduction==<br />
The aim of this guide is to accurately describe the mechanics of classic Sonic games, while explaining the concepts well enough to allow for different ways to implement the same ideas. All that is described can be implemented in many different ways with the same or very similar results, and nothing needs to be done the exact way the Genesis hardware did.<br />
<br />
Before we dive into how the game works, it is first important to know some basic attributes about how the games are structured which will apply to multiple aspects throughout this guide. This is relevant mostly for those looking for extremely close accuracy, and to provide context for some of the game's more subtle behaviour involving collision or motion.<br />
<br />
==Positions==<br />
Game engines these days can effortlessly use real decimal values to move objects around and perform precise calculations. In the original Genesis games, they work a bit differently.<br />
<br />
===Pixel and Subpixel===<br />
Positions and Speeds in the Genesis games are in 2 parts, these being the '''pixel''' and the '''subpixel'''. The pixel is quite plainly what you would expect, it's what you would see on screen. Think of each pixel as a cell in a grid and this is which cell to look at. The subpixel, however, acts as the fractional part of the position.<br />
<br />
Because decimal numbers aren't used, this subpixel actually a positive number up to 256. <br />
<br />
Each pixel is effectively split into 256 slices along both axis and this means the finest fidelity available for movement is 1/256th of a pixel (aka 0.00390625). Think of this as where in that grid cell the real position currently is.<br />
<br />
All decimal values touted in this guide can be translated into a subpixel amount simply by multiplying them by 256. Sonic's acceleration (''acc'') for example, is equal to 12 sub pixels. His gravity (''grv'') is 56 subpixels. <br />
<br />
If, for example, Sonic's X pixel position is 50, and his X subpixel position is 48, the real/decimal X position is 50 + (subpixel / 256) which results in a final ''xpos'' of 50.1875. Even when the pixel portion is negative, the subpixel is always relative to the left within that pixel, and is added to the pixel position. So you can easily think of the pixel value to be the object's position, but floored (rounded down). This applies to all positions for any object.<br />
<br />
Lastly, this exact same principle is applied to speeds, like an X speed or an acceleration, where they all have a pixel and subpixel component.<br />
<br />
===Using Decimals===<br />
Decimal values can be used just fine to emulate this in a modern game engine, but keeping this pixel/subpixel mechanic in mind is important for absolute accuracy.<br />
<br />
==Angles==<br />
<br />
Degree angles are counter-clockwise with 0 facing right (flat floor).<br />
<br />
[[Image:SPGAngles2.png|link=Special:FilePath/SPGAngles2.png]]<br />
Dark blue represents ground, light blue represents air.<br />
<br />
===Hex Angles===<br />
The Mega Drive games use angles in hex, 0 ($00) through 255 ($FF), meaning that there are only 256 divisions of a circle, not 360 like we're used to. <br />
<br />
Note: ''In this case, 256 would be the same angle as 0, much like an angle of 360 is the same as 0.''<br />
<br />
Throughout the guides, angles will be presented in 3 forms. Converted degrees, Inverted Decimal (the real hex values, converted to decimal, and reversed to be anti clockwise for comparison with the degrees), and the original hex value. Degrees are fine to use and are far more intuitive, however if you want pinpoint accuracy to the originals, you would need to create and use your own angle system using 256 clockwise angles.<br />
<br />
====Reference: Converting Hex Angles====<br />
Of course, if for some reason you only have access to the Hex angle, the 256 slices will not help you if you desire using degrees. Worse, the direction is anti-clockwise compared to other languages like GML, so $20 isn't 45° like it should be - it's 315°.<br />
In order to convert the original hex angles into angles you can use in GML, use this calculation:<br />
<br />
real_angle = (256-hex_angle)*1.40625<br />
<br />
This reverses the hex angle, then multiplies it to fit within the 360 range.<br />
<br />
==Objects==<br />
Objects are the building blocks of each Sonic game (ignoring the Terrain Tiles). Sonic is an object, items are objects, and so on. <br />
<br />
===Variables===<br />
<br />
The following variables/constants will be referenced frequently in this guide.<br />
<br />
<nowiki>//General Object Variables/Attributes<br />
X Position: The X-coordinate of the object's centre.<br />
Y Position: The Y-coordinate of the object's centre.<br />
X Speed: The speed at which the object is moving horizontally.<br />
Y Speed: The speed at which the object is moving vertically.<br />
Ground Speed: The speed at which the object is moving on the ground.<br />
Ground Angle: The object's angle on the ground.<br />
Width Radius: The object's width from the origin pixel left and right.<br />
Height Radius: The object's height from the origin pixel up and down.<br />
<br />
//Sonic's Variables<br />
Push Radius: Sonic's width from the origin pixel left and right (for pushing).<br />
Slope Factor: The current slope factor value being used.<br />
<br />
//Sonic's Speed constants<br />
acc: 0.046875 ;acceleration<br />
dec: 0.5 ;deceleration<br />
frc: 0.046875 ;friction (same as acc)<br />
top: 6 ;top horizontal speed<br />
slp: 0.125 ;slope factor when walking/running<br />
slprollup: 0.078125 ;slope factor when rolling uphill<br />
slprolldown: 0.3125 ;slope factor when rolling downhill<br />
fall: 2.5 ;tolerance ground speed for sticking to walls and ceilings<br />
<br />
//Sonic's Airborne Speed Constants<br />
air: 0.09375 ;air acceleration (2x acc)<br />
jmp: 6.5 ;jump force (6 for knuckles)<br />
grv: 0.21875 ;gravity<br />
</nowiki><br />
<br />
===Set up===<br />
Each game object follows the same or similar conventions. They all have X and Y positions, X and Y speeds, a Width Radius, a Height Radius, animation variables, an angle, and more.<br />
<br />
For the most part, the width and Height Radius of an object describes it's solid size, so it's the box size if you can push against it or how big it is when touching solid tiles if it can move around. Or both, as the case may be.<br />
<br />
====Sizes====<br />
Objects use 2 values to define their size for collision. A Width Radius, and a Height Radius. These are centred outward on the object's origin, forming a box around it. <br />
In a game with small sprites and pixel art, a pixel difference with worth acknowledging. A size in Sonic games ''isn't'' simply 2 times the radius.<br />
<br />
[[Image:SPGHitBoxRadius.png]]<br />
<br />
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 pushing Width Radius of 10 become a total width of 21, '''not''' 20. This applies for all heights as well as all widths. This basically results in every collision mask, box, or sensor arrangement always being an odd number in overall size. This means they also won't perfectly match up with the evenly sized pixel art.<br />
<br />
It's a matter of 1 pixel in width and height, but it could make all the difference in accuracy. To avoid confusion, both measurements will be mentioned (otherwise, refer to image examples).<br />
<br />
====Hitboxes====<br />
Hitbox sizes are not the same thing as the object's Width Radius and Height Radius, though they do follow the same format. They are defined separately and can differ greatly from the normal object size. For example, a ring's Width/Height Radius would be 8, but for it's hitbox it is 6.<br />
<br />
====Characters====<br />
Characters follow the same format as any other object for the most part. But there are some specific character traits that can help you get started.<br />
<br />
Sonic's Width Radius is usually 9, and his Height Radius is usually 19. These radiuses determine where Sonic's floor and ceiling tile sensors will be. These can change depending on Sonic's action, and how they behave will be described in Solid Tiles, as they are most relevant to collision specifically.<br />
Sonic also has another radius that he uses for pushing against objects and tiles. We will call this his '''Push Radius''' which is always 10. This radius is implied via being used for the sensor positions and during object collision rather than actually defined as a variable in-game, though it simply makes more sense here to describe it as a constant.<br />
<br />
===Colliding===<br />
The main thing to keep in mind with any object/tile collision is that it typically only concerns itself with any object's ''whole pixel'' position, totally ignoring any subpixel amount. So when you perform any collision calculations, you can emulate this by just comparing/testing/using a floored position instead of the real position.<br />
<br />
[[Category:Sonic Physics Guide|Basics]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323945SPG:Characters2021-04-04T18:48:07Z<p>Lapper2: Referenced the variable name "jmp"</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
The sizes of characters are important, they determine how the characters will collide with Solid Tiles, Solid Objects and more. <br />
<br />
These are not hitboxes, hitboxes are separate and aren't related to the solid size of objects. Hitboxes will be covered in [[SPG:Solid Objects|Solid Objects]]<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
[[Image:SPGSonicSizes.gif|link=Special:FilePath/SPGSonicSizes.gif]]<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force (''jmp'') is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
==Insta-Shield==<br />
<br />
The Insta-Shield expands Sonic's hitbox giving it a width radius of 24 and a height radius of 24, resulting in an overall height of 49 x 49 (more about [[SPG:Game_Objects#Hitboxes|Hit Boxes]]). This lasts for 13 frames.<br />
<br />
The Insta-Shield does nothing to Sonic's X Speed or Y Speed. It does however allow him to control a rolling jump.<br />
<br />
<br />
==Tails==<br />
[[Image:SPGTailsSizes.gif|link=Special:FilePath/SPGTailsSizes.gif]]<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force (''jmp'') is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
[[Image:SPGKnucklesSizes.gif|link=Special:FilePath/SPGKnucklesSizes.gif]]<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
<br />
Well that is except for when he is gliding, climbing and sliding. <br />
<br />
[[Image:SPGKnucklesMoveSizes.gif|link=Special:FilePath/SPGKnucklesMoveSizes.gif]]<br />
<br />
Here, his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force (''jmp'') is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X Speed is set to '''4''' in the opposite direction of the wall, and his Y Speed is set to '''-4'''.<br />
<br />
Interestingly, because of a pixel offset when sprites are flipped, Knuckles' feet poke 1px out from the side of his size when he is on a wall to the left. This means his feet should be inside the wall a bit. Well, when on a left wall there is actually a 1px gap between knuckles X - Push Radius and the wall, purely to make it look correct.<br />
<br />
====Falling====<br />
When climbing, Knuckles will fall off the bottom of a wall if no wall is found at his Y Position + Height Radius (checking horizontally into the wall).<br />
<br />
====Clambering====<br />
Knuckles will clamber atop a ledge if it no wall is found at his Y position - Height Radius (checking horizontally into the wall). <br />
<br />
[[File:SPGKnucklesClamber.gif|link=Special:FilePath/File:SPGKnucklesClamber.gif]]<br />
<br />
When clambering, Knuckles plays a 3 sub-image animation. Each sub-image of the animation lasts 6 frames, and after the 3rd sub-image knuckles is standing on the ledge, where his X Position is the ledge X. Each frame of the animation moves knuckles to a new position as shown.<br />
<br />
His position moves back and forth a bit as he climbs so that his sprite aligns. If your camera is set up correctly, usually the first 2 sub-images of motion will push the camera forward into place and the 3rd sub-image's backward motion won't move the camera at all, which makes it look smooth enough.<br />
<br />
====Stopping at Floors and Ceilings====<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius).<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Slope_Physics&diff=323944SPG:Slope Physics2021-04-04T18:46:48Z<p>Lapper2: Removing jumping at angles from here, moved to jumping page</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*This guide relies on information about tiles and sensors discussed in [[SPG:Solid_Tiles|Solid Tiles]]<br />
<br />
*Following only describes how the Player collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player objects and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
Once you have the Player object able to collide with solid tiles, they need to move correctly over the terrain surface with momentum and physics. Knowing how sensors work will allow the Player move smoothly over terrain with different heights, and knowing how the Player's ground speed is affected by inputs to walk will allow him to move left and right, but that is not all there is to the engine. <br />
This guide will explain how the Player reacts to certain angles, and how 360 degree movement with momentum is achieved.<br />
<br />
==Moving At Angles==<br />
<br />
The Player's speed has to be attenuated by angled ground in order to be realistic.<br />
<br />
There are two ways in which the Player's ground speed is affected on angles. The first will make sure that they do not traverse a hill in the same amount of time as walking over flat ground of an equal width. The second will slow them down when going uphill and speed them up when going downhill. Let's look at each of these in turn.<br />
<br />
===The Three Speed Variables===<br />
<br />
If Sonic were a simple platformer that required nothing but blocks, you would only need two speed variables: X speed (X Speed) and Y speed (Y Speed), the horizontal and vertical components of the Player's velocity. Acceleration (''acc''), deceleration (''dec''), and friction (''frc'') are added to X Speed; jump/bounce velocity and gravity (''grv'') are added to Y Speed (when the Player is in the air).<br />
<br />
But when slopes are involved, while the Player moves along a slope, they're moving both horizontally and vertically. This means that both X Speed and Y Speed have a non-zero value. Simply adding ''acc'', ''dec'', or ''frc'' to X Speed no longer works; imagine the Player was trying to run up a wall - adding to their horizontal speed would be useless because they need to move upward.<br />
<br />
The trick is to employ a third speed variable (as the original engine does), Ground Speed. This is the speed of the Player along the ground, disregarding Ground Angle altogether. ''acc'', ''dec'', and ''frc'' are applied to Ground Speed, not X Speed or Y Speed.<br />
<br />
While on the ground, X Speed and Y Speed are derived from Ground Speed every step before the Player is moved. Perhaps a pseudo-code example is in order:<br />
<br />
X Speed = Ground Speed * cos(Ground Angle)<br />
Y Speed = Ground Speed * -sin(Ground Angle)<br />
<br />
X Position += X Speed<br />
Y Position += Y Speed<br />
<br />
No matter what happens to the Ground Angle, Ground Speed is preserved, so the engine always knows what speed the Player is "really" moving at.<br />
<br />
===Slope Factor===<br />
<br />
By this point, the Player should be able to handle any hills with an accurate velocity but they still need to slow down when going uphill and speed up when going downhill.<br />
<br />
Fortunately, this is simple to achieve - with something called the Slope Factor. Just subtract Slope Factor*sin(Ground Angle) from Ground Speed at the beginning of every step. <br />
<br />
Ground Speed -= Slope Factor*sin(Ground Angle);<br />
<br />
The value of Slope Factor is always ''slp'' when running, but not so when rolling. When the Player is rolling uphill (the sign of Ground Speed is equal to the sign of sin(Ground Angle)), Slope Factor is ''slprollup'' ($001E). When the Player is rolling downhill (the sign of Ground Speed is '''not''' equal to the sign of sin(Ground Angle)), Slope Factor is ''slprolldown'' ($0050).<br />
<br />
'''Note:''' <br />
*In Sonic 1, it appears that Slope Factor doesn't get added if the Player is stopped and in his standing/waiting cycle. But in Sonic 3 & Knuckles, Slope Factor seems to be added even then, so that the Player can't stand on steep slopes - it will force them to walk down.<br />
<br />
==Switching Mode==<br />
<br />
So the Player can run over hills and ramps and ledges, and all that is great. But it is ''still'' not enough. They cannot make their way from the ground to walls and ceilings without more work.<br />
<br />
Why not? Well, because sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> 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.<br />
<br />
How can we solve this? By using four different modes of movement. This will take a little explaining.<br />
<br />
===The Four Modes===<br />
<br />
It seems pretty reasonable to assume that, because the Player 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.<br />
<br />
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 Ground Speed, but nothing more. The character would still always move horizontally and move straight up and down to adhere to floor level.<br />
<br />
This is much like how the Sonic games do things. Only, when Ground Angle gets too steep, the Player 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, the Player behaves like a simpler platformer. The magic happens by combining all four modes, and cleverly switching between them smoothly.<br />
<br />
So how and when does the Player switch mode?<br />
<br />
When in Floor mode, and Ground Angle 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 the Player is moved to "floor" level horizontally instead of vertically.<br />
<br />
Now that they're in Right Wall mode, if Ground Angle is shallower than 45° ($E0), the engine switches back into Floor mode.<br />
<br />
The other transitions work in exactly the same way, with the switch angles relative to the current mode.<br />
<br />
When the mode is being calculated, it simply checks which quadrant the Player's Ground Angle is currently in, which will place the Player in the correct mode (ranges are inclusive):<br />
<br />
Floor Mode (start of rotation)<br />
0° to 45° (1~32) ($FF~$E0)<br />
<br />
Right Wall Mode<br />
46° to 134° (33~95) ($DF~$A1)<br />
<br />
Ceiling Mode<br />
135° to 225° (96~160) ($A0~$60) <br />
<br />
Left Wall Mode<br />
226° to 314° (161~223) ($5F~$21)<br />
<br />
Floor Mode (end of rotation)<br />
315° to 360° (224~256) ($20~$00)<br />
<br />
Note:<br />
*Since the classic games don't use degrees, and rather have angles ranging from 0 to 256, both approximate degree values and a more accurate decimal representation of the Hex values are included.<br />
<br />
These ranges are symmetrical for left and right, but does favour the floor and ceiling modes, with their ranges being a degree or two wider.<br />
<br />
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 <span style="color:#00f000; font-weight: bold;">A</span> is now at the Player's Y Position + Width Radius instead of X Position - width Radius. Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> is now at the Player's Y Position - Width Radius, instead of X Position + Width Radius. Instead of downward vertical sensor, they are now horizontal facing left, at his foot level (which is now "below" them, at X Position + Width Radius). You they move and rotate in the same way for the other modes.<br />
<br />
Yes, because the sensors move so far, it is possible for the Player 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. <br />
<br />
Note:<br />
*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 rectangle. 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 the Player doesn't begin walking on a wall when they get near one, but would mean the Player switched mode sooner on a slope which means less "popping".<br />
<br />
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.<br />
<br />
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, the Player knows that the height value found should be used to move them down and not up.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
With these four modes, the Player can go over all sorts of shapes. Inner curves, outer curves, you name them. Here are some approximate example images with their angle values to help give you some idea of what this results in:<br />
<br />
[[Image:SPGInnerCurve.PNG|link=Special:FilePath/SPGInnerCurve.PNG]] [[Image:SPGInnerCurveChart.PNG|link=Special:FilePath/SPGInnerCurveChart.PNG]]<br />
<br />
You can observe Sonic's mode changing on the frame after his floor angle (Ground Angle) exceeds 45°. Sonic's position shifts a bit when the change occurs, due to the totally new collision angle and position.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
[[Image:SPGOuterCurve.PNG|link=Special:FilePath/SPGOuterCurve.PNG]] [[Image:SPGOuterCurveChart.PNG|link=Special:FilePath/SPGOuterCurveChart.PNG]]<br />
<br />
You may notice the Player's mode switches erratically on the convex curve, this is because his floor angle (Ground Angle) will suddenly decrease when switching to wall mode, causing it to switch back and forth until he is far enough down the curve to stabilise. This isn't usually noticeable, and happens less the faster you are moving.<br />
</div><br />
</div><br />
<br />
Note: The reason the gifs show the mode switch being the frame ''after'' the angle threshold is reached is simply because the collision being shown is the one used for ''that'' frame, ''before'' the Player's Ground Angle updates, but after they have moved. <br />
<br />
===When to Change Mode===<br />
<br />
If you've checked the guide regarding the [[SPG:Main_Game_Loop|Main Game Loop]] you may notice the mode switching isn't mentioned at all, that's because the game doesn't actually ever "switch" the Player's mode. The Player's current "mode" is decided right before collision occurs. It will measure his angle, and decide which mode of collision to use right there and then. There is no "Mode" state stored in memory. So effectively, the Player's mode updates whenever his angle (Ground Angle) does. <br />
<br />
Since the floor angle (Ground Angle) is decided ''after'' floor collision (as a result of floor collision) the floor collision that frame has to use the previous frames angle, even though the Player has moved to a new part of the slope since then. This results in the Player's mode effectively changing 1 frame ''after'' the Player reaches one of the 45 degree angle thresholds, as seen above.<br />
<br />
===Falling and Sliding Off Of Walls And Ceilings===<br />
<br />
When in Right Wall, Left Wall, or Ceiling mode and the Player's Ground Angle is between 90 and 270, they will fall any time absolute Ground Speed falls below ''fall'' ($0280) (Ground Speed is set to 0 at this time, but X Speed and Y Speed are unaffected, so the Player will continue their trajectory through the air). This happens even if there is ground beneath them. If the Player is in Right Wall, Left Wall, or Ceiling Mode but their Ground Angle is not between 90 and 270 then the horizontal control lock timer described below will still be set to 30 but the Player will not enter a falling state remaining in their current state.<br />
<br />
====Horizontal Control Lock====<br />
<br />
When the Player falls or slides off in the manner described above, the [[SPG:Springs and Things#Horizontal Control Lock|horizontal control lock]] timer is set to 30 ($1E) (it won't begin to count down until the Player lands back on the ground). While this timer is non-zero and the Player is on the ground, it prevents directional input from adjusting the Player'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 the Player fell back on the ground with is in effect, so the Player will slip back down the slope.<br />
<br />
if abs(Ground Speed) < 2.5 and (angle >= 45 and angle <= 315)<br />
{<br />
if angle >= 90 and angle <= 270<br />
{<br />
floor_mode = 0<br />
Ground Speed = 0<br />
}<br />
horizontal_lock_timer = 30<br />
}<br />
<br />
==The Air State==<br />
<br />
Any time the Player is in the air, they don't have to worry about angles, Ground Speed, ''slp'', or any of that jazz. All they have to do is move using X Speed and Y Speed until they detect the ground, at which point they re-enter the ground state.<br />
<br />
===Jumping "Through" Floors===<br />
<br />
There are some ledges that the Player can jump up "through". These are often in the hilly, green zones such as [[Green Hill Zone (Sonic the Hedgehog 16-bit)|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 the Player's <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors. They are ignored entirely by C and D as well as the wall sensors E and F. Finally, sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> (mostly) only detect the floor when the Player is moving downwards (but always while on the ground). So with a slightly shorter jump, you will see the Player 'pop' upwards onto a jump through surface once they begin to fall.<br />
<br />
===Reacquisition Of The Ground===<br />
Both X Speed and Y Speed are derived from Ground Speed while the Player is on the ground. When they fall or otherwise leave the ground, X Speed and Y Speed are already the proper values for him to continue his trajectory through the air. But when they land back on the ground, Ground Speed must be calculated from the X Speed and Y Speed that they have when it happens.<br />
You might think that the game 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.<br />
<br />
As you land the angle of the ground you touch is read (Ground Angle).<br />
The following covers the angle (Ground Angle) of the ground (floor or ceiling) that the Player touches as they land, and only happens the frame when they land when changing from in air to on ground.<br />
<br />
''Note: Since the classic games don't use degrees, and rather have angles ranging from 0 to 256, both approximate degree values and a more accurate (and inverted) decimal representation of the Hex values are included.''<br />
<br />
====When Falling Downward====<br />
[[Image:SPGLandFloor.png|link=Special:FilePath/SPGLandFloor.png]]<br />
<br />
The following ranges are inclusive.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-4 columns" style="padding:0px"><br />
'''Shallow:'''<br />
When Ground Angle is in the range of <br />
<br />
0° to 23° (1~16) ($FF~$F0) <br />
and mirrored:<br />
339° to 360° (241~256) ($0F~$00)<br />
<br />
Ground Speed is set to the value of X Speed.<br />
</div><br />
<div class="large-4 columns"><br />
'''Half Steep:'''<br />
When Ground Angle is in the range of <br />
<br />
24° to 45° (17~32) ($EF~$E0) <br />
and mirrored:<br />
316° to 338° (225~240) ($1F~$10)<br />
<br />
Ground Speed is set to X Speed but only if the absolute of X Speed is greater than Y Speed. Otherwise, Ground Speed is set to Y Speed*0.5*-sign(sin(Ground Angle)). <br />
</div><br />
<div class="large-4 columns"><br />
'''Full Steep:'''<br />
When Ground Angle is in the range of <br />
<br />
46° to 90° (33~64) ($DF~$C0) <br />
and mirrored:<br />
271° to 315° (193~224) ($3F~$20)<br />
<br />
Ground Speed is set to X Speed but only if the absolute of X Speed is greater than Y Speed. Otherwise, Ground Speed is set to Y Speed*-sign(sin(Ground Angle)).<br />
</div><br />
</div><br />
<br />
====When Going Upward====<br />
[[Image:SPGLandCeiling.png|link=Special:FilePath/SPGLandCeiling.png]]<br />
<br />
The following ranges are inclusive.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Slope:'''<br />
When the ceiling Ground Angle detected is in the range of <br />
<br />
91° to 135° (65~96) ($BF~$A0) <br />
and mirrored <br />
226° to 270° (161~192) ($5F~$40)<br />
<br />
The Player reattaches to the ceiling and Ground Speed is set to Y Speed*-sign(sin(Ground Angle)).<br />
</div><br />
<div class="large-6 columns"><br />
'''Ceiling:'''<br />
When the ceiling Ground Angle is in the range of <br />
<br />
136° to 225° (97~160) ($9F~$60)<br />
<br />
The Player hits his head like with any ceiling, and doesn't reattach to it. Y Speed is set to 0, and X Speed is unaffected.<br />
</div><br />
</div><br />
<br />
===Air Rotation===<br />
When the Player leaves a slope, such as running up and off a quarter pipe, the Players's Ground Angle smoothly returns to 0. <br />
<br />
The Player's Ground Angle changes by <br />
2.8125° (2) ($2)<br />
each frame, in the direction towards 0.<br />
<br />
<br />
*''Note: Degree angle is approximate, as the original game has angles ranging up to 256. Degree, decimal, and hex have been provided.''<br />
*''Note: Regardless of the Player's Ground Angle, their airborne sensors do not rotate. Air collision essentially ignores the Player's mode.''<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Jumping&diff=323943SPG:Jumping2021-04-04T18:46:24Z<p>Lapper2: Moved slope jumping to this guide, removed unnecessary inaccurate air drag calculations, Also replaced "Sonic" with "the Player"</p>
<hr />
<div>Notes:<br />
<br />
Research applies to all four of the [[Sega Mega Drive]] games, and [[Sonic CD]].<br />
<br />
The following only applies when [[Sonic]] is out of water land with no special power-ups. Water physics, [[Super Sonic]], and [[Speed Shoes]] will be covered in separate guides.<br />
<br />
==Air Acceleration==<br />
<br />
When the Player is airborne, they can accelerate twice as fast as on land (''air'').<br />
<br />
There is no friction in the air (but there is a complicated drag effect that will be explained later), and pretty much, once the Player has a certain X Speed in the air, they'll just keep going at that speed until you tell them otherwise or they hit a wall.<br />
<br />
Furthermore, there is no distinction for deceleration in the air, either, so pressing {{left}} simply subtracts ''air'' from X Speed no matter what, and pressing {{right}} adds ''air'' to X Speed.<br />
<br />
Sonic's top speed (''top'') is the same in the air as on the ground.<br />
<br />
==Gravity==<br />
<br />
Gravity (''grv'') is added to Y Speed in every step in which the Player isn't on the ground. It's what makes them fall downward when they run off of cliffs. It's also what stops them from moving upward forever when they jump.<br />
<br />
==Top Y Speed==<br />
<br />
In Sonic 1, the Player's Y Speed does not seem to be limited. As they fall, ''grv'' continues to be added to Y Speed, increasing indefinitely.<br />
<br />
In Sonic CD, however, a limit was introduced so that Y Speed does not exceed 16 pixels per step. This limit is important so that the Player never outruns the camera, or passes through solid ground because they're moving so fast per step that they never even collide with it. Most likely introduced in Sonic CD because of the increased height in the design of levels such as Collision Chaos, and the endless vertical shaft in Tidal Tempest. Without the limit, the Player would accelerate to ridiculous velocities in some of these areas and probably break the game.<br />
<br />
<nowiki><br />
Y Speed += grv<br />
if (Y Speed > 16) Y Speed = 16<br />
</nowiki><br />
<br />
==Air Drag==<br />
<br />
Each step the Player is in the air, a special formula is performed on their's X Speed, but only if certain conditions are met. First, Y Speed must be negative. Second, Y Speed must be more than -4 (e.g. -3, or -3.5, is "more" than -4). <br />
<br />
<nowiki><br />
if (Y Speed < 0 && Y Speed > -4)<br />
{<br />
X Speed -= ((X Speed div 0.125) / 256);<br />
}<br />
</nowiki><br />
<br />
Air drag is calculated each step before ''grv'' is added to Y Speed.<br />
<br />
==Jump Velocity==<br />
Jumping is affected by the angle the Player is at when they do it. It can't simply set Y Speed to negative ''jmp'' - they need to jump away from the Ground Angle they're standing on. Instead, both X Speed and Y Speed must have ''jmp'' subtracted from them, using cos() and sin() to get the right values.<br />
<br />
More pseudo-code:<br />
<br />
X Speed -= jmp * sin(Ground Angle)<br />
Y Speed -= jmp * cos(Ground Angle)<br />
<br />
Notice how the jump values are subtracted from the X Speed and Y Speed. 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.<br />
<br />
===Variable Jump Height===<br />
Though we've become accustomed to it now, at the time Sonic the Hedgehog was first released, there were a whole lot of games that had fixed jump heights. No matter how quickly you released the jump button, the character would sail up into the air the same number of pixels. Games like Mario and Sonic were some of the first to have more variable and responsive controls, allowing for an improved sense of control over the character, and therefore a much more fun - and forgiving - gameplay experience.<br />
<br />
So how does variable jump height work?<br />
<br />
If the jump button is no longer being held, and you are currently jumping (in the air after pressing jump, not walking or rolling off the ground or pushed away by a spring etc), the computer checks to see if Y Speed is less than -4 (e.g. -5 is "less" than -4). If it is, then Y Speed is set to -4. In this way, you can cut your jump short at any time, just by releasing the jump button. If you release the button in the very next step after jumping, the Player makes the shortest possible jump. This happens whenever you aren't holding the jump button and are currently jumping, not just on the release of the button.<br />
<br />
The check to see if the button is not being held is performed before the Player is moved to his new position and ''grv'' is added to Y Speed.<br />
<br />
===Other characters===<br />
The Player's jump force (jmp) varies depending on the character, these differences are detailed in [[SPG:Characters|Characters]].<br />
<br />
===Bugs when Jumping===<br />
Because the Player is moving from one action to another, at the same time as changing his collision sensor arrangement, and at the same time as changing speed and leaving the floor... some bugs can occur when launching from the ground and when landing.<br />
<br />
Disturbingly, the computer checks to see if you press the jump button before moving the Player. If you do press the jump button, it exits the rest of the cycle - so the Player doesn't move at all during the step in which you jump, vertically or horizontally.<br />
<br />
[[Image:SPGJumpDelay.gif]]<br />
<br />
This can mean that in the next step it can detect a release of the jump button and the Player will move up at a Y Speed of -4 without having ever moved up at the speed of ''jmp''.<br />
<br />
As described in [[SPG:Solid_Tiles#Sensors|Sensors]] the Player's sensor arrangement will change in size depending on what they're doing. When rolling/jumping, this arrangement becomes smaller. Whenever this happens, the Player will move up or down by 5px to keep his bottom most pixel in the same position.<br />
However in Sonic 2, when you are rolling and perform a jump, the Player's collision size '''incorrectly''' becomes it's standing size rather than the smaller rolling size. This in turn causes a few bizarre issues. <br />
<br />
Since while jumping the Player is in his ball form, they will still 'uncurl' when they land, and this involves setting the Width and Height Radius to standing height and subtracting 5 from Y Position. This of course will simply move the Player's bottom most pixel up by 5 if their sizes don't change size. <br />
So, when the Player lands from this jump, it just assumes their Height Radius is becoming big again, so 5px is subtracted from his Y Position. However because their Size didn't change at all and the Player was already at floor level, they just rise 5px above the floor instantly upon landing.<br />
<br />
[[Image:SPGRollingJumpLandBug.gif]]<br />
<br />
This results in them making contact with the floor, then being moved up 5px then falling back down slowly via gravity.<br />
<br />
[[Category:Sonic Physics Guide|Jumping]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=323938SPG:Solid Objects2021-04-02T18:41:03Z<p>Lapper2: Making solid object explanation easier to follow, and changing "Sonic" to "The Player"</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with the Player in many different ways and are a very different beast than Solid Tiles.<br />
<br />
There are 2 main ways objects interact with the Player. Hitboxes like rings and checkpoints, and solidity like item boxes or push blocks. Yes, both of these are completely seperate.<br />
<br />
We'll go over hitbox interactions first.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with the Player when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Note:<br />
*Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push the Player in the other direction. As a general rule, any seemingly solid object that the Player cannot stand on is most likely actually using a hitbox rather than ''real'' solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If the Player's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to the Player. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===The Player's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, the Player needs their own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
The hitbox for Sonic.<br />
<br />
The Player's hitbox is much like that of any other object. It sits at the their X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than the Player's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, obviously the Player's hitbox needs to shrink. Problem is, the Player's position and Height Radius don't actually change at all while crouching. So to tackle this the game manually updates the hitbox's size and position while the Player crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centred between pixels (see [[SPG:Basics#Sizes|Basics]]) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
<br />
==Solid Objects==<br />
<br />
Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. The Player does not collide with objects using his solid tile sensors, instead, special calculations are used to check if the Player's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after the Player's code has been executed for that frame, including their tile collision '''and''' movement. Since objects run their code '''after''' the Player, it's the job of the objects to push the Player out of themselves. Like say the Player is running towards a solid block object with some medium speed. When their position changes at the end of their code, they will move inside the solid object. Then soon afterwards on the same frame the solid object runs its code, checks for the Player and acts solid accordingly, pushing the Player out.<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does not involve the object hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. The Player will use their Height Radius for this too, but horizontally they of course use their Push Radius instead.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* The Player will check if he is overlapping the object.<br />
* The Player will decide which side of the object he is nearest to n both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels he is to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether he's closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* He will then be pushed out towards that side on that axis by the distance he overlaps. <br />
<br />
Now, let's get into the details.<br />
<br />
The first thing the object collision code does is check if the Player is standing on the object. The Player has a flag which determines if they are standing an object, which is set upon landing on one. If they are, it will skip straight to checking if the Player has walked off the edges rather than general object collision (which we will go into detail about further down in [[SPG:Solid Objects#Standing On Solid Objects|Standing On Solid Objects]]). Otherwise, it will continue as follows.<br />
<br />
<br />
====Checking For An Overlap====<br />
<br />
First thing that needs to happen is the game needs to know if the Player is even touching the object to begin with. <br />
<br />
Both the Player and the solid object are of course rectangles, but it would be costly to check if 2 rectangles overlap each frame. Instead, a lot of calculations are saved because checks if a single position (the Player's position) is within one rectangle. This is achieved by combining the Player's current Push and Height Radius values with the object's Width and Height Radius values to form this new rectangle.<br />
<br />
Horizontally, the object combines its own Width Radius with the Player's Push Radius and adds 1px extra (so Push Radius + 1). The extra pixel is added because the final position the Player pushes at is the Players Push Radius + 1 away from the object's side edge.<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with the Player's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
Here's a demonstration of how these new radiuses relate to the Player's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius''', and I will refer to the entire box as the '''combined box'''.<br />
I will also refer to '''combined X diameter''' (which is combined X radius * 2) and '''combined Y diameter''' (which is combined Y radius * 2).<br />
<br />
Now all the game needs to worry about is the Player's X Position and Y Position being within this new '''combined box''', it no longer needs to worry about what the Player's sizes are at all.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between the Player's X Position and the left edge of the '''combined box'''.<br />
<br />
left_difference = (the Player's X Position - object's X Position) + combined_x_radius<br />
<br />
Then, it will check if this new difference value has passed the left or right boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far to the left to be touching?<br />
if (left_difference < 0) exit object collision<br />
<br />
// the Player is too far to the right to be touching?<br />
if (left_difference > combined_x_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
<br />
<br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between the Player's Y Position and the top edge of the '''combined box'''.<br />
<br />
top_difference = (the Player's Y Position - object's Y Position) + 4 + combined_y_radius<br />
<br />
The game also allows the Player to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge the Player was standing on. The game does this by effectively pretending the Player is 4px lower than they really are when checking the y overlap. If the object is lower than the Player, top_difference would be negative before '''combined Y radius''' is added, so it is achieved by simply adding 4 to the distance. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the top or bottom boundaries of the '''combined box''', and exit the object collision if it has.<br />
<br />
// the Player is too far above to be touching<br />
if (top_difference < 0) exit object collision<br />
<br />
// the Player is too far down to be touching<br />
if (top_difference > combined_y_diameter) exit object collision<br />
<br />
If no exit occurred, the Player is overlapping on the y axis, and it will continue to the next step. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
The reason the game does it in this fashion rather than just checking between -radius and +radius for example is to preserve calculations needed. It has been done in such a way that it now has 2 variables it can keep using, left_difference and top_difference.<br />
<br />
<br />
====Finding The Direction of Collision====<br />
If the Player is found to be touching the object, the game will then decide whether they are to be popped out the top or bottom, or the left or right of the object. The game will compare the Player's position to the object's position to determine which side they are on. <br />
<br />
To do this, the game will first determine which side the Player is in comparison with the object's position. <br />
<br />
If the Player's X Position is greater than the object's X position, they're on the right, otherwise, they're on the left. If the Player's Y Position is greater than the object's Y position, they're on the bottom, otherwise, they're on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the ''nearest'' edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
<br />
<br />
=====Horizontal Edge Distance=====<br />
If the Player is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side of the '''combined box''' and will be a positive number. <br />
<br />
x_distance = left_difference<br />
<br />
If the Player is on the right, the distance will be flipped around like so:<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the right side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance''', and the game knows which side, left or right, the Player is based on the sign (+/-) of this value.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If the Player is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the top side of the '''combined box''' and will be a positive number. <br />
<br />
y_distance = top_distance<br />
<br />
If the Player is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
y_distance = top_difference - 4 - object_y_diameter<br />
<br />
This is effectively the distance to the bottom side of the '''combined box''' and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance''', and the game knows which side, top or bottom, the Player is based on the sign (+/-) of this value.<br />
<br />
''Note: You may have noticed that if the Player is on the top, the extra 4px isn't subtracted yet. It will be subtracted upon landing on top.''<br />
</div><br />
</div><br />
<br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way the Player should be popped out. Either vertically or horizontally.<br />
<br />
It does this by finding which side the Player is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic's Width and Height Radius not being square. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out the Player on either axis depending on the sign (+/-) of the distance value. When colliding vertically, the game knows that Player is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
<br />
====Popping The Player Out====<br />
Once a collision has occurred and the game had decided the direction the Player then needs to be "popped out" of the '''combined box''' so that their position is no longer within it. But where does it put the Player? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance the Player needs to move to exit the object, but reversed. So when they are popped out, they will simply be subtracted from their position.<br />
<br />
=====Popped Left and Right=====<br />
Popping the Player out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping the Player out horizontally if absolute '''y distance''' is greater than 4. If not, it will exit.<br />
<br />
If the game does decide to affect the Player's speeds, this also depends on a few factors. If the Player is on the left and the game has decided they need to be popped out to the object's left side, it will only stop the Player's speeds if they are is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if the Player is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from the Player's position, popping the Player out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and the Player is told they are pushing.<br />
<br />
=====Popped Downwards=====<br />
If the Player bumps the bottom of an object, the game will check if the Player is moving vertically (Y Speed is not 0). If not, the game then checks if the Player is standing on the ground, and if they are, kills them from crushing, then exits. This is why you can see secret crushing objects in ceilings, as when the Player touches them while standing on anything he will be crushed as described. Only objects can do this.<br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as the Player is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides the Player is to be popped out upwards, they will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is larger than or equal than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will completely forget about the '''combined X radius''' we were using before, and use the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare the Player's position using this radius.<br />
<br />
First it will get a distance from the Player's X Position to the object's right edge.<br />
<br />
x_comparison = object's X Position + object's X Radius - the Player's X Position<br />
<br />
Then it will check this comparison to tell if the Player is within the x boundaries.<br />
<br />
// if the Player is too far to the right<br />
if (x_comparison is less than 0) exit landing<br />
<br />
// if the Player is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter) exit landing<br />
<br />
This means the Player will exit the landing and will just slip off the side keep falling if their X Position isn't directly above the object, which is actually quite strange as it's as if the Player is only 1 pixel thick. You may wish to keep using the combined radius here instead.<br />
<br />
The last check is if the Player's Y Speed is negative, they wont land and it will exit.<br />
<br />
Finally, if the code has reached this far, the Player will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from the Player's Y Position. It also subtracts an extra 1px afterwards to align them correctly (which is why that extra 1px was added to the combined_x_radius in the first place!).<br />
<br />
Then it resets the Player to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets the Player's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game the Player is on the object. <br />
<br />
Finally, the Player's Ground Speed is set to equal their X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], the Player's collisions with tiles and objects only concern themselves with the Player's floored position (their pixel position), and the same applies to the object itself. So, upon the point of contact, the Player's floored X Position finds itself overlapping the '''combined box'''. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if the Player was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if the Player keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath the Player each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue the Player to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, the Player is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If the Player is standing on an object, the object will only check if the Player has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = (the Player's X Position - the object's X) + combined X radius //get the position difference<br />
<br />
The Player will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and the Player is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if the Player is still on it, the game handles moving the Player with the object and keeping them stuck to it.<br />
<br />
====Things to Note====<br />
As mentioned when describing hitboxes, they are uneven and odd sized compared to the sprite. Using the method described above - the same is true for solid object boxes, so the Player will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if the Player's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push the Player two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, the Player can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
The Player will collide on top with both spikes, but his position isn't directly over either of them when landing, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overlap====<br />
When the vertical overlap is being checked, the game pretends the Player is 4px lower than they actually are. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, the Player will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
This can be corrected by accounting for the added 4px when checking overlap and calculating distances with the bottom of the object.<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that the Player is still grounded even though there's no longer any object to be grounded to. Because the Player's grounded, he won't fall. Additionally, they also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like the Player does, at their central bottom point. The same applies to wall collision. The way that objects use tile collision varies greatly and will be described for each individual Game Object.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
The array for these objects are <br />
32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 48 48 48 48 48<br />
and<br />
32 32 32 32 32 32 32 32 32 32 32 32 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 32 32 32 32 32 32 32 32 32 32 32 32 32<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. Each value is the distance from the Y Position to the object's surface. Effectively a "fake" Height Radius for the top surface at every x position along the object.<br />
<br />
The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read. There are exceptions to this however, if the arrays step up in 2s (like 12 14 16) when stretched out the game will interpolate between these values to a 45 degree slope. This happens for the diagonal springs, for example.<br />
<br />
When a sloped object is acting solid to the Player, instead of using the y position of the surface of the solid box using the object's Height Radius, it instead reads the value from the array, and from there on as far as the Player is concerned, the object's Height Radius is as high as the array tells it. This continuously happens as the Player passes over the object, resulting in smooth motion.<br />
<br />
As you can see there is some extra information to the sides, this is simply because the Player can be standing on an object while their X Position isn't directly over it. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the object boundary.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that the Player will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, the Player will be slightly "deeper" into the slopes than they would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of their side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all the Player can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if the Player 's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with the Player while they are moving down or staying still. This is why the Player can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then the Player's bottom Y is calculated by adding their Height Radius to their Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows the Player to collide even when they're 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than the Player's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between the Player's bottom and the platform's surface (platform's surface Y minus the Player's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as the Player is too low.<br />
<br />
If it reaches past all those checks, the Player will land.<br />
<br />
====Popping The Player Out====<br />
<br />
The distance from before is added to the Player's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add the Player's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with most platforms. This means not only will the Player fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should as if they are only 1px in total width, unlike the normal object collision.<br />
<br />
This was probably missed because the Player doesn't need to push against these platforms, so it's much harder to notice if the Player's Push Radius hasn't been applied. <br />
<br />
After this of course, the Player is still standing on it, so the game handles updating the Player's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and the Player's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When the Player has contacted the push block, the Player has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If the Player pushed to the left, both the Player and the block will move 1 pixel to the left, the Player's X Speed is set to 0 and Ground Speed is set to -0.25. If they pushed to the right, both the Player and the block will move 1 pixel to the right, the Player's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out the Player is no longer touching the object. When this happens, the Player's pixel position has been altered, but their subpixel position remains the same. So if the Player was half a pixel into the object before, they're now half a pixel outside of it. Before they make contact with the object again, they needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because they are given a headstart when their Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. the Player's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and the Player's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, the Player has just pushed the block and has been moved out of it, then along with it. The fractional part of their position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because the Player's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of their position is currently .003 , the very left of the pixel. This means they have farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- the Player gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- the Player gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. the Player's X pixel has changed<br />
<br />
-- the Player makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- the Player makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
The Player has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while the Player is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item Monitors, as you may have noticed, are not always solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what the Player is doing and changing how the Item Monitor will react.<br />
<br />
While not curled up in a ball, the Item Monitor acts as solid. The Item Monitor's hitbox isn't accessible at this time.<br />
<br />
While curled, however, the item box will no longer have any solidity at all, and its hitbox is active and accessible. The hitbox collision is what destroys the Item Box and bounces the Player off (Details in [[SPG:Rebound#Monitors|Monitors Rebound]]).<br />
<br />
However, there is an exception. If the Player is moving up (Y Speed < 0) while curled, the Item Box will in fact still act solid. Additionally, if the Player's Y Position-16 is smaller than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards, and the Player's Y Speed will be reversed.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Camera&diff=323914SPG:Camera2021-04-01T18:16:00Z<p>Lapper2: Changing "Sonic" to "the Player"</p>
<hr />
<div>'''Note:''' Applies to Sonic 1, 2, 3 & K, and CD except where otherwise noted.<br />
<br />
==View Borders==<br />
<br />
The Sonic games have a screen size of '''320x224''' pixels during normal Zone play. The Player isn't always locked in the centre of the screen, but has some freedom of movement within the view borders before it begins to scroll.<br />
<br />
===Horizontal Border===<br />
<br />
*Left border: '''144'''<br />
*Right border: '''160'''<br />
<br />
If you exceed the border, the camera will move by how much the Player has exceeded, up to '''16'''. So if they move faster than this, the camera will lag behind.<br />
<br />
Interestingly, this centres the Player when they walk right but they aren't centred when they walk left. If you so choose, you can change the borders to '''152''' and '''168''' instead, to even things out a bit.<br />
<br />
Sonic CD, though, effectively has both borders set to '''160''' so that the Player is always at dead centre and the view scrolls immediately when they begin moving.<br />
<br />
===Vertical Border===<br />
<br />
====In the Air====<br />
<br />
*Top border: '''64'''<br />
*Bottom border: '''128'''<br />
<br />
If you exceed the border, the camera will move by how much you've exceeded, up to '''16'''. So if the Player moves faster than this, the camera will lag behind.<br />
<br />
''Note: Knuckles uses the same vertical borders while gliding and climbing. He's considered back on the ground when he starts to clamber over the corner of a wall, or stands up after sliding from a glide (i.e. when sliding, even though he is in contact with the ground, it still considers him to be in the air).''<br />
<br />
Here's a demonstration to show how the Player's y position roams freely between the y borders.<br />
<br />
[[Image:SPGCameraAir.gif]]<br />
<br />
''Note: Sonic's position may appear a little lower than a border while jumping, this is due to a 5px offset while sonic is curled which applies to all characters. See [[SPG:Solid_Tiles#Floor_Sensors_(A_and_B)|Solid Tiles]] for more details.''<br />
<br />
====On the Ground====<br />
<br />
If the Player's Y is not equal to '''96''', the camera will move by the difference, up to a speed of '''6''' (if the Player's Y speed is less than or equal to '''6'''), or '''16''' (if the Player's ground speed is greater than or equal to '''8'''). This makes the camera catch up faster when going slow, but otherwise go at a slow pace.<br />
<br />
Here's a demonstration to show how the Player's y position stays locked to the central y.<br />
<br />
[[Image:SPGCameraGround.gif]]<br />
<br />
==Looking Up and Down==<br />
<br />
*Looking Up: Scrolls up by '''104''' pixels, at a rate of '''2''' per step.<br />
*Looking Down: Scrolls down by '''88''' pixels, at a rate of '''2''' per step.<br />
<br />
In Sonic 1, the camera begins to scroll immediately when you press up or down. Obviously, this is annoying if there is to be a spindash or super peel out in your engine. In Sonic 2, 3 & K, the solution is to simply wait '''120''' steps after you begin to hold the button before it starts scrolling. In Sonic CD, though, another method is employed. After you press the up or down button, you have '''16''' steps to press it again, upon which the screen begins to scroll. Unfortunately it will freeze the scrolling if you press the up or down button while the screen is already scrolling. For instance, look up by double-tapping, let go, and then press down. The screen will freeze, and stay there until you let go. Even initiating a spindash doesn't return things to normal.<br />
<br />
In all the games, once you let go of up or down (also in Sonic 2, 3, & K, once you initiate a spindash), the camera scrolls back to the neutral position by '''2''' pixels per step.<br />
<br />
==Spindash Lag==<br />
<br />
(Not in Sonic CD)<br />
<br />
The Player object has a previous position table in memory. The game knows where they've been over the last several seconds with frame precision. This is called upon for the spindash lag.<br />
<br />
When the spindash is launched, a timer (''t'') is set to '''32''' (minus the rev variable, which can be between '''0''' and '''8''' - see [[SPG:Special Abilities#Spindash (Sonic 2, 3, & K)|spindash]] for more details). ''t'' then decreases by '''1''' every step.<br />
<br />
While ''t'' is non-zero, the camera stops behaving normally and begins to move, not based on the Player's current position, but their position ''t''-1 steps ago. Once ''t'' runs out, the camera returns to business as usual.<br />
<br />
If the camera travels 32 steps into the past, and moves based on those recorded positions for a duration of 32 steps, it would effectively just repeat them, and then switch back to the current position, hopping 32 steps back into the future. This isn't how it works. Since it goes back 32 steps, and waits 32 more to return to normal, that means a total of 64 recorded camera positions. In order to take all of these positions into account during the 32 steps before the camera returns to normal, they are added together in pairs.<br />
<br />
As an example, let's imagine the Player has been charging up their spindash for at least 32 steps, so that they haven't moved during this time. Then, they launch at a speed of 8. Since in the last 32 steps he hasn't moved, the camera will move by 0 + 0 for 16 frames, remaining stationary. Then, it will have caught up to the point in time at which the Player launched. The Player will have been moving 8 pixels every step from this point on. The camera will then move 16 pixels for 16 more steps until ''t'' runs out. (Technically, the Player doesn't move in the exact frame in which the spindash launches, so the camera will move by 0 + 8 for one step, and then 8 + 8 for a while, and the 7 + 8 as friction kicks in, and then 7 + 7, and so on).<br />
<br />
The trouble with this lag is that if you initiate and release a spindash quickly enough after moving, the camera will actually move backward to follow where you've been.<br />
<br />
===Flame Shield/Hyper Sonic Air Dash===<br />
<br />
The same lag routine happens when Sonic does the airdash as Hyper Sonic, or with the Flame Shield. However, the trick is when he launches, the ''entire previous position table'' is blanked out with his current position, so that the camera can't scroll backward. In your engine, if you do the same thing for the launch of a spindash, you won't have to worry about backward scrolling at all.<br />
<br />
Note:<br />
*You can achieve an almost identical effect without a previous position table by simply disallowing the camera to move for about 16 steps after launching. This is somewhat easier to do.<br />
<br />
==Extended Camera (Sonic CD)==<br />
<br />
In Sonic CD only, when the Player reaches a ground speed of '''6''' (or is '''16''' steps into charging up a spindash or super peel out), the camera's X position begins to shift forward. It moves '''64''' pixels in total, '''2''' per step. When their ground speed drops back below '''6''', it moves back. The issue is, the ground speed doesn't update in mid-air, often leading to an off-centre view when jumping or running off of a ledge. <br />
<br />
If you use this shift effect in your engine, you could perhaps use the Player's actual horizontal speed instead, as it doesn't make sense to scroll horizontally when going down or up a wall, or worse, on a ceiling.<br />
<br />
[[Category:Sonic Physics Guide|Camera]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Special_Abilities&diff=323910SPG:Special Abilities2021-04-01T13:41:27Z<p>Lapper2: Moved insta-shield to Characters page</p>
<hr />
<div>==Variables==<br />
<br />
The following variables/constants will be referenced frequently in this section.<br />
<br />
<nowiki>//Variables<br />
spinrev: the amount a spindash has been charged<br />
<br />
//Constants<br />
drpspd: 8 //the base speed for a drop dash<br />
drpmax: 12 //the top speed for a drop dash<br />
drpspdsup: 12 //the base speed for a drop dash while super<br />
drpmaxsup: 13 //the top speed for a drop dash while super<br />
</nowiki><br />
<br />
==Spindash (Sonic 2, 3, & K)==<br />
<br />
When you first begin the Spindash, ''spinrev'' is set to '''0'''. With every press of the button, ''spinrev'' is increased by '''2''', up to a maximum of '''8'''. Furthermore, during the entire time the Spindash is charging, ''spinrev'' is being affected by the following calculation:<br />
<br />
<nowiki><br />
spinrev -= ((p div 0.125) / 256) // "div" is division ignoring any remainder<br />
</nowiki><br />
<br />
This is exactly like the [[SPG:Jumping#Air Drag|air drag]] calculation.<br />
<br />
What this means is that the higher ''spinrev'' is, the faster it bleeds away toward zero.<br />
<br />
When you release the Down button, the character launches forward at a speed of ''8'', plus floor(''spinrev'') - i.e. ''spinrev'' minus its fractional part (everything after the decimal point) - divided by '''2'''.<br />
<br />
<nowiki><br />
Ground Speed = 8 + (floor(spinrev) / 2) // this would be negative if the character were facing left, of course<br />
</nowiki><br />
<br />
Because the higher ''spinrev'' is, the faster it depletes, it is nearly impossible to get maximum thrust from a Spindash without the aid of a Turbo controller or frame-by-frame play. However, if you were able to, the highest speed achievable through the Spindash is '''12'''.<br />
<br />
'''Note:''' Water has no effect on the release speed.<br />
<br />
===Spindash (Sonic CD)===<br />
<br />
The Sonic CD style Spindash is much simpler. Many Sonic players may find it inferior to the Sonic 2 style, though, so you may not want to use it in your engine. Or, if you do, you might include a choice in the control options which one to use.<br />
<br />
Once the button is pressed, Sonic will begin charging the Spindash. After '''45''' steps have passed, he is ready to go. When the Down button is released, Sonic launches at a speed of '''12'''. If the Down button is released early, nothing happens.<br />
<br />
<br />
==Shield Abilities (Sonic 3 & K)==<br />
<br />
===Flame Shield===<br />
<br />
When Sonic does the Flame Shield special move, his X speed is set to '''8''' in the direction he is facing, and his Y speed is set to '''0''', regardless of their previous values.<br />
<br />
===Bubble Shield===<br />
<br />
When Sonic does the Bubble Shield bounce, his X Speed is set to '''0''', and his Y speed to '''8'''. When he rebounds from the ground, his Y Speed is set to '''-7.5'''.<br />
<br />
===Electric Shield===<br />
<br />
When Sonic does the Electric Shield jump, his X Speed is unaffected, but his Y Speed is set to '''-5.5''' regardless of its previous value.<br />
<br />
'''Note:''' All of the Shield abilities can only be performed once in the air. Sonic must connect with the ground before he can perform them again.<br />
<br />
====Ring Magnetisation====<br />
When Sonic has an Electric shield, nearby rings will become mobile and begin moving towards him in a unique way. <br />
<br />
If a ring falls within a radius of approximately 64px around Sonic's position, they will become Magnetised and begin to move. When Magnetised, the rings have an X and Y speed and do not collide with anything (except for Sonic). In order to move correctly, we have to calculate how fast to move the ring, and in what direction to accelerate. The ring accelerates at 2 different rates depending on its relative position and speed, 0.1875 when already moving towards Sonic (in order to not pass him too quickly), and 0.75 when not (in order to quickly catch back up with him).<br />
<br />
First, it does a check to see where Sonic is in relativity horizontally.<br />
<br />
If Sonic is to the left of the ring:<br />
If the ring's X Speed is less than 0, add -0.1875 to the ring's X Speed.<br />
Otherwise, add -0.75 to the ring's X Speed.<br />
<br />
<br />
If Sonic is to the right of the ring:<br />
If the ring's X Speed is greater than 0, add 0.1875 to the ring's X Speed.<br />
Otherwise, add 0.75 to the ring's X Speed.<br />
<br />
<br />
Then, the attracted ring checks Sonic's relativity vertically.<br />
<br />
If Sonic is above the ring:<br />
If the ring's Y Speed is less than 0, add -0.1875 to the ring's Y Speed.<br />
Otherwise, add -0.75 to the ring's Y Speed.<br />
<br />
<br />
If Sonic is below the ring:<br />
If the ring's Y Speed is greater than 0, add 0.1875 to the ring's Y Speed.<br />
Otherwise, add 0.75 to the ring's Y Speed.<br />
<br />
<br />
The ring's X and Y Speeds are added to the ring's X and Y Positions, moving the ring.<br />
<br />
Here's some example code for the ring while magnetised<br />
<br />
<nowiki><br />
if (magnetised)<br />
{<br />
//relative positions<br />
sx = sign(Sonic's X Position - X Position)<br />
sy = sign(Sonic's Y Position - Y Position)<br />
<br />
//check relative movement<br />
tx = (sign(Ring's X Speed) == sx)<br />
ty = (sign(Ring's Y Speed) == sy)<br />
<br />
//add to speed<br />
X Speed += (ringacceleration[tx] * sx)<br />
Y Speed += (ringacceleration[ty] * sy)<br />
//"ringacceleration" would be an array, where: [0] = 0.75 [1] = 0.1875<br />
<br />
//move<br />
X Position += X Speed<br />
Y Position += Y Speed<br />
}<br />
</nowiki><br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323909SPG:Characters2021-04-01T13:41:09Z<p>Lapper2: Add information about knuckles clambering, and moved insta-shield info to Sonic specific section</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
The sizes of characters are important, they determine how the characters will collide with Solid Tiles, Solid Objects and more. <br />
<br />
These are not hitboxes, hitboxes are separate and aren't related to the solid size of objects. Hitboxes will be covered in [[SPG:Solid Objects|Solid Objects]]<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
[[Image:SPGSonicSizes.gif|link=Special:FilePath/SPGSonicSizes.gif]]<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
==Insta-Shield==<br />
<br />
The Insta-Shield expands Sonic's hitbox giving it a width radius of 24 and a height radius of 24, resulting in an overall height of 49 x 49 (more about [[SPG:Game_Objects#Hitboxes|Hit Boxes]]). This lasts for 13 frames.<br />
<br />
The Insta-Shield does nothing to Sonic's X Speed or Y Speed. It does however allow him to control a rolling jump.<br />
<br />
<br />
==Tails==<br />
[[Image:SPGTailsSizes.gif|link=Special:FilePath/SPGTailsSizes.gif]]<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
[[Image:SPGKnucklesSizes.gif|link=Special:FilePath/SPGKnucklesSizes.gif]]<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
<br />
Well that is except for when he is gliding, climbing and sliding. <br />
<br />
[[Image:SPGKnucklesMoveSizes.gif|link=Special:FilePath/SPGKnucklesMoveSizes.gif]]<br />
<br />
Here, his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X Speed is set to '''4''' in the opposite direction of the wall, and his Y Speed is set to '''-4'''.<br />
<br />
Interestingly, because of a pixel offset when sprites are flipped, Knuckles' feet poke 1px out from the side of his size when he is on a wall to the left. This means his feet should be inside the wall a bit. Well, when on a left wall there is actually a 1px gap between knuckles X - Push Radius and the wall, purely to make it look correct.<br />
<br />
====Falling====<br />
When climbing, Knuckles will fall off the bottom of a wall if no wall is found at his Y Position + Height Radius (checking horizontally into the wall).<br />
<br />
====Clambering====<br />
Knuckles will clamber atop a ledge if it no wall is found at his Y position - Height Radius (checking horizontally into the wall). <br />
<br />
[[File:SPGKnucklesClamber.gif|link=Special:FilePath/File:SPGKnucklesClamber.gif]]<br />
<br />
When clambering, Knuckles plays a 3 sub-image animation. Each sub-image of the animation lasts 6 frames, and after the 3rd sub-image knuckles is standing on the ledge, where his X Position is the ledge X. Each frame of the animation moves knuckles to a new position as shown.<br />
<br />
His position moves back and forth a bit as he climbs so that his sprite aligns. If your camera is set up correctly, usually the first 2 sub-images of motion will push the camera forward into place and the 3rd sub-image's backward motion won't move the camera at all, which makes it look smooth enough.<br />
<br />
====Stopping at Floors and Ceilings====<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius).<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGKnucklesClamber.gif&diff=323908File:SPGKnucklesClamber.gif2021-04-01T13:37:02Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGKnucklesSizes.gif&diff=323907File:SPGKnucklesSizes.gif2021-04-01T12:00:59Z<p>Lapper2: Lapper2 uploaded a new version of File:SPGKnucklesSizes.gif</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGKnucklesMoveSizes.gif&diff=323904File:SPGKnucklesMoveSizes.gif2021-03-31T20:14:53Z<p>Lapper2: Lapper2 uploaded a new version of File:SPGKnucklesMoveSizes.gif</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=Sonic_Physics_Guide&diff=323903Sonic Physics Guide2021-03-31T19:58:07Z<p>Lapper2: /* Gameplay */</p>
<hr />
<div>ROM Hacks make the process of developing a functional Sonic game with unique art, enemies, and modifications much easier, since the game engine and basic mechanics are already functional. However, if the game requires a different game engine, modifying existing low-level assembly may be inappropriate, and some game designers might choose to program their own unique game engine. The physics of a game engine are rules that describe how to transform the Player's input (either in the form of buttons, keyboard, or even a mouse if the designer feels inclined) into appropriate changes in the position of the sprites in the game (such as the Sonic sprite, or alternatively, how enemy sprites will respond). These physics guides will hopefully make the process of simulating the rules used in Sonic games easier.<br />
<br />
Since the rules themselves are independent of how they are implemented, many people choose programming languages such as Java, C, C++, Python, or a Lisp dialect to implement game physics. In addition, people can choose to use more specialized applications like Adobe Flash (Animate), GameMaker Studio 2, or a Clickteam program like Multimedia Fusion 2.<br />
<br />
Hopefully, these guides will provide adequate information to facilitate implementation.<br />
<br />
== Physics Guides ==<br />
*[[SPG:Basics]]<br />
A prerequisite for much of the info on this guide.<br />
*[[SPG:Characters]]<br />
Basic info about characters such as their varying sizes and jump height, and also detailing how their moves work.<br />
<br />
<div class="large-3 columns"><br />
===Collision===<br />
*[[SPG:Solid Tiles]]<br />
A detailed description of how sloped terrain is constructed, and how objects use sensors to collide with it. <br />
*[[SPG:Slope Physics]]<br />
How the Player moves with momentum over angled surfaces, along with the specific physics for actions such as rolling.<br />
*[[SPG:Solid Objects]]<br />
Explaining object hitboxes, solidity, the Player's hitbox, and other ways objects directly interact with them.<br />
</div><br />
<div class="large-3 columns"><br />
===Gameplay===<br />
*[[SPG:Running]]<br />
Describing how horizontal inputs control the Player.<br />
*[[SPG:Jumping]]<br />
The Player's jump and acceleration in the air.<br />
*[[SPG:Rolling]]<br />
Physics and quirks of rolling.<br />
*[[SPG:Game Objects]]<br />
How objects such as rings, enemies, blocks, and springs move around, are constructed, and react to certain situations.<br />
*[[SPG:Main Game Loop]]<br />
The order of events for objects, including characters.<br />
</div><br />
<div class="large-3 columns"><br />
<br />
===Specific===<br />
*[[SPG:Ring Loss]]<br />
How rings disperse when hit.<br />
*[[SPG:Getting Hit]]<br />
What happens when the Player gets hit.<br />
*[[SPG:Rebound]]<br />
Describing how the Player bounces off enemies and other destroy-able items.<br />
*[[SPG:Underwater]]<br />
How Sonic's abilities change underwater.<br />
*[[SPG:Super Speeds]]<br />
How Sonic's abilities change when super.<br />
*[[SPG:Special Abilities]]<br />
Abilities such as spindashing and elemental shields.<br />
</div><br />
<div class="large-3 columns"><br />
===General===<br />
*[[SPG:Camera]]<br />
Mechanics of the camera following the Player.<br />
*[[SPG:Animations]]<br />
Covering how animations play and specific animation timings.<br />
</div><br />
[[Category:Sonic Physics Guide| ]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Slope_Physics&diff=323902SPG:Slope Physics2021-03-31T19:56:54Z<p>Lapper2: Changing "Sonic" to "the Player"</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*This guide relies on information about tiles and sensors discussed in [[SPG:Solid_Tiles|Solid Tiles]]<br />
<br />
*Following only describes how the Player collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player objects and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
Once you have the Player object able to collide with solid tiles, they need to move correctly over the terrain surface with momentum and physics. Knowing how sensors work will allow the Player move smoothly over terrain with different heights, and knowing how the Player's ground speed is affected by inputs to walk will allow him to move left and right, but that is not all there is to the engine. <br />
This guide will explain how the Player reacts to certain angles, and how 360 degree movement with momentum is achieved.<br />
<br />
==Moving At Angles==<br />
<br />
The Player's speed has to be attenuated by angled ground in order to be realistic.<br />
<br />
There are two ways in which the Player's ground speed is affected on angles. The first will make sure that they do not traverse a hill in the same amount of time as walking over flat ground of an equal width. The second will slow them down when going uphill and speed them up when going downhill. Let's look at each of these in turn.<br />
<br />
===The Three Speed Variables===<br />
<br />
If Sonic were a simple platformer that required nothing but blocks, you would only need two speed variables: X speed (X Speed) and Y speed (Y Speed), the horizontal and vertical components of the Player's velocity. Acceleration (''acc''), deceleration (''dec''), and friction (''frc'') are added to X Speed; jump/bounce velocity and gravity (''grv'') are added to Y Speed (when the Player is in the air).<br />
<br />
But when slopes are involved, while the Player moves along a slope, they're moving both horizontally and vertically. This means that both X Speed and Y Speed have a non-zero value. Simply adding ''acc'', ''dec'', or ''frc'' to X Speed no longer works; imagine the Player was trying to run up a wall - adding to their horizontal speed would be useless because they need to move upward.<br />
<br />
The trick is to employ a third speed variable (as the original engine does), Ground Speed. This is the speed of the Player along the ground, disregarding Ground Angle altogether. ''acc'', ''dec'', and ''frc'' are applied to Ground Speed, not X Speed or Y Speed.<br />
<br />
While on the ground, X Speed and Y Speed are derived from Ground Speed every step before the Player is moved. Perhaps a pseudo-code example is in order:<br />
<br />
X Speed = Ground Speed * cos(Ground Angle)<br />
Y Speed = Ground Speed * -sin(Ground Angle)<br />
<br />
X Position += X Speed<br />
Y Position += Y Speed<br />
<br />
No matter what happens to the Ground Angle, Ground Speed is preserved, so the engine always knows what speed the Player is "really" moving at.<br />
<br />
===Slope Factor===<br />
<br />
By this point, the Player should be able to handle any hills with an accurate velocity but they still need to slow down when going uphill and speed up when going downhill.<br />
<br />
Fortunately, this is simple to achieve - with something called the Slope Factor. Just subtract Slope Factor*sin(Ground Angle) from Ground Speed at the beginning of every step. <br />
<br />
Ground Speed -= Slope Factor*sin(Ground Angle);<br />
<br />
The value of Slope Factor is always ''slp'' when running, but not so when rolling. When the Player is rolling uphill (the sign of Ground Speed is equal to the sign of sin(Ground Angle)), Slope Factor is ''slprollup'' ($001E). When the Player is rolling downhill (the sign of Ground Speed is '''not''' equal to the sign of sin(Ground Angle)), Slope Factor is ''slprolldown'' ($0050).<br />
<br />
'''Note:''' In Sonic 1, it appears that Slope Factor doesn't get added if the Player is stopped and in his standing/waiting cycle. But in Sonic 3 & Knuckles, Slope Factor seems to be added even then, so that the Player can't stand on steep slopes - it will force them to walk down.<br />
<br />
==Jumping At Angles==<br />
<br />
Jumping is also affected by the angle the Player is at when they do it. It can't simply set Y Speed to negative ''jmp'' - he needs to jump away from the Ground Angle they're standing on. Instead, both X Speed and Y Speed must have ''jmp'' subtracted from them, using cos() and sin() to get the right values.<br />
<br />
More pseudo-code:<br />
<br />
X Speed -= jmp * sin(Ground Angle)<br />
Y Speed -= jmp * cos(Ground Angle)<br />
<br />
Notice how the jump values are subtracted from the X Speed and Y Speed. 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.<br />
<br />
==Switching Mode==<br />
<br />
So the Player can run over hills and ramps and ledges, and all that is great. But it is ''still'' not enough. They cannot make their way from the ground to walls and ceilings without more work.<br />
<br />
Why not? Well, because sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> 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.<br />
<br />
How can we solve this? By using four different modes of movement. This will take a little explaining.<br />
<br />
===The Four Modes===<br />
<br />
It seems pretty reasonable to assume that, because the Player 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.<br />
<br />
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 Ground Speed, but nothing more. The character would still always move horizontally and move straight up and down to adhere to floor level.<br />
<br />
This is much like how the Sonic games do things. Only, when Ground Angle gets too steep, the Player 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, the Player behaves like a simpler platformer. The magic happens by combining all four modes, and cleverly switching between them smoothly.<br />
<br />
So how and when does the Player switch mode?<br />
<br />
When in Floor mode, and Ground Angle 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 the Player is moved to "floor" level horizontally instead of vertically.<br />
<br />
Now that they're in Right Wall mode, if Ground Angle is shallower than 45° ($E0), the engine switches back into Floor mode.<br />
<br />
The other transitions work in exactly the same way, with the switch angles relative to the current mode.<br />
<br />
When the mode is being calculated, it simply checks which quadrant the Player's Ground Angle is currently in, which will place the Player in the correct mode (ranges are inclusive):<br />
<br />
Floor Mode (start of rotation)<br />
0° to 45° (1~32) ($FF~$E0)<br />
<br />
Right Wall Mode<br />
46° to 134° (33~95) ($DF~$A1)<br />
<br />
Ceiling Mode<br />
135° to 225° (96~160) ($A0~$60) <br />
<br />
Left Wall Mode<br />
226° to 314° (161~223) ($5F~$21)<br />
<br />
Floor Mode (end of rotation)<br />
315° to 360° (224~256) ($20~$00)<br />
<br />
''Note: Since the classic games don't use degrees, and rather have angles ranging from 0 to 256, both approximate degree values and a more accurate decimal representation of the Hex values are included.''<br />
<br />
These ranges are symmetrical for left and right, but does favour the floor and ceiling modes, with their ranges being a degree or two wider.<br />
<br />
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 <span style="color:#00f000; font-weight: bold;">A</span> is now at the Player's Y Position + Width Radius instead of X Position - width Radius. Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> is now at the Player's Y Position - Width Radius, instead of X Position + Width Radius. Instead of downward vertical sensor, they are now horizontal facing left, at his foot level (which is now "below" them, at X Position + Width Radius). You they move and rotate in the same way for the other modes.<br />
<br />
Yes, because the sensors move so far, it is possible for the Player 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. <br />
<br />
*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 rectangle. 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 the Player doesn't begin walking on a wall when they get near one, but would mean the Player switched mode sooner on a slope which means less "popping".<br />
<br />
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.<br />
<br />
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, the Player knows that the height value found should be used to move them down and not up.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
With these four modes, the Player can go over all sorts of shapes. Inner curves, outer curves, you name them. Here are some approximate example images with their angle values to help give you some idea of what this results in:<br />
<br />
[[Image:SPGInnerCurve.PNG|link=Special:FilePath/SPGInnerCurve.PNG]] [[Image:SPGInnerCurveChart.PNG|link=Special:FilePath/SPGInnerCurveChart.PNG]]<br />
<br />
You can observe Sonic's mode changing on the frame after his floor angle (Ground Angle) exceeds 45°. Sonic's position shifts a bit when the change occurs, due to the totally new collision angle and position.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
[[Image:SPGOuterCurve.PNG|link=Special:FilePath/SPGOuterCurve.PNG]] [[Image:SPGOuterCurveChart.PNG|link=Special:FilePath/SPGOuterCurveChart.PNG]]<br />
<br />
You may notice the Player's mode switches erratically on the convex curve, this is because his floor angle (Ground Angle) will suddenly decrease when switching to wall mode, causing it to switch back and forth until he is far enough down the curve to stabilise. This isn't usually noticeable, and happens less the faster you are moving.<br />
</div><br />
</div><br />
<br />
Note: The reason the gifs show the mode switch being the frame ''after'' the angle threshold is reached is simply because the collision being shown is the one used for ''that'' frame, ''before'' the Player's Ground Angle updates, but after they have moved. <br />
<br />
===When to Change Mode===<br />
<br />
If you've checked the guide regarding the [[SPG:Main_Game_Loop|Main Game Loop]] you may notice the mode switching isn't mentioned at all, that's because the game doesn't actually ever "switch" the Player's mode. The Player's current "mode" is decided right before collision occurs. It will measure his angle, and decide which mode of collision to use right there and then. There is no "Mode" state stored in memory. So effectively, the Player's mode updates whenever his angle (Ground Angle) does. <br />
<br />
Since the floor angle (Ground Angle) is decided ''after'' floor collision (as a result of floor collision) the floor collision that frame has to use the previous frames angle, even though the Player has moved to a new part of the slope since then. This results in the Player's mode effectively changing 1 frame ''after'' the Player reaches one of the 45 degree angle thresholds, as seen above.<br />
<br />
===Falling and Sliding Off Of Walls And Ceilings===<br />
<br />
When in Right Wall, Left Wall, or Ceiling mode and the Player's Ground Angle is between 90 and 270, they will fall any time absolute Ground Speed falls below ''fall'' ($0280) (Ground Speed is set to 0 at this time, but X Speed and Y Speed are unaffected, so the Player will continue their trajectory through the air). This happens even if there is ground beneath them. If the Player is in Right Wall, Left Wall, or Ceiling Mode but their Ground Angle is not between 90 and 270 then the horizontal control lock timer described below will still be set to 30 but the Player will not enter a falling state remaining in their current state.<br />
<br />
====Horizontal Control Lock====<br />
<br />
When the Player falls or slides off in the manner described above, the [[SPG:Springs and Things#Horizontal Control Lock|horizontal control lock]] timer is set to 30 ($1E) (it won't begin to count down until the Player lands back on the ground). While this timer is non-zero and the Player is on the ground, it prevents directional input from adjusting the Player'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 the Player fell back on the ground with is in effect, so the Player will slip back down the slope.<br />
<br />
if abs(Ground Speed) < 2.5 and (angle >= 45 and angle <= 315)<br />
{<br />
if angle >= 90 and angle <= 270<br />
{<br />
floor_mode = 0<br />
Ground Speed = 0<br />
}<br />
horizontal_lock_timer = 30<br />
}<br />
<br />
==The Air State==<br />
<br />
Any time the Player is in the air, they don't have to worry about angles, Ground Speed, ''slp'', or any of that jazz. All they have to do is move using X Speed and Y Speed until they detect the ground, at which point they re-enter the ground state.<br />
<br />
===Jumping "Through" Floors===<br />
<br />
There are some ledges that the Player can jump up "through". These are often in the hilly, green zones such as [[Green Hill Zone (Sonic the Hedgehog 16-bit)|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 the Player's <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sensors. They are ignored entirely by C and D as well as the wall sensors E and F. Finally, sensor <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> (mostly) only detect the floor when the Player is moving downwards (but always while on the ground). So with a slightly shorter jump, you will see the Player 'pop' upwards onto a jump through surface once they begin to fall.<br />
<br />
===Reacquisition Of The Ground===<br />
Both X Speed and Y Speed are derived from Ground Speed while the Player is on the ground. When they fall or otherwise leave the ground, X Speed and Y Speed are already the proper values for him to continue his trajectory through the air. But when they land back on the ground, Ground Speed must be calculated from the X Speed and Y Speed that they have when it happens.<br />
You might think that the game 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.<br />
<br />
As you land the angle of the ground you touch is read (Ground Angle).<br />
The following covers the angle (Ground Angle) of the ground (floor or ceiling) that the Player touches as they land, and only happens the frame when they land when changing from in air to on ground.<br />
<br />
''Note: Since the classic games don't use degrees, and rather have angles ranging from 0 to 256, both approximate degree values and a more accurate (and inverted) decimal representation of the Hex values are included.''<br />
<br />
====When Falling Downward====<br />
[[Image:SPGLandFloor.png|link=Special:FilePath/SPGLandFloor.png]]<br />
<br />
The following ranges are inclusive.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-4 columns" style="padding:0px"><br />
'''Shallow:'''<br />
When Ground Angle is in the range of <br />
<br />
0° to 23° (1~16) ($FF~$F0) <br />
and mirrored:<br />
339° to 360° (241~256) ($0F~$00)<br />
<br />
Ground Speed is set to the value of X Speed.<br />
</div><br />
<div class="large-4 columns"><br />
'''Half Steep:'''<br />
When Ground Angle is in the range of <br />
<br />
24° to 45° (17~32) ($EF~$E0) <br />
and mirrored:<br />
316° to 338° (225~240) ($1F~$10)<br />
<br />
Ground Speed is set to X Speed but only if the absolute of X Speed is greater than Y Speed. Otherwise, Ground Speed is set to Y Speed*0.5*-sign(sin(Ground Angle)). <br />
</div><br />
<div class="large-4 columns"><br />
'''Full Steep:'''<br />
When Ground Angle is in the range of <br />
<br />
46° to 90° (33~64) ($DF~$C0) <br />
and mirrored:<br />
271° to 315° (193~224) ($3F~$20)<br />
<br />
Ground Speed is set to X Speed but only if the absolute of X Speed is greater than Y Speed. Otherwise, Ground Speed is set to Y Speed*-sign(sin(Ground Angle)).<br />
</div><br />
</div><br />
<br />
====When Going Upward====<br />
[[Image:SPGLandCeiling.png|link=Special:FilePath/SPGLandCeiling.png]]<br />
<br />
The following ranges are inclusive.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Slope:'''<br />
When the ceiling Ground Angle detected is in the range of <br />
<br />
91° to 135° (65~96) ($BF~$A0) <br />
and mirrored <br />
226° to 270° (161~192) ($5F~$40)<br />
<br />
The Player reattaches to the ceiling and Ground Speed is set to Y Speed*-sign(sin(Ground Angle)).<br />
</div><br />
<div class="large-6 columns"><br />
'''Ceiling:'''<br />
When the ceiling Ground Angle is in the range of <br />
<br />
136° to 225° (97~160) ($9F~$60)<br />
<br />
The Player hits his head like with any ceiling, and doesn't reattach to it. Y Speed is set to 0, and X Speed is unaffected.<br />
</div><br />
</div><br />
<br />
===Air Rotation===<br />
When the Player leaves a slope, such as running up and off a quarter pipe, the Players's Ground Angle smoothly returns to 0. <br />
<br />
The Player's Ground Angle changes by <br />
2.8125° (2) ($2)<br />
each frame, in the direction towards 0.<br />
<br />
<br />
*''Note: Degree angle is approximate, as the original game has angles ranging up to 256. Degree, decimal, and hex have been provided.''<br />
*''Note: Regardless of the Player's Ground Angle, their airborne sensors do not rotate. Air collision essentially ignores the Player's mode.''<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=Sonic_Physics_Guide&diff=323901Sonic Physics Guide2021-03-31T19:32:51Z<p>Lapper2: Changing "Sonic" to "the Player"</p>
<hr />
<div>ROM Hacks make the process of developing a functional Sonic game with unique art, enemies, and modifications much easier, since the game engine and basic mechanics are already functional. However, if the game requires a different game engine, modifying existing low-level assembly may be inappropriate, and some game designers might choose to program their own unique game engine. The physics of a game engine are rules that describe how to transform the Player's input (either in the form of buttons, keyboard, or even a mouse if the designer feels inclined) into appropriate changes in the position of the sprites in the game (such as the Sonic sprite, or alternatively, how enemy sprites will respond). These physics guides will hopefully make the process of simulating the rules used in Sonic games easier.<br />
<br />
Since the rules themselves are independent of how they are implemented, many people choose programming languages such as Java, C, C++, Python, or a Lisp dialect to implement game physics. In addition, people can choose to use more specialized applications like Adobe Flash (Animate), GameMaker Studio 2, or a Clickteam program like Multimedia Fusion 2.<br />
<br />
Hopefully, these guides will provide adequate information to facilitate implementation.<br />
<br />
== Physics Guides ==<br />
*[[SPG:Basics]]<br />
A prerequisite for much of the info on this guide.<br />
*[[SPG:Characters]]<br />
Basic info about characters such as their varying sizes and jump height, and also detailing how their moves work.<br />
<br />
<div class="large-3 columns"><br />
===Collision===<br />
*[[SPG:Solid Tiles]]<br />
A detailed description of how sloped terrain is constructed, and how objects use sensors to collide with it. <br />
*[[SPG:Slope Physics]]<br />
How the Player moves with momentum over angled surfaces, along with the specific physics for actions such as rolling.<br />
*[[SPG:Solid Objects]]<br />
Explaining object hitboxes, solidity, the Player's hitbox, and other ways objects directly interact with them.<br />
</div><br />
<div class="large-3 columns"><br />
===Gameplay===<br />
*[[SPG:Running]]<br />
Describing how horizontal inputs control the Player.<br />
*[[SPG:Jumping]]<br />
Sonic's jump and acceleration in the air.<br />
*[[SPG:Rolling]]<br />
Physics and quirks of rolling.<br />
*[[SPG:Game Objects]]<br />
How objects such as rings, enemies, blocks, and springs move around, are constructed, and react to certain situations.<br />
*[[SPG:Main Game Loop]]<br />
The order of events for objects, including characters.<br />
</div><br />
<div class="large-3 columns"><br />
===Specific===<br />
*[[SPG:Ring Loss]]<br />
How rings disperse when hit.<br />
*[[SPG:Getting Hit]]<br />
What happens when the Player gets hit.<br />
*[[SPG:Rebound]]<br />
Describing how the Player bounces off enemies and other destroy-able items.<br />
*[[SPG:Underwater]]<br />
How Sonic's abilities change underwater.<br />
*[[SPG:Super Speeds]]<br />
How Sonic's abilities change when super.<br />
*[[SPG:Special Abilities]]<br />
Abilities such as spindashing and elemental shields.<br />
</div><br />
<div class="large-3 columns"><br />
===General===<br />
*[[SPG:Camera]]<br />
Mechanics of the camera following the Player.<br />
*[[SPG:Animations]]<br />
Covering how animations play and specific animation timings.<br />
</div><br />
[[Category:Sonic Physics Guide| ]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323900SPG:Solid Tiles2021-03-31T19:28:14Z<p>Lapper2: /* Introduction */</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how the Player object collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for the Player and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of the Player's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
the Player's collisions and interactions with these solid tiles are what make up their basic engine. They dictate how they handles floors, walls, ceilings, slopes, and loops. <br />
<br />
First we will look at how the environment is constructed from tiles, and then the Player's method for detecting their environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of the Player's sensors.''<br />
<br />
In this example, the sensor to the Player's mid right points right, and those at the Player's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking nearby tiles also, until certain conditions are met. <br />
<br />
In the case of a sensor which is pointing down looking for solids below:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
*If the '''anchor point''' finds a Solid Tile, and if the height array value at the sensor's X of that tile is 16 (meaning the tile is completely filled in that area), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
**If a regression occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
*If the '''anchor point''' just finds an empty tile (height array value of 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
**If an extension occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
</div><br />
If the extension/regression does not fail, the new tile is the one which is processed, otherwise the first tile is processed instead.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
To reiterate, when the sensor is within a tile which has a height array value of 0 at the sensor's x position (empty) it will check another tile in the sensor direction (extension). If the tile's height value is between 0 and 16 (not inclusive), the surface of the terrain has been found without needing to check extra tiles. If the tile's height value is 16, it will check another tile opposite to the sensor direction (regression).<br />
If the regression still fails to find a tile with a surface within, it will still return the information of the second tile. If the extension fails to find any solid tile, the sensor will return a distance of 0.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another). <br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including the Player, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for the Player.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, the Player will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, the Player will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over the Player while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==The Player's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround the Player. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - the Player's X Position and Y Position<br />
<br />
<br />
Since the Player's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. The Player has separate radius values for their <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (their '''Push Radius''') which always remains the same, and for their <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 and Height Radius both of which will change depending on the Player's state. For these sizes see [[SPG:Characters|Characters]].<br />
<br />
Note on sprite alignment:<br />
* The Player's sprite is 1 pixel offset to the left when they faces left, which can result in them appearing to be 1px inside a tile when pushing leftwards. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at their feet at Y Position + Height Radius.<br />
<br />
====Movement====<br />
These sensors are <span style="color:#00f000; font-weight: bold;">A</span> on the Player's left side, at X Position - Width Radius, Y Position + Y Radius. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on their right, at X Position + Width Radius, Y Position + Height Radius.<br />
<br />
These radius values change depending on the character and action (see [[SPG:Characters|Characters]]).<br />
<br />
====Method====<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition the Player. The result is the Player will stand atop the floor at the floor's surface Y level - (the Player's Height Radius + 1)<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If the Player's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want them to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, the Player won't collide.<br />
In Sonic 2 onward however the positive limit depends on the Player's current speed - in this case, (for when the Player is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then they won't collide. So the faster the Player moves, the greater the distance the Player can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If the Player was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) the Player won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
The Player 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 them.<br />
<br />
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, the Player will "fall" - a flag will be set telling the engine they is now in the air.<br />
<br />
=====Balancing On Edges=====<br />
<br />
One nice touch is that the Player goes into a balancing animation when near to the edge of a ledge. This only happens when they is stopped (their Ground Speed is 0).<br />
<br />
How does the engine know? It is simple - any time only one of the ground sensors is activated, the Player must be near a ledge. If <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to their right and vice versa.<br />
<br />
But if the Player began balancing the instant one of the sensors found nothing, they would do it too "early", and it would look silly. So it only happens when only one sensor is active, and X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), the Player will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). they'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than they is facing, they has a second balancing animation.<br />
<br />
In ''[[Sonic 2]]'', ''[[Sonic 3]]'', and ''[[Sonic & Knuckles]]'', the Player has yet a ''third'' balancing animation, for when they's even further out on the ledge. Assuming the same values as above, this would start when they is at an X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, etc). In the Player 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
the Player'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 the Player's floor sensors, they have the same X positions but are flipped upside down and face upwards. They perform in the exact same way, competing against eachother, simply up instead of down.<br />
<br />
However, they aren't active at the same times as the floor sensors, only while airborne.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from the Player's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at their left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at their right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to the Player's left side, at X Position - 10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to their right, at X Position + 10, giving the Player a total width of 21 pixels when pushing.<br />
<br />
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 the Player's Y Position however while the Player's Ground Angle is 0 (on totally flat ground) both wall sensors will move to their Y Position + 8 so that they can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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 the Player is 21 pixels wide when pushing into walls. This means that the Player is skinnier by 2 pixels when running off of ledges than when bumping into walls.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before the Player's position physically moves anywhere, so they wont actually be in a wall when they tries to collide with it. The game accounts for this by actually adding their X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if the Player had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), the Player cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), the Player cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from the Player's X Position-10 to X Position+10. <br />
<br />
When the Player collides with a wall, this will set their Ground Speed to 0 if they is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop the Player at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because they will not collide. If the sensor's distance is negative, this means that when the Player's position actually does change, they will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of the Player (where they will be after they moves) instead of using the distance to reposition the Player by directly changing their position, the game smartly uses the fact that the Player has still yet to move within the current frame. All it has to do is add the distance to the Player's X Speed (if moving right, or ''subtract'' the distance from the Player's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in the Player moving when their position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 the Player will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, they will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
the Player cannot jump when there is a low ceiling above them. If there is a collision detected with sensors at the Player's X Position-9 and X Position+9, at Y Position-25, the Player won't bother jumping at all.<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will actively search for new floor below the Player's feet. <br />
<br />
When grounded, Wall Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when the Player is walking in that direction. For example, while standing still the Player isn't checking with their wall sensors at all, but while Ground Speed is positive, the Player's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when the Player's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when the Player's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and the Player won't check for collision with solid tiles above themself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach the Player to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
the Player is going mostly right<br />
else<br />
the Player is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
the Player is going mostly down<br />
else<br />
the Player is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors are pushing the Player 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
If the Player stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes the Player to be set to the wrong position.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
The Player raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as they moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, the Player defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then they raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as they moves further right. So they will move up, drop down, and move up again as they runs off the ledge.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (the Player the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but the Player 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 them to dip down in the middle by about 1 pixel.<br />
<br />
But that is not all. Because the highest sensor is the one the Player gets the angle from, even though it looks like they should be considered to be at the angle of the ramp on the right (because they is closer to it), they will still have the angle of the ramp on the left. When you jump, they will jump at that angle, moving backward, not forward like you would expect.<br />
<br />
==Notes==<br />
* Find information on how the Player's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323899SPG:Solid Tiles2021-03-31T19:25:58Z<p>Lapper2: This replaces specifying "Sonic" with a general "the Player". Also removes sonic specific info. (Submitted prematurely, some corrections here)</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how the Player object collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for the Player and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of the Player's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
What are solid tiles? While there are often solid objects in the Player 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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
the Player's collisions and interactions with these solid tiles are what make up their basic engine. They dictate how they handles floors, walls, ceilings, slopes, and loops. <br />
<br />
First we will look at how the environment is constructed from tiles, and then the Player's method for detecting their environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of the Player's sensors.''<br />
<br />
In this example, the sensor to the Player's mid right points right, and those at the Player's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking nearby tiles also, until certain conditions are met. <br />
<br />
In the case of a sensor which is pointing down looking for solids below:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
*If the '''anchor point''' finds a Solid Tile, and if the height array value at the sensor's X of that tile is 16 (meaning the tile is completely filled in that area), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
**If a regression occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
*If the '''anchor point''' just finds an empty tile (height array value of 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
**If an extension occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
</div><br />
If the extension/regression does not fail, the new tile is the one which is processed, otherwise the first tile is processed instead.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
To reiterate, when the sensor is within a tile which has a height array value of 0 at the sensor's x position (empty) it will check another tile in the sensor direction (extension). If the tile's height value is between 0 and 16 (not inclusive), the surface of the terrain has been found without needing to check extra tiles. If the tile's height value is 16, it will check another tile opposite to the sensor direction (regression).<br />
If the regression still fails to find a tile with a surface within, it will still return the information of the second tile. If the extension fails to find any solid tile, the sensor will return a distance of 0.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another). <br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including the Player, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for the Player.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, the Player will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, the Player will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over the Player while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==The Player's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround the Player. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - the Player's X Position and Y Position<br />
<br />
<br />
Since the Player's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. The Player has separate radius values for their <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (their '''Push Radius''') which always remains the same, and for their <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 and Height Radius both of which will change depending on the Player's state. For these sizes see [[SPG:Characters|Characters]].<br />
<br />
Note on sprite alignment:<br />
* The Player's sprite is 1 pixel offset to the left when they faces left, which can result in them appearing to be 1px inside a tile when pushing leftwards. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at their feet at Y Position + Height Radius.<br />
<br />
====Movement====<br />
These sensors are <span style="color:#00f000; font-weight: bold;">A</span> on the Player's left side, at X Position - Width Radius, Y Position + Y Radius. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on their right, at X Position + Width Radius, Y Position + Height Radius.<br />
<br />
These radius values change depending on the character and action (see [[SPG:Characters|Characters]]).<br />
<br />
====Method====<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition the Player. The result is the Player will stand atop the floor at the floor's surface Y level - (the Player's Height Radius + 1)<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If the Player's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want them to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, the Player won't collide.<br />
In Sonic 2 onward however the positive limit depends on the Player's current speed - in this case, (for when the Player is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then they won't collide. So the faster the Player moves, the greater the distance the Player can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If the Player was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) the Player won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
The Player 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 them.<br />
<br />
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, the Player will "fall" - a flag will be set telling the engine they is now in the air.<br />
<br />
=====Balancing On Edges=====<br />
<br />
One nice touch is that the Player goes into a balancing animation when near to the edge of a ledge. This only happens when they is stopped (their Ground Speed is 0).<br />
<br />
How does the engine know? It is simple - any time only one of the ground sensors is activated, the Player must be near a ledge. If <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to their right and vice versa.<br />
<br />
But if the Player began balancing the instant one of the sensors found nothing, they would do it too "early", and it would look silly. So it only happens when only one sensor is active, and X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), the Player will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). they'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than they is facing, they has a second balancing animation.<br />
<br />
In ''[[Sonic 2]]'', ''[[Sonic 3]]'', and ''[[Sonic & Knuckles]]'', the Player has yet a ''third'' balancing animation, for when they's even further out on the ledge. Assuming the same values as above, this would start when they is at an X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, etc). In the Player 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
the Player'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 the Player's floor sensors, they have the same X positions but are flipped upside down and face upwards. They perform in the exact same way, competing against eachother, simply up instead of down.<br />
<br />
However, they aren't active at the same times as the floor sensors, only while airborne.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from the Player's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at their left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at their right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to the Player's left side, at X Position - 10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to their right, at X Position + 10, giving the Player a total width of 21 pixels when pushing.<br />
<br />
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 the Player's Y Position however while the Player's Ground Angle is 0 (on totally flat ground) both wall sensors will move to their Y Position + 8 so that they can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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 the Player is 21 pixels wide when pushing into walls. This means that the Player is skinnier by 2 pixels when running off of ledges than when bumping into walls.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before the Player's position physically moves anywhere, so they wont actually be in a wall when they tries to collide with it. The game accounts for this by actually adding their X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if the Player had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), the Player cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), the Player cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from the Player's X Position-10 to X Position+10. <br />
<br />
When the Player collides with a wall, this will set their Ground Speed to 0 if they is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop the Player at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because they will not collide. If the sensor's distance is negative, this means that when the Player's position actually does change, they will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of the Player (where they will be after they moves) instead of using the distance to reposition the Player by directly changing their position, the game smartly uses the fact that the Player has still yet to move within the current frame. All it has to do is add the distance to the Player's X Speed (if moving right, or ''subtract'' the distance from the Player's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in the Player moving when their position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 the Player will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, they will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
the Player cannot jump when there is a low ceiling above them. If there is a collision detected with sensors at the Player's X Position-9 and X Position+9, at Y Position-25, the Player won't bother jumping at all.<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will actively search for new floor below the Player's feet. <br />
<br />
When grounded, Wall Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when the Player is walking in that direction. For example, while standing still the Player isn't checking with their wall sensors at all, but while Ground Speed is positive, the Player's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when the Player's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when the Player's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and the Player won't check for collision with solid tiles above themself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach the Player to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
the Player is going mostly right<br />
else<br />
the Player is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
the Player is going mostly down<br />
else<br />
the Player is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors are pushing the Player 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
If the Player stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes the Player to be set to the wrong position.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
The Player raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as they moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, the Player defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then they raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as they moves further right. So they will move up, drop down, and move up again as they runs off the ledge.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (the Player the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but the Player 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 them to dip down in the middle by about 1 pixel.<br />
<br />
But that is not all. Because the highest sensor is the one the Player gets the angle from, even though it looks like they should be considered to be at the angle of the ramp on the right (because they is closer to it), they will still have the angle of the ramp on the left. When you jump, they will jump at that angle, moving backward, not forward like you would expect.<br />
<br />
==Notes==<br />
* Find information on how the Player's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323898SPG:Solid Tiles2021-03-31T18:58:51Z<p>Lapper2: </p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how the Player object collides and interacts with solid tiles. Solid objects, such as [[Monitor|Monitors]], Moving Platforms, and Blocks each have their own collision routines with the Player and don't necessarily behave exactly the same as the tiles do. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for the Player and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of the Player's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
What are solid tiles? While there are often solid objects in the Player 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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
the Player's collisions and interactions with these solid tiles are what make up their basic engine. They dictate how they handles floors, walls, ceilings, slopes, and loops. <br />
<br />
First we will look at how the environment is constructed from tiles, and then the Player's method for detecting their environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of the Player's sensors.''<br />
<br />
In this example, the sensor to the Player's mid right points right, and those at the Player's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking nearby tiles also, until certain conditions are met. <br />
<br />
In the case of a sensor which is pointing down looking for solids below:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
*If the '''anchor point''' finds a Solid Tile, and if the height array value at the sensor's X of that tile is 16 (meaning the tile is completely filled in that area), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
**If a regression occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
*If the '''anchor point''' just finds an empty tile (height array value of 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
**If an extension occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
</div><br />
If the extension/regression does not fail, the new tile is the one which is processed, otherwise the first tile is processed instead.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
To reiterate, when the sensor is within a tile which has a height array value of 0 at the sensor's x position (empty) it will check another tile in the sensor direction (extension). If the tile's height value is between 0 and 16 (not inclusive), the surface of the terrain has been found without needing to check extra tiles. If the tile's height value is 16, it will check another tile opposite to the sensor direction (regression).<br />
If the regression still fails to find a tile with a surface within, it will still return the information of the second tile. If the extension fails to find any solid tile, the sensor will return a distance of 0.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another). <br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including the Player, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for the Player.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, the Player will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, the Player will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over the Player while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==The Player's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround the Player. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - the Player's X Position and Y Position<br />
<br />
<br />
Since the Player's collision setup is symmetrical, it makes sense for the game to set up widths and heights using radius values. The Player has separate radius values for their <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> sensor pair (their '''Push Radius''') which always remains the same, and for their <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 and Height Radius both of which will change depending on the Player's state. For these sizes see [[SPG:Characters|Characters]].<br />
<br />
Note on sprite alignment:<br />
* The Player's sprite is 1 pixel offset to the left when they faces left, which can result in him appearing to be 1px inside a tile when pushing leftwards. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at their feet at Y Position+Height Radius.<br />
<br />
====Movement====<br />
Typically, the Player's Width Radius is 9, placing <span style="color:#00f000; font-weight: bold;">A</span> on the Player's left side, at X Position-9. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on their right, at X Position+9, 19 pixels wide.<br />
their Height Radius is usually 19, making him 39 pixels tall in total.<br />
<br />
However while rolling or jumping, their Width Radius becomes 7, placing <span style="color:#00f000; font-weight: bold;">A</span> at X Position-7. With <span style="color:#38ffa2; font-weight: bold;">B</span> at X Position+7, 15 pixels wide. This is to ensure the Player doesn't appear too far away from the floor at steep angles while in curled up. Crouching does not affect their Width Radius.<br />
<br />
Here's an example of that in action:<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
While rolling or jumping (and otherwise generally curled up), the Player's Height Radius becomes smaller at a value of 14, making him 29 pixels tall. Because of this, in the step in which the Player rolls or jumps or otherwise becomes shorter, the game adds 5 to their Y Position so that their bottom point will remain unchanged despite him getting shorter and their center changing position. 5 also has to be subtracted from Y Position when they unroll or lands from a jump. The camera system also has to keep this offset in mind, otherwise, the view will jump when the Player changes height.<br />
<br />
====Method====<br />
<br />
Assuming the ground level to be at a Y position of 736 ($02E0), while standing the Player is atop it at a Y Position of 716 ($02CC), which is 20 pixels above ground level.<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition the Player.<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If the Player's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want him to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, the Player won't collide.<br />
In Sonic 2 onward however the positive limit depends on the Player's current speed - in this case, (for when the Player is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then they won't collide. So the faster the Player moves, the greater the distance the Player can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If the Player was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) the Player won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
the Player 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.<br />
<br />
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, the Player will "fall" - a flag will be set telling the engine they is now in the air.<br />
<br />
=====Balancing On Edges=====<br />
<br />
One nice touch is that the Player goes into a balancing animation when near to the edge of a ledge. This only happens when they is stopped (their Ground Speed is 0).<br />
<br />
How does the engine know? It is simple - any time only one of the ground sensors is activated, the Player must be near a ledge. If <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to their right and vice versa.<br />
<br />
But if the Player began balancing the instant one of the sensors found nothing, they would do it too "early", and it would look silly. So it only happens when only one sensor is active, and X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), the Player will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). they'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than they is facing, they has a second balancing animation.<br />
<br />
In ''[[Sonic 2]]'', ''[[Sonic 3]]'', and ''[[Sonic & Knuckles]]'', the Player has yet a ''third'' balancing animation, for when they's even further out on the ledge. Assuming the same values as above, this would start when they is at an X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, etc). In the Player 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
the Player'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 the Player's floor sensors, they have the same X positions and length but are flipped upside down. <br />
<br />
They perform in the exact same way simply up instead of down.<br />
<br />
However, they aren't active at the same times.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from the Player's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at their left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at their right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to the Player's left side, at X Position-10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to their right, at X Position+10, giving the Player a total width of 21 pixels when pushing.<br />
<br />
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 the Player's Y Position however while the Player's Ground Angle is 0 (on totally flat ground) both wall sensors will move to their Y Position+8 so that they can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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 the Player is 21 pixels wide when pushing into walls. This means that the Player is skinnier by 2 pixels when running off of ledges than when bumping into walls.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before the Player's position physically moves anywhere, so they wont actually be in a wall when they tries to collide with it. The game accounts for this by actually adding their X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if the Player had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), the Player cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), the Player cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from the Player's X Position-10 to X Position+10. <br />
<br />
When the Player collides with a wall, this will set their Ground Speed to 0 if they is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop the Player at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because they will not collide. If the sensor's distance is negative, this means that when the Player's position actually does change, they will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of the Player (where they will be after they moves) instead of using the distance to reposition the Player by directly changing their position, the game smartly uses the fact that the Player has still yet to move within the current frame. All it has to do is add the distance to the Player's X Speed (if moving right, or ''subtract'' the distance from the Player's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in the Player moving when their position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 the Player will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, they will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
the Player cannot jump when there is a low ceiling above him. If there is a collision detected with sensors at the Player's X Position-9 and X Position+9, at Y Position-25, the Player won't bother jumping at all.<br />
<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will use their 1 tile extension to actively search for new floor below the Player's feet. <br />
<br />
When grounded, Wall Sensors <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> only activate when the Player is walking in that direction. For example, while standing still the Player isn't checking with their wall sensors at all, but while Ground Speed is positive, the Player's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when the Player's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when the Player's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and the Player won't check for collision with solid tiles above himself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach the Player to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
the Player is going mostly right<br />
else<br />
the Player is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
the Player is going mostly down<br />
else<br />
the Player is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', the Player's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors are pushing the Player 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
If the Player stands on a slanted ledge, one sensor will find no tile and return a height of foot level. This causes the Player to be set to the wrong position.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
the Player raises up with sensor <span style="color:#38ffa2; font-weight: bold;">B</span> sensor as they moves right. When <span style="color:#38ffa2; font-weight: bold;">B</span> drops off the ledge, the Player defaults to the level of sensor <span style="color:#00f000; font-weight: bold;">A</span>. Then they raises up with sensor <span style="color:#00f000; font-weight: bold;">A</span> as they moves further right. So they will move up, drop down, and move up again as they runs off the ledge.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (the Player the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
Sensor <span style="color:#38ffa2; font-weight: bold;">B</span> starts climbing down the ramp on the right, but the Player 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.<br />
<br />
But that is not all. Because the highest sensor is the one the Player gets the angle from, even though it looks like they should be considered to be at the angle of the ramp on the right (because they is closer to it), they will still have the angle of the ramp on the left. When you jump, they will jump at that angle, moving backward, not forward like you would expect.<br />
<br />
==Notes==<br />
* Find information on how the Player's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323897SPG:Characters2021-03-31T16:26:21Z<p>Lapper2: Added images for each character's size</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
The sizes of characters are important, they determine how the characters will collide with Solid Tiles, Solid Objects and more. <br />
<br />
These are not hitboxes, hitboxes are separate and aren't related to the solid size of objects. Hitboxes will be covered in [[SPG:Solid Objects|Solid Objects]]<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
[[Image:SPGSonicSizes.gif|link=Special:FilePath/SPGSonicSizes.gif]]<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
<br />
==Tails==<br />
[[Image:SPGTailsSizes.gif|link=Special:FilePath/SPGTailsSizes.gif]]<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
[[Image:SPGKnucklesSizes.gif|link=Special:FilePath/SPGKnucklesSizes.gif]]<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
<br />
Well that is except for when he is gliding, climbing and sliding. <br />
<br />
[[Image:SPGKnucklesMoveSizes.gif|link=Special:FilePath/SPGKnucklesMoveSizes.gif]]<br />
<br />
Here, his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
When climbing, Knuckles will fall off the bottom of a wall if it is within 10px of his Y Position, and climb atop a ledge if it too is within 10px of his Y position. This 10px is just his Height Radius at the time.<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius)<br />
<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X speed is set to '''4''' in the opposite direction of the wall, and his Y speed is set to '''-4'''.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGKnucklesMoveSizes.gif&diff=323896File:SPGKnucklesMoveSizes.gif2021-03-31T16:19:29Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGKnucklesSizes.gif&diff=323895File:SPGKnucklesSizes.gif2021-03-31T16:18:23Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGTailsSizes.gif&diff=323894File:SPGTailsSizes.gif2021-03-31T16:17:48Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=File:SPGSonicSizes.gif&diff=323893File:SPGSonicSizes.gif2021-03-31T16:17:10Z<p>Lapper2: Category:Sonic Physics Guide images</p>
<hr />
<div>[[Category:Sonic Physics Guide images]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323885SPG:Characters2021-03-29T21:57:36Z<p>Lapper2: /* Knuckles */ Grammar</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
<br />
==Tails==<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
Well that is except for when he is gliding, climbing and sliding. Here, his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
When climbing, Knuckles will fall off the bottom of a wall if it is within 10px of his Y Position, and climb atop a ledge if it too is within 10px of his Y position. This 10px is just his Height Radius at the time.<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius)<br />
<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X speed is set to '''4''' in the opposite direction of the wall, and his Y speed is set to '''-4'''.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Special_Abilities&diff=323884SPG:Special Abilities2021-03-29T21:54:28Z<p>Lapper2: Moved very character specific info to new Characters page</p>
<hr />
<div>==Variables==<br />
<br />
The following variables/constants will be referenced frequently in this section.<br />
<br />
<nowiki>//Variables<br />
spinrev: the amount a spindash has been charged<br />
<br />
//Constants<br />
drpspd: 8 //the base speed for a drop dash<br />
drpmax: 12 //the top speed for a drop dash<br />
drpspdsup: 12 //the base speed for a drop dash while super<br />
drpmaxsup: 13 //the top speed for a drop dash while super<br />
</nowiki><br />
<br />
==Spindash (Sonic 2, 3, & K)==<br />
<br />
When you first begin the Spindash, ''spinrev'' is set to '''0'''. With every press of the button, ''spinrev'' is increased by '''2''', up to a maximum of '''8'''. Furthermore, during the entire time the Spindash is charging, ''spinrev'' is being affected by the following calculation:<br />
<br />
<nowiki><br />
spinrev -= ((p div 0.125) / 256) // "div" is division ignoring any remainder<br />
</nowiki><br />
<br />
This is exactly like the [[SPG:Jumping#Air Drag|air drag]] calculation.<br />
<br />
What this means is that the higher ''spinrev'' is, the faster it bleeds away toward zero.<br />
<br />
When you release the Down button, the character launches forward at a speed of ''8'', plus floor(''spinrev'') - i.e. ''spinrev'' minus its fractional part (everything after the decimal point) - divided by '''2'''.<br />
<br />
<nowiki><br />
Ground Speed = 8 + (floor(spinrev) / 2) // this would be negative if the character were facing left, of course<br />
</nowiki><br />
<br />
Because the higher ''spinrev'' is, the faster it depletes, it is nearly impossible to get maximum thrust from a Spindash without the aid of a Turbo controller or frame-by-frame play. However, if you were able to, the highest speed achievable through the Spindash is '''12'''.<br />
<br />
'''Note:''' Water has no effect on the release speed.<br />
<br />
===Spindash (Sonic CD)===<br />
<br />
The Sonic CD style Spindash is much simpler. Many Sonic players may find it inferior to the Sonic 2 style, though, so you may not want to use it in your engine. Or, if you do, you might include a choice in the control options which one to use.<br />
<br />
Once the button is pressed, Sonic will begin charging the Spindash. After '''45''' steps have passed, he is ready to go. When the Down button is released, Sonic launches at a speed of '''12'''. If the Down button is released early, nothing happens.<br />
<br />
<br />
==Shield Abilities (Sonic 3 & K)==<br />
<br />
===Flame Shield===<br />
<br />
When Sonic does the Flame Shield special move, his X speed is set to '''8''' in the direction he is facing, and his Y speed is set to '''0''', regardless of their previous values.<br />
<br />
===Bubble Shield===<br />
<br />
When Sonic does the Bubble Shield bounce, his X Speed is set to '''0''', and his Y speed to '''8'''. When he rebounds from the ground, his Y Speed is set to '''-7.5'''.<br />
<br />
===Electric Shield===<br />
<br />
When Sonic does the Electric Shield jump, his X Speed is unaffected, but his Y Speed is set to '''-5.5''' regardless of its previous value.<br />
<br />
'''Note:''' All of the Shield abilities can only be performed once in the air. Sonic must connect with the ground before he can perform them again.<br />
<br />
====Ring Magnetisation====<br />
When Sonic has an Electric shield, nearby rings will become mobile and begin moving towards him in a unique way. <br />
<br />
If a ring falls within a radius of approximately 64px around Sonic's position, they will become Magnetised and begin to move. When Magnetised, the rings have an X and Y speed and do not collide with anything (except for Sonic). In order to move correctly, we have to calculate how fast to move the ring, and in what direction to accelerate. The ring accelerates at 2 different rates depending on its relative position and speed, 0.1875 when already moving towards Sonic (in order to not pass him too quickly), and 0.75 when not (in order to quickly catch back up with him).<br />
<br />
First, it does a check to see where Sonic is in relativity horizontally.<br />
<br />
If Sonic is to the left of the ring:<br />
If the ring's X Speed is less than 0, add -0.1875 to the ring's X Speed.<br />
Otherwise, add -0.75 to the ring's X Speed.<br />
<br />
<br />
If Sonic is to the right of the ring:<br />
If the ring's X Speed is greater than 0, add 0.1875 to the ring's X Speed.<br />
Otherwise, add 0.75 to the ring's X Speed.<br />
<br />
<br />
Then, the attracted ring checks Sonic's relativity vertically.<br />
<br />
If Sonic is above the ring:<br />
If the ring's Y Speed is less than 0, add -0.1875 to the ring's Y Speed.<br />
Otherwise, add -0.75 to the ring's Y Speed.<br />
<br />
<br />
If Sonic is below the ring:<br />
If the ring's Y Speed is greater than 0, add 0.1875 to the ring's Y Speed.<br />
Otherwise, add 0.75 to the ring's Y Speed.<br />
<br />
<br />
The ring's X and Y Speeds are added to the ring's X and Y Positions, moving the ring.<br />
<br />
Here's some example code for the ring while magnetised<br />
<br />
<nowiki><br />
if (magnetised)<br />
{<br />
//relative positions<br />
sx = sign(Sonic's X Position - X Position)<br />
sy = sign(Sonic's Y Position - Y Position)<br />
<br />
//check relative movement<br />
tx = (sign(Ring's X Speed) == sx)<br />
ty = (sign(Ring's Y Speed) == sy)<br />
<br />
//add to speed<br />
X Speed += (ringacceleration[tx] * sx)<br />
Y Speed += (ringacceleration[ty] * sy)<br />
//"ringacceleration" would be an array, where: [0] = 0.75 [1] = 0.1875<br />
<br />
//move<br />
X Position += X Speed<br />
Y Position += Y Speed<br />
}<br />
</nowiki><br />
<br />
==Insta-Shield==<br />
<br />
The Insta-Shield expands Sonic's hitbox giving it a width radius of 24 and a height radius of 24, resulting in an overall height of 49 x 49 (more about [[SPG:Game_Objects#Hitboxes|Hit Boxes]]). This lasts for 13 frames.<br />
<br />
The Insta-Shield does nothing to Sonic's X Speed or Y Speed. It does however allow him to control a rolling jump.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=Sonic_Physics_Guide&diff=323883Sonic Physics Guide2021-03-29T21:54:10Z<p>Lapper2: /* Physics Guides */ New page</p>
<hr />
<div>ROM Hacks make the process of developing a functional Sonic game with unique art, enemies, and modifications much easier, since the game engine and basic mechanics are already functional. However, if the game requires a different game engine, modifying existing low-level assembly may be inappropriate, and some game designers might choose to program their own unique game engine. The physics of a game engine are rules that describe how to transform the player's input (either in the form of buttons, keyboard, or even a mouse if the designer feels inclined) into appropriate changes in the position of the sprites in the game (such as the Sonic sprite, or alternatively, how enemy sprites will respond). These physics guides will hopefully make the process of simulating the rules used in Sonic games easier.<br />
<br />
Since the rules themselves are independent of how they are implemented, many people choose programming languages such as Java, C, C++, Python, or a Lisp dialect to implement game physics. In addition, people can choose to use more specialized applications like Adobe Flash (Animate), GameMaker Studio 2, or a Clickteam program like Multimedia Fusion 2.<br />
<br />
Hopefully, these guides will provide adequate information to facilitate implementation.<br />
<br />
== Physics Guides ==<br />
*[[SPG:Basics]]<br />
A prerequisite for much of the info on this guide.<br />
*[[SPG:Characters]]<br />
Basic info about characters such as their varying sizes and jump height, and also detailing how their moves work.<br />
<br />
<div class="large-3 columns"><br />
===Collision===<br />
*[[SPG:Solid Tiles]]<br />
A detailed description of how sloped terrain is constructed, and how objects use sensors to collide with it. <br />
*[[SPG:Slope Physics]]<br />
How Sonic moves with momentum over angled surfaces, along with the specific physics for actions such as rolling.<br />
*[[SPG:Solid Objects]]<br />
Explaining object hitboxes, solidity, Sonic's hitbox, and other ways objects directly interact with Sonic.<br />
</div><br />
<div class="large-3 columns"><br />
===Gameplay===<br />
*[[SPG:Running]]<br />
Describing how horizontal inputs control Sonic.<br />
*[[SPG:Jumping]]<br />
Sonic's jump and acceleration in the air.<br />
*[[SPG:Rolling]]<br />
Physics and quirks of rolling.<br />
*[[SPG:Game Objects]]<br />
How objects such as rings, enemies, blocks, and springs move around, are constructed, and react to certain situations.<br />
*[[SPG:Main Game Loop]]<br />
The order of events for objects, including characters.<br />
</div><br />
<div class="large-3 columns"><br />
===Specific===<br />
*[[SPG:Ring Loss]]<br />
How rings disperse when hit.<br />
*[[SPG:Getting Hit]]<br />
What happens when Sonic gets hit.<br />
*[[SPG:Rebound]]<br />
Describing how Sonic bounces off enemies and other destroy-able items.<br />
*[[SPG:Underwater]]<br />
How Sonic's abilities change underwater.<br />
*[[SPG:Super Speeds]]<br />
How Sonic's abilities change when super.<br />
*[[SPG:Special Abilities]]<br />
Abilities such as spindashing and elemental shields.<br />
</div><br />
<div class="large-3 columns"><br />
===General===<br />
*[[SPG:Camera]]<br />
Mechanics of the camera following Sonic.<br />
*[[SPG:Animations]]<br />
Covering how animations play and specific animation timings.<br />
</div><br />
[[Category:Sonic Physics Guide| ]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Characters&diff=323882SPG:Characters2021-03-29T21:51:24Z<p>Lapper2: New Page SPG:Characters, detailing character specific abilities, sizes, and more</p>
<hr />
<div>==Intro==<br />
<br />
Each character in the Sonic games is constructed differently. While similar, they differ in size and moveset.<br />
<br />
For all characters, their Push Radius is always 10.<br />
<br />
==Sonic==<br />
<br />
When standing, Sonic's Width Radius is 9 and his Height Radius is 19, resulting in 19 pixels wide and 39 pixels tall.<br />
<br />
When Jumping or rolling, his Width Radius is 7 and his Height Radius is 14, resulting in 15 pixels wide and 29 pixels tall.<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
Sonic's jump force is 6.5.<br />
<br />
===Drop Dash (Mania)===<br />
The drop dash in Sonic Mania isn't simply an instant landing spindash.<br />
<br />
To charge Sonic up you release then hold the jump button while already jumping. The dash will take 20 frames to charge and once charged, when Sonic reaches the ground, your ground speed will be set. If the jump button is released before Sonic hits the ground, the move is cancelled.<br />
<br />
As Sonic hits the ground, Sonic's Ground Speed is calculated as normal. So at this point, Sonic will have a Ground Speed value (as if he landed normally) which will be used in calculating the drop dash speed.<br />
<br />
The game checks if you were moving backwards in the air. By backwards, I mean opposite to the way you were facing/pushing. For example, if your X Speed was positive but you were holding & facing ''left'' at the time this would count as backwards, same for the other direction. In either case, Sonic's actual direction is then set to that which you are facing/holding.<br />
<br />
====If you were moving forwards====<br />
Sonic's Ground Speed is set to his Ground Speed divided by 4, plus (or minus, depending on direction) ''drpspd''. This speed is limited to ''drpmax''.<br />
<br />
Ground Speed = (Ground Speed / 4) + (drpspd * direction) //direction is either 1 (right) or -1 (left), this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
====If you were going backwards====<br />
<br />
If Sonic's ''ang'' is 0 (flat), his Ground Speed is simply set to ''drpspd'' (or negative ''drpspd'')<br />
<br />
Ground Speed = drpspd * direction<br />
<br />
Otherwise, on slopes, his speed is set in the same way as normal, but divided by 2 rather than 4. Of course, because you were moving backwards your Ground Speed will be opposite direction of that which the dash is trying to propel you, so this results in a rather slow dash.<br />
<br />
Ground Speed = (Ground Speed / 2) + (drpspd * direction) // this speed would then be limited between the min and max values of -drpmax and drpmax<br />
<br />
<br />
A similar [[SPG:Camera#Spindash_Lag|Camera Lag]] effect to that used when spin dashing is used here too, to make the move more dramatic.<br />
<br />
===Dash (Super Peel Out)===<br />
<br />
Once the button is pressed, Sonic will begin charging the Dash. After '''30''' steps have passed, he is ready to go. When the Up button is released, Sonic launches at a speed of '''12'''. If the Up button is released early, nothing happens.<br />
<br />
<br />
==Tails==<br />
<br />
Tails is much smaller than the other characters.<br />
When standing, his Width Radius is 9 and his Height Radius is 15, resulting in 19 pixels wide and 31 pixels tall.<br />
<br />
The only time this changes is when he jumps or rolls, where his Width Radius is 7 and his Height Radius is 14, much like Sonic, resulting in 19 pixels wide and 29 pixels tall. <br />
<br />
His size is the same as standing when he flies.<br />
<br />
Tails' jump force is 6.5.<br />
<br />
===Flying===<br />
<br />
When Tails begins to fly, his Y speed is unaffected. However, since Tails has to release the button in order to press it again to fly, he can't possibly fly up faster than '''-4'''.<br />
<br />
While flying, the variables are much like a standard jump. He accelerates at '''0.09375''', and there is no separate deceleration value. The normal [[SPG:Jumping#Air Drag|air drag]] calculation is performed, which means Tails can't fly horizontally as fast while moving upward than when moving downward. The air drag cancels out the acceleration at an X speed of '''3'''. There is no air drag while moving down, though, so he can reach an X speed of '''6''', the normal maximum.<br />
<br />
While flying, gravity is '''0.03125'''. Pressing Up or Down doesn't decrease or increase it.<br />
<br />
Pressing the button doesn't cause an immediate loss of Y speed (like a double-jump), but instead a temporary change in gravity. Gravity becomes '''-0.125''', and remains so until Y speed is less than '''-1'''. Then gravity returns to normal in the next step and Tails begins to fly back down. If Y speed is already less than '''-1''', pressing the button does nothing.<br />
<br />
Tails can only fly for '''480''' frames, or '''8''' seconds, before getting tired. The only difference being tired makes (besides the pooped-out expression) is that pressing the button doesn't have any effect anymore. Gravity, and all other variables, remain the same.<br />
<br />
As stated above if you have negative gravity, a '''Ysp''' smaller than '''-1''' is needed to return to positive gravity. This can cause issues when you hit a ceiling and your '''Ysp''' is set to '''0.''' Your gravity will remain negative and you will be stuck. In your engine, to prevent Tails from being stuck in negative gravity, you should reset the gravity to the positive value when a ceiling is detected.<br />
<br />
'''Note:''' Tails' tails deflect projectiles (just like the Shields do) while he is flying.<br />
<br />
<br />
==Knuckles==<br />
<br />
Knuckles sizes are the same as Sonic's. <br />
Well, that is except for when he is gliding and climbing, sliding, where his Width Radius is 10 and his Height Radius is also 10, resulting in 21 pixels wide and 21 pixels tall. <br />
This makes him very wide but very slim compared to normal, which makes sense for his gliding pose. When falling from a glide, he uses his standing sizes.<br />
<br />
Knuckles' jump force is only 6, which results in a much lower jump than the others.<br />
<br />
===Gliding===<br />
<br />
When Knuckles first begins gliding, his X speed is set to '''4''' in the direction he is facing. Y speed is set to '''0''', but only if it was negative at the time, otherwise it is unaffected. X speed then accelerates by '''0.015625''' every step.<br />
<br />
Gliding has a top speed of '''24'''. This top speed is so high that it is unreachable anywhere in the game -- except for [[Mushroom_Hill_Zone|Mushroom Hill Zone]] Act 1, where Super/Hyper Knuckles can glide across the top of the level to achieve this speed.<br />
<br />
During the glide, gravity is '''0.125''', which is weaker than usual. Also, unlike a normal jump, gravity is only added while Y speed is less than '''0.5'''. If Y speed is higher than that (say Knuckles was falling quickly when he began to glide), gravity is ''subtracted'' from Y speed instead, slowing his descent.<br />
<br />
<nowiki><br />
if (ysp < 0.5) ysp += 0.125;<br />
if (ysp > 0.5) ysp -= 0.125;<br />
</nowiki><br />
<br />
When you let go of the button, Knuckles drops, and his X speed is multiplied by '''0.25'''. When he hits the ground, it is set to '''0'''. While dropping from a glide, gravity is the normal value, '''0.21875'''.<br />
<br />
If you don't release the button, but allow Knuckles to glide into the ground and slide on his stomach, he has a friction value of '''0.125''' while sliding. He starts to stand up as soon as X speed reaches '''0'''. If you release the button after he has begun to slide, X speed is set to '''0''' immediately, and he begins to stand up. Pressing Left or Right while sliding or standing up has no effect, but you can break into the standing up animation to jump if you press the jump button again.<br />
<br />
If Knuckles hits a wall while gliding, he catches on, and can climb it. He will catch on even if he's turning around, as long as his X speed is still in the direction of the wall.<br />
<br />
'''Note:''' Knuckles' knuckles deflect projectiles (just like the Shields do) while he is gliding.<br />
<br />
====Turning Around====<br />
<br />
When Knuckles is gliding, you can turn him around simply by tapping the Left or Right button. Even if you let go, he will continue to make a full turn. You can, however, reverse your decision and turn him back in the original direction before he makes a full turn.<br />
<br />
You might think that turning around while gliding would be much like turning around while running on the ground. X speed would be steadily decreased until it reached zero, and then would start adding in the other direction. This is not the case, though, and a special method is used that preserves Knuckles' gliding speed.<br />
<br />
When Knuckles is gliding, there is a value, which we'll call ''a'', that is '''0''' when he's gliding to the right, and '''180''' when he's gliding to the left.<br />
<br />
When Knuckles begins to turn, his X speed is stored - let's call the stored value ''t''. If he's turning from the left to the right, ''a'' is decreased by '''2.8125''' until it reaches '''0''' (which takes '''64''' steps). If he's turning from right to left, ''a'' is increased by '''2.8125''' until it reaches '''180'''. During the turn X speed is made to equal ''t'' times the cosine of ''a''.<br />
<br />
<nowiki><br />
a += 2.8125 * -sign(t);<br />
xsp = t * cosine(a);<br />
</nowiki><br />
<br />
So, no matter how fast Knuckles is gliding, he turns around in the same amount of time, and his speed reverses fully. During the turn, there is no acceleration. It kicks back in once he's finished turning all the way around.<br />
<br />
====Gliding Rebound====<br />
<br />
An interesting side-effect of the fact that Knuckles' Y speed is not immediately blunted when he begins gliding while falling quickly is the "Gliding Rebound". If you press the button to begin gliding just as Knuckles connects with an enemy or item monitor, his Y speed is reversed from the rebound just as he begins to glide. Since gliding gravity is weaker than standard gravity, he goes soaring up into the air. This is not necessarily a bug - it's actually kind of fun.<br />
<br />
Once Knuckles is already gliding, rebound operates normally. Since he can't exceed a Y speed of '''0.5''' while gliding, though, the effect is rather weak.<br />
<br />
====Underwater====<br />
<br />
Strangely enough, Knuckles' gliding and climbing physics are totally unaffected by water. I suspect this is because the code performed when entering and exiting the water simply changes the acceleration, deceleration, and top speed constants (this is why falling in water nullifies Super Fast Shoes). Because Knuckles' gliding and climbing code operates irrespective of these values, his abilities couldn't be affected by water without rewriting the water entry and exit code. In your engine you may wish to halve some of Knuckles' speeds when submerged to be more realistic, unless you want to remain 100% true to the original games.<br />
<br />
====Sliding====<br />
When you finish a glide by sliding on the ground, the game doesn't set Knuckles' grounded flag until he stops. Though, he mostly acts grounded, sticking to the floor and changing his angle as normal.<br />
<br />
===Climbing===<br />
When climbing, Knuckles will fall off the bottom of a wall if it is within 10px of his Y Position, and climb atop a ledge if it too is within 10px of his Y position. This 10px is just his Height Radius at the time.<br />
If there is a floor that meets the wall, he will stop climbing down when the floor is within around 19 pixels of his Y Position (so, his normal Height Radius)<br />
<br />
He climbs up and down at a speed of '''1'''. When Knuckles jumps off of a wall, his X speed is set to '''4''' in the opposite direction of the wall, and his Y speed is set to '''-4'''.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Camera&diff=323881SPG:Camera2021-03-29T19:43:25Z<p>Lapper2: Removed conversational language</p>
<hr />
<div>'''Note:''' Applies to Sonic 1, 2, 3 & K, and CD except where otherwise noted.<br />
<br />
==View Borders==<br />
<br />
The Sonic games have a screen size of '''320x224''' pixels during normal Zone play. Sonic isn't always locked in the centre of the screen, but has some freedom of movement within the view borders before it begins to scroll.<br />
<br />
===Horizontal Border===<br />
<br />
*Left border: '''144'''<br />
*Right border: '''160'''<br />
<br />
If you exceed the border, the camera will move by how much you've exceeded, up to '''16'''. So if Sonic moves faster than this, the camera will lag behind.<br />
<br />
Interestingly, this centres Sonic when he walks right but he isn't centred when he walks left. If you so choose, you can change the borders to '''152''' and '''168''' instead, to even things out a bit.<br />
<br />
Sonic CD, though, effectively has both borders set to '''160''' so that Sonic is always at dead centre and the view scrolls immediately when he begins moving.<br />
<br />
===Vertical Border===<br />
<br />
====In the Air====<br />
<br />
*Top border: '''64'''<br />
*Bottom border: '''128'''<br />
<br />
If you exceed the border, the camera will move by how much you've exceeded, up to '''16'''. So if Sonic moves faster than this, the camera will lag behind.<br />
<br />
''Note: Knuckles uses the same vertical borders while gliding and climbing. He's considered back on the ground when he starts to clamber over the corner of a wall, or stands up after sliding from a glide (i.e. when sliding, even though he is in contact with the ground, it still considers him to be in the air).''<br />
<br />
Here's a demonstration to show how Sonic's y position roams freely between the y borders.<br />
<br />
[[Image:SPGCameraAir.gif]]<br />
<br />
''Note: Sonic's position may appear a little lower than a border while jumping, this is due to a 5px offset while sonic is curled. See [[SPG:Solid_Tiles#Floor_Sensors_(A_and_B)|Solid Tiles]] for more details.''<br />
<br />
====On the Ground====<br />
<br />
If Sonic's Y is not equal to '''96''', the camera will move by the difference, up to a speed of '''6''' (if Sonic's Y speed is less than or equal to '''6'''), or '''16''' (if Sonic's ground speed is greater than or equal to '''8'''). This makes the camera catch up faster when going slow, but otherwise go at a slow pace.<br />
<br />
Here's a demonstration to show how Sonic's y position stays locked to the central y.<br />
<br />
[[Image:SPGCameraGround.gif]]<br />
<br />
==Looking Up and Down==<br />
<br />
*Looking Up: Scrolls up by '''104''' pixels, at a rate of '''2''' per step.<br />
*Looking Down: Scrolls down by '''88''' pixels, at a rate of '''2''' per step.<br />
<br />
In Sonic 1, the camera begins to scroll immediately when you press up or down. Obviously, this is annoying if there is to be a spindash or super peel out in your engine. In Sonic 2, 3 & K, the solution is to simply wait '''120''' steps after you begin to hold the button before it starts scrolling. In Sonic CD, though, another (IMO inferior) method is employed. After you press the up or down button, you have '''16''' steps to press it again, upon which the screen begins to scroll. The lame thing about this is that it will freeze the scrolling if you press the up or down button while the screen is already scrolling. For instance, look up by double-tapping, let go, and then press down. The screen will freeze, and stay there until you let go. Even initiating a spindash doesn't return things to normal.<br />
<br />
In all the games, once you let go of up or down (also in Sonic 2, 3, & K, once you initiate a spindash), the camera scrolls back to the neutral position by '''2''' pixels per step.<br />
<br />
==Spindash Lag==<br />
<br />
(Not in Sonic CD)<br />
<br />
Sonic has a previous position table in memory. The game knows where he's been over the last several seconds with great precision. This is called upon for the spindash lag.<br />
<br />
When the spindash is launched, a timer (''t'') is set to '''32''' (minus the rev variable, which can be between '''0''' and '''8''' - see [[SPG:Special Abilities#Spindash (Sonic 2, 3, & K)|spindash]] for more details). ''t'' then decreases by '''1''' every step.<br />
<br />
While ''t'' is non-zero, the camera stops behaving normally and begins to move, not based on Sonic's current position, but his position ''t''-1 steps ago. Once ''t'' runs out, the camera returns to business as usual.<br />
<br />
If the camera travels 32 steps into the past, and moves based on those recorded positions for a duration of 32 steps, it would effectively just repeat them, and then switch back to the current position, hopping 32 steps back into the future. This isn't how it works. Since it goes back 32 steps, and waits 32 more to return to normal, that means a total of 64 recorded camera positions. In order to take all of these positions into account during the 32 steps before the camera returns to normal, they are added together in pairs.<br />
<br />
As an example, let's imagine Sonic has been charging up his spindash for at least 32 steps, so that he hasn't moved during this time. Then, he launches at a speed of 8. Since in the last 32 steps he hasn't moved, the camera will move by 0 + 0 for 16 frames, remaining stationary. Then, it will have caught up to the point in time at which Sonic launched. Sonic will have been moving 8 pixels every step from this point on. The camera will then move 16 pixels for 16 more steps until ''t'' runs out. (Technically, Sonic doesn't move in the exact frame in which the spindash launches, so the camera will move by 0 + 8 for one step, and then 8 + 8 for a while, and the 7 + 8 as friction kicks in, and then 7 + 7, and so on).<br />
<br />
The trouble with this lag is that if you initiate and release a spindash quickly enough after moving, the camera will actually move backward to follow where you've been.<br />
<br />
===Flame Shield/Hyper Sonic Air Dash===<br />
<br />
The same lag routine happens when Sonic does the airdash as Hyper Sonic, or with the Flame Shield. However, the trick is when he launches, the ''entire previous position table'' is blanked out with his current position, so that the camera can't scroll backward. In your engine, if you do the same thing for the launch of a spindash, you won't have to worry about backward scrolling at all.<br />
<br />
Note:<br />
*You can achieve an almost identical effect without a previous position table by simply disallowing the camera to move for about 16 steps after launching. This is somewhat easier to do.<br />
<br />
==Extended Camera (Sonic CD)==<br />
<br />
In Sonic CD only, when Sonic reaches a ground speed of '''6''' (or is '''16''' steps into charging up a spindash or super peel out), the camera's X position begins to shift forward. It moves '''64''' pixels in total, '''2''' per step. When his ground speed drops back below '''6''', it moves back. The issue is, the ground speed doesn't update in mid-air, often leading to an off-centre view when jumping or running off of a ledge. <br />
<br />
If you use this shift effect in your engine, you could perhaps use Sonic's actual horizontal speed instead, as it doesn't make sense to scroll horizontally when going down or up a wall, or worse, on a ceiling.<br />
<br />
[[Category:Sonic Physics Guide|Camera]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=323880SPG:Game Objects2021-03-29T12:55:43Z<p>Lapper2: /* Calculating Each Log Depression */ Extra info</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
Item Monitors can act solid and also have a hitbox, depending on if Sonic is curled up or not, as well as other factors. They have a Width Radius of 16 and a Height Radius of 16, resulting in a 33 x 33 rectangle. The hitbox is the same.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians. Because the original game uses 256 angles rather than 360, there may be slight differences with the sine function causing some logs to be a pixel off (never the logs being stood on, mainly the logs towards the sides). It's tiny and unnoticeable, but can be corrected with extra work.<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// the above can be done upon bridge creation, getting the max depression for all segments and placing them in an array for later use<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
Each segment has a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle. The hitboxes are the same.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=323879SPG:Game Objects2021-03-29T12:44:56Z<p>Lapper2: /* Calculating Each Log Depression */ Improving code example</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
Item Monitors can act solid and also have a hitbox, depending on if Sonic is curled up or not, as well as other factors. They have a Width Radius of 16 and a Height Radius of 16, resulting in a 33 x 33 rectangle. The hitbox is the same.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some custom code to calculate these automatically may go as follows:<br />
<br />
Notes:<br />
* This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1. <br />
* If Sonic is not on any of the logs (from walking off), the max depression is just 0 so the bridge won't need to run this code. In addition, the logs don't need to update when Sonic jumps off apart from slowly relaxing the bridge.<br />
* It does not account for any smoothing of the bridge movement, like in Sonic Mania<br />
* Sine here uses degrees not radians<br />
<br />
// get the current segment stood on<br />
CurrentSegment = floor((Sonic's X position - Bridge's start X) / 16) + 1<br />
<br />
// get the current maximum depression for the bridge<br />
if CurrentSegment <= SegmentAmount / 2<br />
MaxDepression = CurrentSegment * 2 //working from the left side in<br />
else <br />
MaxDepression = ((SegmentAmount - CurrentSegment) + 1) * 2 // working from the right side in<br />
<br />
// loop through all segments and find their y positions<br />
for (i = 0; i < SegmentAmount; i ++)<br />
{<br />
// get difference in position of this log to current log stood on<br />
difference = abs((i + 1) - CurrentSegment);<br />
<br />
// get distance from current log to the closest side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) <br />
log_distance = 1 - (difference / CurrentSegment) //working from the left side in<br />
else <br />
log_distance = 1 - (difference / ((SegmentAmount - CurrentSegment) + 1)) // working from the right side in<br />
<br />
// get y of log using max depression and log distance<br />
LogY[i] = BridgeY + floor(MaxDepression * sine(90 * log_distance)) //the final y position for the log<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
Each segment has a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle. The hitboxes are the same.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Basics&diff=323857SPG:Basics2021-03-28T10:56:05Z<p>Lapper2: General fixes</p>
<hr />
<div>''Notes: Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.''<br />
<br />
==Introduction==<br />
The aim of this guide is to accurately describe the mechanics of classic Sonic games, while explaining the concepts well enough to allow for different ways to implement the same ideas. All that is described can be implemented in many different ways with the same or very similar results, and nothing needs to be done the exact way the Genesis hardware did.<br />
<br />
Before we dive into how the game works, it is first important to know some basic attributes about how the games are structured which will apply to multiple aspects throughout this guide. This is relevant mostly for those looking for extremely close accuracy, and to provide context for some of the game's more subtle behaviour involving collision or motion.<br />
<br />
==Positions==<br />
Game engines these days can effortlessly use real decimal values to move objects around and perform precise calculations. In the original Genesis games, they work a bit differently.<br />
<br />
===Pixel and Subpixel===<br />
Positions and Speeds in the Genesis games are in 2 parts, these being the '''pixel''' and the '''subpixel'''. The pixel is quite plainly what you would expect, it's what you would see on screen. Think of each pixel as a cell in a grid and this is which cell to look at. The subpixel, however, acts as the fractional part of the position.<br />
<br />
Because decimal numbers aren't used, this subpixel actually a positive number up to 256. <br />
<br />
Each pixel is effectively split into 256 slices along both axis and this means the finest fidelity available for movement is 1/256th of a pixel (aka 0.00390625). Think of this as where in that grid cell the real position currently is.<br />
<br />
All decimal values touted in this guide can be translated into a subpixel amount simply by multiplying them by 256. Sonic's acceleration (''acc'') for example, is equal to 12 sub pixels. His gravity (''grv'') is 56 subpixels. <br />
<br />
If, for example, Sonic's X pixel position is 50, and his X subpixel position is 48, the real/decimal X position is 50 + (subpixel / 256) which results in a final ''xpos'' of 50.1875. Even when the pixel portion is negative, the subpixel is always relative to the left within that pixel, and is added to the pixel position. So you can easily think of the pixel value to be the object's position, but floored (rounded down). This applies to all positions for any object.<br />
<br />
Lastly, this exact same principle is applied to speeds, like an X speed or an acceleration, where they all have a pixel and subpixel component.<br />
<br />
===Using Decimals===<br />
Decimal values can be used just fine to emulate this in a modern game engine, but keeping this pixel/subpixel mechanic in mind is important for absolute accuracy.<br />
<br />
==Angles==<br />
<br />
Degree angles are counter-clockwise with 0 facing right (flat floor).<br />
<br />
[[Image:SPGAngles2.png|link=Special:FilePath/SPGAngles2.png]]<br />
Dark blue represents ground, light blue represents air.<br />
<br />
===Hex Angles===<br />
The Mega Drive games use angles in hex, 0 ($00) through 255 ($FF), meaning that there are only 256 divisions of a circle, not 360 like we're used to. <br />
<br />
Note: ''In this case, 256 would be the same angle as 0, much like an angle of 360 is the same as 0.''<br />
<br />
Throughout the guides, angles will be presented in 3 forms. Converted degrees, Inverted Decimal (the real hex values, converted to decimal, and reversed to be anti clockwise for comparison with the degrees), and the original hex value. Degrees are fine to use and are far more intuitive, however if you want pinpoint accuracy to the originals, you would need to create and use your own angle system using 256 clockwise angles.<br />
<br />
====Reference: Converting Hex Angles====<br />
Of course, if for some reason you only have access to the Hex angle, the 256 slices will not help you if you desire using degrees. Worse, the direction is anti-clockwise compared to other languages like GML, so $20 isn't 45° like it should be - it's 315°.<br />
In order to convert the original hex angles into angles you can use in GML, use this calculation:<br />
<br />
real_angle = (256-hex_angle)*1.40625<br />
<br />
This reverses the hex angle, then multiplies it to fit within the 360 range.<br />
<br />
==Objects==<br />
Objects are the building blocks of each Sonic game (ignoring the Terrain Tiles). Sonic is an object, items are objects, and so on. <br />
<br />
===Variables===<br />
<br />
The following variables/constants will be referenced frequently in this guide.<br />
<br />
<nowiki>//General Object Variables/Attributes<br />
X Position: The X-coordinate of the object's centre.<br />
Y Position: The Y-coordinate of the object's centre.<br />
X Speed: The speed at which the object is moving horizontally.<br />
Y Speed: The speed at which the object is moving vertically.<br />
Ground Speed: The speed at which the object is moving on the ground.<br />
Ground Angle: The object's angle on the ground.<br />
Width Radius: The object's width from the origin pixel left and right.<br />
Height Radius: The object's height from the origin pixel up and down.<br />
<br />
//Sonic's Variables<br />
Push Radius: Sonic's width from the origin pixel left and right (for pushing).<br />
Slope Factor: The current slope factor value being used.<br />
<br />
//Sonic's Speed constants<br />
acc: 0.046875 ;acceleration<br />
dec: 0.5 ;deceleration<br />
frc: 0.046875 ;friction (same as acc)<br />
top: 6 ;top horizontal speed<br />
slp: 0.125 ;slope factor when walking/running<br />
slprollup: 0.078125 ;slope factor when rolling uphill<br />
slprolldown: 0.3125 ;slope factor when rolling downhill<br />
fall: 2.5 ;tolerance ground speed for sticking to walls and ceilings<br />
<br />
//Sonic's Airborne Speed Constants<br />
air: 0.09375 ;air acceleration (2x acc)<br />
jmp: 6.5 ;jump force<br />
knxjmp: 6 ;knuckles' jump force<br />
grv: 0.21875 ;gravity<br />
</nowiki><br />
<br />
===Set up===<br />
Each game object follows the same or similar conventions. They all have X and Y positions, X and Y speeds, a Width Radius, a Height Radius, animation variables, an angle, and more.<br />
<br />
For the most part, the width and Height Radius of an object describes it's solid size, so it's the box size if you can push against it or how big it is when touching solid tiles if it can move around. Or both, as the case may be.<br />
<br />
====Sizes====<br />
Objects use 2 values to define their size for collision. A Width Radius, and a Height Radius. These are centred outward on the object's origin, forming a box around it. <br />
In a game with small sprites and pixel art, a pixel difference with worth acknowledging. A size in Sonic games ''isn't'' simply 2 times the radius.<br />
<br />
[[Image:SPGHitBoxRadius.png]]<br />
<br />
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 pushing Width Radius of 10 become a total width of 21, '''not''' 20. This applies for all heights as well as all widths. This basically results in every collision mask, box, or sensor arrangement always being an odd number in overall size. This means they also won't perfectly match up with the evenly sized pixel art.<br />
<br />
It's a matter of 1 pixel in width and height, but it could make all the difference in accuracy. To avoid confusion, both measurements will be mentioned (otherwise, refer to image examples).<br />
<br />
====Hitboxes====<br />
Hitbox sizes are not the same thing as the object's Width Radius and Height Radius, though they do follow the same format. They are defined separately and can differ greatly from the normal object size. For example, a ring's Width/Height Radius would be 8, but for it's hitbox it is 6.<br />
<br />
====Characters====<br />
Characters follow the same format as any other object for the most part. But there are some specific character traits that can help you get started.<br />
<br />
Sonic's Width Radius is usually 9, and his Height Radius is usually 19. These radiuses determine where Sonic's floor and ceiling tile sensors will be. These can change depending on Sonic's action, and how they behave will be described in Solid Tiles, as they are most relevant to collision specifically.<br />
Sonic also has another radius that he uses for pushing against objects and tiles. We will call this his '''Push Radius''' which is always 10. This radius is implied via being used for the sensor positions and during object collision rather than actually defined as a variable in-game, though it simply makes more sense here to describe it as a constant.<br />
<br />
===Colliding===<br />
The main thing to keep in mind with any object/tile collision is that it typically only concerns itself with any object's ''whole pixel'' position, totally ignoring any subpixel amount. So when you perform any collision calculations, you can emulate this by just comparing/testing/using a floored position instead of the real position.<br />
<br />
[[Category:Sonic Physics Guide|Basics]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=323856SPG:Solid Objects2021-03-28T10:53:05Z<p>Lapper2: Formatting and consistency</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with Sonic in many different ways and are a very different beast than Solid Tiles. Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. This guide will go over the collision of various objects found in the Sonic trilogy.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with Sonic when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push Sonic in the other direction. As a general rule, any seemingly solid object that Sonic cannot stand on is most likely actually using a hitbox rather than real solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If Sonic's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to Sonic. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===Sonic's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, Sonic needs his own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
<br />
Sonic's hitbox is much like that of any other object. It sits at Sonic's X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than Sonic's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, Sonic's hitbox needs to shrink. Problem is, Sonic's position and Height Radius don't actually change at all while crouching. So to tackle this it manually updates the hitbox's size and position while Sonic crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centered between pixels (rather they are 1px left and down) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
The same is true for solid object boxes, so Sonic will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
==Solid Objects==<br />
<br />
As stated above, object collision is totally separate from tile collision. Sonic does not collide with objects using his solid tile sensors, instead, special calculations are used to check if Sonic's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after Sonic's code has been executed for that frame, including his tile collision and movement. Since objects run their code after Sonic, it's the job of the objects to push Sonic out of themselves.<br />
<br />
''Note: These collisions deal with floored positions (integer values), ignoring any fractional/subpixel amount.''<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does away entirely with hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. For normal objects that is, Sonic will use his Height Radius for this too, but horizontally he of course needs to use his Push Radius instead.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* Sonic will check if he is overlapping the object.<br />
* Sonic will decide which side of the object he is nearest to n both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels he is to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether he's closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* He will then be pushed out towards that side on that axis by the distance he overlaps. <br />
<br />
Now, let's get into the details.<br />
<br />
The first thing the object collision code does is check if Sonic is standing on the object (using a flag which is set if he lands on one). If he is, it will skip straight to checking if Sonic has walked off the edges rather than general object collision. Otherwise, it will continue as follows.<br />
<br />
====Checking For An Overlap====<br />
To check for an overlap the game needs to know the boundaries that Sonic's positions need to be within.<br />
<br />
Horizontally, the object combines its own Width Radius with Sonic's Push Radius and adds 1px extra (so Push Radius + 1).<br />
<br />
Vertically, it very similar. The object combines its own Height Radius with Sonic's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
''Note: Objects don't always use their real Height Radius when acting solid, and will just pass in a hard-coded value. Sometimes this value is slightly different than you'd expect, like 17 instead of 16. Point being, it's worth checking each individual object for the specific values they use. Generally speaking, the above applies to most.''<br />
<br />
Here's a demonstration of how these new radiuses relate to Sonic's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius'''.<br />
<br />
Now all the game needs to worry about is Sonic's X Position and Y Position being within this new box, it no longer needs to worry about what Sonic's sizes are.<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between Sonic's X Position and the object's left side.<br />
<br />
left_difference = sonic's X Position - object's X Position<br />
left_difference += combined_x_radius //add combined radius<br />
<br />
Then, it will check if this new difference value has passed the boundaries, and exit the object collision if it has.<br />
<br />
//sonic is too far to the left to be touching<br />
if (left_difference < 0) exit;<br />
<br />
object_x_diameter = combined_x_radius * 2 //get object's x diameter<br />
<br />
//sonic is too far to the right to be touching<br />
if (left_difference > object_x_diameter) exit; <br />
<br />
If no exit occurred, Sonic is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between Sonic's Y Position and the object's top side.<br />
<br />
top_difference = sonic's Y Position - object's Y Position<br />
top_difference += 4 //add 4<br />
top_difference += combined_y_radius //add combined radius<br />
<br />
The game also allows Sonic to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge Sonic was standing on. The game does this by effectively pretending Sonic is 4px lower than he really is when checking the y overlap. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the boundaries, and exit the object collision if it has.<br />
<br />
//sonic is too far above to be touching<br />
if (top_difference < 0) exit;<br />
<br />
object_y_diameter = combined_y_radius * 2 //get object's y diameter<br />
<br />
//sonic is too far down to be touching<br />
if (top_difference > object_y_diameter) exit; <br />
<br />
If no exit occurred, Sonic is overlapping on the y axis, and it will continue. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
====Finding The Direction of Collision====<br />
If Sonic is found to be touching the object, the game will then decide whether he is to be popped out the top or bottom, or the left or right of the object. The game will compare Sonic's position to the object's position to determine which side he is on. <br />
<br />
To do this, the game will first determine which side Sonic is in comparison with the object's position. <br />
<br />
If Sonic's X Position is greater than the object's X position, he's on the right, otherwise, he's on the left. If Sonic's Y Position is greater than the object's Y position, he's on the bottom, otherwise, he's on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the nearest edge. <br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
=====Horizontal Edge Distance=====<br />
If Sonic is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side and will be a positive number. <br />
<br />
If Sonic is on the right, the distance will be flipped around.<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the object's right side and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance'''.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the y axis. If Sonic is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the left side and will be a positive number. <br />
<br />
If Sonic is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
top_difference -= 4<br />
y_distance = top_difference - object_y_diameter<br />
<br />
This is effectively the distance to the objects bottom side and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance'''.<br />
<br />
''Note: If Sonic is on the top, the extra 4px isnt subtracted yet. It will be subtracted upon landing.''<br />
</div><br />
</div><br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way Sonic should be popped out. <br />
<br />
It does this by finding which side Sonic is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical, which is simply due to Sonic not being square himself. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out Sonic on either axis depending on the sign of the distance value. When colliding vertically, the game knows that Sonic is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
====Popping Sonic Out====<br />
Once a collision has occurred and the game had decided the direction Sonic then needs to be "popped out" of the object so that he is no longer in it, and his speeds need to be changed. But where does it put Sonic? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance Sonic needs to move to exit the object, but reversed. So when he is popped out, they will simply be subtracted from his position.<br />
<br />
=====Popped Left and Right=====<br />
Popping Sonic out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping Sonic out if absolute '''y distance''' is greater than 4. The game will also only bother touching Sonic's speeds if '''x distance''' is not 0.<br />
<br />
If the game does decide to affect Sonic's speeds, this also depends on a few factors. If Sonic is on the left and the game has decided he needs to be popped out to the object's left side, it will only stop Sonic's speeds if Sonic is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if Sonic is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from Sonic's position, popping Sonic out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and Sonic is told he is pushing.<br />
<br />
=====Popped Downwards=====<br />
If Sonic bumps the bottom of an object, the game will check if Sonic is moving vertically (Y Speed is not 0). If not, the game then checks if Sonic is standing on the ground, and if he is, kills him from crushing, then exits. <br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as Sonic is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides Sonic is to be popped out upwards, Sonic will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is less than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will scrap the '''combined X radius''' we were using before, and find a new X radius, this being the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare Sonic's position using this radius.<br />
<br />
action_radius = 16 //an example<br />
action_diameter = action_radius*2<br />
<br />
x_comparison = action_radius plus object's X position<br />
x_comparison -= sonic's X Position<br />
<br />
//if sonic is too far to the right<br />
if (x_comparison is less than 0)<br />
{<br />
exit;<br />
}<br />
<br />
//if sonic is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter)<br />
{<br />
exit;<br />
}<br />
<br />
This means Sonic will exit the landing and will just slip off the side keep falling if his X Position isn't directly above the object, which is actually quite strange as it's as if Sonic is only 1 pixel thick.<br />
<br />
The last check is if Sonic's Y Speed is negative, he wont land and it will exit.<br />
<br />
Finally, if it reaches this far, he will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from Sonic's Y Position. It also subtracts an extra 1px afterwards to align him correctly.<br />
<br />
Then it resets Sonic to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets Sonic's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game Sonic is on the object. <br />
<br />
Finally, Sonic's Ground Speed is set to equal his X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], Sonic's collisions with tiles and objects only concern themselves with Sonic's floored position (his pixel position), and the same applies to the object itself. So, upon the point of contact, Sonic's floored X Position finds himself overlapping the object. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if Sonic was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if Sonic keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath Sonic each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue Sonic to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, Sonic is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If Sonic is standing on an object, the game will only check if Sonic has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = sonic's X Position - the object's X //get the position difference<br />
x_left_distance += combined X radius //add the combined X radius<br />
<br />
Sonic will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and Sonic is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if Sonic is still on it, the game handles moving Sonic with the object and keeping him stuck to it.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if Sonic's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push Sonic two conflicting ways, the one who executes their solid object code last will win out. The result of this, and partly thanks to the edge slipping mentioned above, Sonic can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
Sonic will collide on top with both spikes, but his position isn't directly over either of them, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overalap====<br />
When the vertical overlap is being checked, the game pretends Sonic is 4px lower than he actually is. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, Sonic will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that Sonic is still grounded even though there's no longer any object to be grounded to. Because Sonic's grounded, he won't fall. Additionally, he also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like Sonic does, at their central bottom point. The same applies to wall collision.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read.<br />
<br />
When a sloped object is acting solid to sonic, instead of using the y position of the top of the solid box, it instead reads the value from the array, and from there as far as the game is concerned, the object is as high as the array tells it. This continuously happens as Sonic passes over the object, resulting in smooth motion.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that Sonic will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, Sonic will be slightly "deeper" into the slopes than he would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of his side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all Sonic can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if Sonic's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with Sonic while he is moving down or staying still. This is why Sonic can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Height Radius from the Y Position.<br />
<br />
Then Sonic's bottom Y is calculated by adding his Height Radius to his Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows Sonic to collide even when he's 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than Sonic's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between Sonic's bottom and the platform's surface (platform's surface Y minus Sonic's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as Sonic is too low.<br />
<br />
If it reaches past all those checks, Sonic will land.<br />
<br />
====Popping Sonic Out====<br />
<br />
The distance from before is added to Sonic's Y Position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add Sonic's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with platforms. This means not only will Sonic fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should, unlike the normal object collision.<br />
<br />
This was probably missed because Sonic doesn't need to push against these platforms, so it's much harder to notice if Sonic's Push Radius hasn't been applied. <br />
<br />
After this of course, Sonic is still standing on it, so the game handles updating Sonic's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
Sloped objects, like those that fall in Green Hill, and these platforms also provide the actual X radius rather than a combined one. There's also a likely reason for this too.<br />
<br />
These have slope data, which is a height array, and the value returned from this is based on Sonic's X Position. So Sonic stands on them, and his current height on the platform is decided based on his X Position relative to the object. If Sonic's Push Radius was added to the x radius used for Sonic to walk off the edges, and if Sonic could then hang off the edges slightly, the current height of the object's slope would be invalid causing odd results like Sonic being sent somewhere weird vertically due to an invalid height being read. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the platform boundary.<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and Sonic's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When Sonic has contacted the push block, Sonic has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If Sonic pushed to the left, both Sonic and the block will move 1 pixel to the left, Sonic's X Speed is set to 0 and Ground Speed is set to -0.25. If he pushed to the right, both Sonic and the block will move 1 pixel to the right, Sonic's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out Sonic is no longer touching the object. When this happens, Sonic's pixel position has been altered, but his subpixel position remains the same. So if Sonic was half a pixel into the object before, he's now half a pixel outside of it. Before he makes contact with the object again, he needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because he is given a headstart when his Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. Sonic's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and Sonic's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, Sonic has just pushed the block and has been moved out of it, then along with it. The fractional part of his position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- Sonic makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because Sonic's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
Sonic has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of his position is currently .003 , the very left of the pixel. This means he has farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- Sonic gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- Sonic makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
Sonic has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while Sonic is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item Monitors, as you may have noticed, are not always solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what Sonic is doing and changing how the Item Monitor will react.<br />
<br />
While not curled up in a ball, the Item Monitor acts as solid. The Item Monitor's hitbox isn't accessible at this time.<br />
<br />
While curled, however, the item box will no longer have any solidity at all, and its hitbox is active and accessible. The hitbox collision is what destroys the Item Box and bounces Sonic off (Details in [[SPG:Rebound#Monitors|Monitors Rebound]]).<br />
<br />
However, there is an exception. If Sonic is moving up (Y Speed < 0) while curled, the Item Box will in fact still act solid. Additionally, if Sonic's Y Position-16 is smaller than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards, and Sonic's Y Speed will be reversed.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Tiles&diff=323855SPG:Solid Tiles2021-03-28T10:38:04Z<p>Lapper2: Clarifications</p>
<hr />
<div>'''Notes:'''<br />
*The research applies to all four of the [[Sega Mega Drive]] games and ''[[Sonic CD]]''.<br />
<br />
*Following only describes how [[Sonic the Hedgehog|Sonic]] collides and interacts with solid tiles. Solid objects, such as [[Monitor|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. For this, refer to [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
*The original games use solid tiles, however the ideas and mechanics of Sonic's base collision setup can be adapted (with adjustments) to other engines using sprite masks, line intersections, etc.<br />
<br />
*While 16x16 tiles are "officially" named blocks, they are being referred to as solid tiles here since they are a simple grid pattern of sets of data which can be read simply, as opposed to objects or any other method. "Solid Tiles" and "Blocks" can be used interchangeably in this guide.<br />
<br />
==Introduction==<br />
<br />
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.<br />
<br />
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 pixel blocks, which are again in turn broken into even smaller 8x8 pixel tiles. All of the solidity magic happens with the 16x16 blocks, so those are the only ones we will be interested in throughout this guide. <br />
<br />
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. <br />
<br />
First we will look at how the environment is constructed from tiles, and then Sonic's method for detecting his environment.<br />
<br />
==Solid Tiles==<br />
<br />
Solid tiles are a grid of data blocks, which represent solid areas within each grid cell. This area is defined using height masks.<br />
<br />
===Height Masks===<br />
<br />
When checking a solid tile, how is the height of the tile found?<br />
<br />
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 0px ($00) to 16px ($10) and an angle value.<br />
<br />
[[Image:SPGHeightMask.PNG|link=Special:FilePath/SPGHeightMask.PNG]]<br />
<br />
This height mask, for example, has the height array 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9, and the angle 33.75° ($E8).<br />
<br />
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.<br />
<br />
If the height value found is 16px ($10), that's the entire tile filled at that X position, so then the sensor has to check for another tile above the first one found, and search for that one's height value.<br />
<br />
====Horizontal Axis====<br />
Solid Tiles also have another height array (or, well, a width array) for horizontal collisions. This other array represents the same data and creates the exact same shape within the tile. This is only possible because the shapes represented in tiles are usually smooth continuous slopes, which don't extend in one direction then regress back. but either continue sloping in the same direction or stop.<br />
<br />
====Flipping Tiles====<br />
You may rightly wonder hows sloped ceilings are possible if the height array starts at one end only. The answer to this is that tiles can be flipped horizontally or vertically. The collision systems take this into account when reading the height data from tiles.<br />
<br />
==Sensors==<br />
<br />
"Sensors" are simply checks performed by objects which look for solid tiles around them. <br />
<br />
An x/y position ('''anchor point''') is checked, and if it finds a solid tile, they will gather information about the tile. <br />
Sensors can point down, right, up, and left, and all behave the same in their respective directions.<br />
<br />
[[Image:SPGSensorAnchors.png]] ''The white points represent the '''anchor''' positions of Sonic's sensors.''<br />
<br />
In this example, the sensor to Sonic's mid right points right, and those at Sonic's feet point down. Their direction dictates in which direction they are looking for a surface. A sensor pointing right is looking for the leftmost edge of solid terrain nearby, a sensor pointing down is looking for the topmost edge of solid terrain nearby.<br />
<br />
So, we know they are points which look for solid tiles they touch. However, this is not the whole picture. If a sensor finds an empty tile or the array value of the tile found by the sensor is 16 (a full block amount), then it's likely that the surface of the solid terrain is actually found within an adjacent tile.<br />
<br />
====Sensor Regression & Extension====<br />
So when a sensor check is performed at a sensor's '''anchor point''' it has either found a solid tile, or it hasn't. If it has, what if the height value found is 16 and isn't actually the surface of the terrain? Or if it hasn't, what if there's a solid tile nearby? <br />
<br />
Well, this is easily solved by checking nearby tiles also, until certain conditions are met. <br />
<br />
In the case of a sensor which is pointing down looking for solids below:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''Regression:'''<br />
<br />
*If the '''anchor point''' finds a Solid Tile, and if the height array value at the sensor's X of that tile is 16 (meaning the tile is completely filled in that area), it will check up by 1 extra Solid Tile. We'll call this the "regression" since it goes back against the sensor direction.<br />
**If a regression occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
<div class="large-6 columns"><br />
'''Extension:'''<br />
<br />
*If the '''anchor point''' just finds an empty tile (height array value of 0), it will check down by 1 extra Solid Tile. We'll call this the "extension" because it goes outwards, in the direction of the sensor.<br />
**If an extension occurs and finds no solid in the second tile, the second tile will be ignored.<br />
</div><br />
</div><br />
If the extension/regression does not fail, the new tile is the one which is processed, otherwise the first tile is processed instead.<br />
<br />
[[Image:SPGSensorDistance.gif]]<br />
<br />
In the above example, a downward facing sensor moves down through 3 Solid Tiles. We can see the tiles it checks, and the distance it returns at each position. We can also see if it is performing extension or regression. You can see this means the sensor can effectively act as a line, it can regress or extend up to 32 pixels to find the nearest surface.<br />
<br />
To reiterate, when the sensor is within a tile which has a height array value of 0 at the sensor's x position (empty) it will check another tile in the sensor direction (extension). If the tile's height value is between 0 and 16 (not inclusive), the surface of the terrain has been found without needing to check extra tiles. If the tile's height value is 16, it will check another tile opposite to the sensor direction (regression).<br />
If the regression still fails to find a tile with a surface within, it will still return the information of the second tile. If the extension fails to find any solid tile, the sensor will return a distance of 0.<br />
<br />
The regression & extension will occur in the direction of the sensor, be it horizontal or vertical. If the sensor is horizontal, it reads the other height array belonging to the tile, using the sensor's y position. Essentially rotating the entire setup.<br />
So a right facing sensor's regression would check an extra tile to the left, and extension would check an extra tile to the right. While an upward facing sensor's regression would check an extra tile below, and extension would check an extra tile above. <br />
<br />
With all this, a sensor can always locate the nearest open surface (and the tile containing that surface) of the terrain within a range of 2 tiles (the tile the sensor '''anchor point''' is touching plus another). <br />
<br />
====Reaction====<br />
Once a final suitable tile has been found, information about the tile is returned.<br />
<br />
The information a sensor finds is as follows:<br />
*The distance from the sensor pixel to the surface of the solid tile found (in the sensor's direction)<br />
*The angle of the tile found<br />
*The tile ID<br />
<br />
=====Distance=====<br />
The distance to the solid tile surface (found by the sensor) is the most important piece of information dictating how an object will react to solid tiles. It's not the distance to the tile, it's the distance to the ''edge of the solid area within the tile'', precisely.<br />
<br />
The distance can either be 0, negative, or positive. When no Solid Tile is found by a sensor, a distance of 0 is returned by default.<br />
<br />
*A distance of 0 means the sensor is just touching the solid tile surface (or has found nothing) and the object does not need to move.<br />
<br />
*A negative distance means the surface found is closer to the object than the sensor position. Negative distances are almost always reacted to when colliding, because it indicates that the object is inside the solid and can be pushed out.<br />
<br />
*Positive distances mean the surface found is further away from the object than the sensor position. Since this means the object isn't actually touching the tile, it's rarely used - but not never. A notable example of it's use is by floor sensors of various objects, including Sonic, to keep them attached to the ground even if the ground has sloped away from them a bit as they move. Objects usually employ a limit to how far an object can be "pulled" down to a solid they aren't actually touching. This will be detailed further down for Sonic.<br />
<br />
If the object decides to snap itself to the terrain, it simply has to add the distance value to it's position. Or subtract, depending on the sensor's direction. A sensor pointing left may return a negative distance if it is inside a solid, but the object would have to subtract the distance in order to move to the right, out of it.<br />
<br />
Of course, as stated, this distance can be representative of any 4 directions, depending on the sensor's own angle.<br />
<br />
====Summary====<br />
Here's a demonstrative animation showing a very simplified process of how the floor sensors detect a tile and be moved upwards. In this case, Sonic will have a Ground Speed of 6.<br />
<br />
[[Image:SPGSensorProcess.gif]]<br />
<br />
====Visual Depiction====<br />
Throughout this guide these sensors will be drawn as lines. But, they are not. Or well, they are, but not quite as shown ahead.<br />
<br />
Sensors will be drawn from the sensor anchor, extending towards the centre of the object. You can imagine it like so - if the exact surface pixels of the ground is within these lines, Sonic will be pushed out. It is of course not quite this simple in reality. As shown in the previous visualisation of a sensor, the "line" portion can extend up to 32 pixels in either direction, all depending on where the sensor anchor currently sits within it's tile... This would be impossible to accurately draw over Sonic while keeping things understandable and clear. This visualisation is the most easy to visualise way to think about the solidity on a surface level.<br />
<br />
Just be aware that the line based depictions are for simple illustration purposes only and the endpoints of the lines are the active sensor anchors (which always behave as described).<br />
<br />
==Sonic's Sensors==<br />
<br />
Like any object which wants to collide with tiles, sensors surround Sonic. <br />
<br />
[[Image:SPGSensors.png|link=Special:FilePath/SPGSensors.png]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> - Floor collision<br />
<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> - Ceiling collision (only used mid-air)<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> - Wall collision (shifting by 8px depending on certain factors, which will be explained)<br />
XY - Sonic's X Position and Y Position<br />
<br />
<br />
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 and Height Radius both of which will change depending on Sonic's state.<br />
<br />
Note on sprite alignment:<br />
* 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. Amusingly, this offset will appear corrected when pushing 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 accuracy, it will appear the opposite in-game. More about object collision in [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
<br />
=== Floor Sensors (<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span>) ===<br />
<br />
[[Image:SPGStandingAnimated.gif|link=Special:FilePath/SPGStandingAnimated.gif]]<br />
<br />
<span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> sit at his feet at Y Position+Height Radius.<br />
<br />
====Movement====<br />
Typically, Sonic's Width Radius is 9, placing <span style="color:#00f000; font-weight: bold;">A</span> on Sonic's left side, at X Position-9. While <span style="color:#38ffa2; font-weight: bold;">B</span> should be on his right, at X Position+9, 19 pixels wide.<br />
His Height Radius is usually 19, making him 39 pixels tall in total.<br />
<br />
However while rolling or jumping, his Width Radius becomes 7, placing <span style="color:#00f000; font-weight: bold;">A</span> at X Position-7. With <span style="color:#38ffa2; font-weight: bold;">B</span> at X Position+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 Width Radius.<br />
<br />
Here's an example of that in action:<br />
<br />
[[Image:SPGWidthRadiusChange.gif|link=Special:FilePath/SPGWidthRadiusChange.gif]]<br />
<br />
While rolling or jumping (and otherwise generally curled up), Sonic's 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 game adds 5 to his Y Position so that his bottom point will remain unchanged despite him getting shorter and his center changing position. 5 also has to be subtracted from Y Position 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.<br />
<br />
====Method====<br />
<br />
Assuming the ground level to be at a Y position of 736 ($02E0), while standing Sonic is atop it at a Y Position of 716 ($02CC), which is 20 pixels above ground level.<br />
<br />
Floor sensors are a special case, there are 2 sensors and they need to detect slopes. Both sensors behave the same and search for a Solid Tile. The smaller distance is the sensor that wins. For example, -10 is a smaller distance than 5. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
<br />
Once the winning distance is found, it can be used to reposition Sonic.<br />
<br />
======Distance Limits======<br />
As we know, sensors return a distance to the nearest surface, up to an extreme maximum of 32 pixels. If Sonic's floor sensors are within 32 pixels of the floor, the game may know the floor is there but we might not just want him to snap down right away. The game will test the distance found and react appropriately.<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While grounded:'''<br />
<br />
In Sonic 1, if the distance value is less than -14 or greater than 14, Sonic won't collide.<br />
In Sonic 2 onward however the positive limit depends on Sonic's current speed - in this case, (for when Sonic is on the floor) if the distance is greater than <br />
<br />
minimum(absolute(X Speed)+4, 14)<br />
<br />
then he won't collide. So the faster Sonic moves, the greater the distance Sonic can be from the floor while still being pulled back down to it. <br />
The -14 limit remains the same.<br />
<br />
If Sonic was in a sideways mode, such as on a wall, it would use Y Speed instead.<br />
</div><br />
<div class="large-6 columns"><br />
'''While airborne:'''<br />
<br />
While airborne, if the distance value is greater than or equal 0 (meaning the sensor isn't touching the floor yet) Sonic won't collide. <br />
</div><br />
</div><br />
=====Ledges=====<br />
<br />
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.<br />
<br />
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.<br />
<br />
=====Balancing On Edges=====<br />
<br />
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).<br />
<br />
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 <span style="color:#00f000; font-weight: bold;">A</span> is active and <span style="color:#38ffa2; font-weight: bold;">B</span> is not the ledge is to his right and vice versa.<br />
<br />
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 X Position is greater than the edge of the solid tile found by the active sensor.<br />
<br />
[[Image:SPGBalancingAnimated.gif|link=Special:FilePath/SPGBalancingAnimated.gif]]<br />
<br />
Assuming the right edge of the ledge to be an X position of 2655 ($0A5F), Sonic will only start to balance at an X Position of 2656 ($0A60) (edge pixel+1). He'll fall off at an X Position of 2665 ($0A69) (edge pixel+10) when both sensors find nothing.<br />
<br />
In ''[[Sonic 2]]'' and ''[[Sonic CD]]'', if the ledge is the opposite direction than he is facing, he has a second balancing animation.<br />
<br />
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 X Position of 2662 ($0A66).<br />
<br />
'''Note:''' While balancing, certain abilities are not allowed (ducking, looking up, spindash, 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.<br />
<br />
<br />
=== Ceiling Sensors (<span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span>) ===<br />
<br />
[[Image:SPGHitCeiling.gif|link=Special:FilePath/SPGHitCeiling.gif]]<br />
<br />
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. <br />
<br />
They perform in the exact same way simply up instead of down.<br />
<br />
However, they aren't active at the same times.<br />
<br />
====Method====<br />
<br />
When these sensors find a ceiling, much like the floor sensors the sensor which finds the smallest distance will win. The sensor that wins is the distance and angle used (and it's found tile is the one referenced).<br />
This winning distance can then be subtracted from Sonic's position.<br />
<br />
=====Distance Limits=====<br />
Distance limits here work in the same way as the floor sensors while airborne.<br />
<br />
<br />
=== Wall Sensors (<span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span>) ===<br />
<br />
[[Image:SPGPushingAnimated.gif|link=Special:FilePath/SPGPushingAnimated.gif]]<br />
<br />
<span style="color:#ff38ff; font-weight: bold;">E</span> sits at his left at X Position-'''Push Radius''', while <span style="color:#ff5454; font-weight: bold;">F</span> sits at his right at X Position+'''Push Radius'''.<br />
<br />
====Movement====<br />
<br />
'''Push Radius''' is always 10, placing <span style="color:#ff38ff; font-weight: bold;">E</span> to Sonic's left side, at X Position-10. While <span style="color:#ff5454; font-weight: bold;">F</span> is to his right, at X Position+10, giving Sonic a total width of 21 pixels when pushing.<br />
<br />
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 Y Position however while Sonic's Ground Angle is 0 (on totally flat ground) both wall sensors will move to his Y Position+8 so that he can push against low steps and not just snap up ontop of them.<br />
<br />
The horizontal sensors are always positioned at Y Position while airborne.<br />
<br />
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.<br />
<br />
That's not to say these sensors don't move though. They do, and a lot. <br />
As noted in [[SPG:Main Game Loop|Main Game Loop]] wall collision (while grounded) actually takes place before Sonic's position physically moves anywhere, so he wont actually be in a wall when he tries to collide with it. The game accounts for this by actually adding his X Speed and Y Speed to the sensor's position, this is where the sensor ''would'' be if Sonic had moved yet.<br />
<br />
====Method====<br />
<br />
Assuming the wall's left side to be at an X position of 704 ($02C0), Sonic cannot get closer than an X Position of 693 ($02B5). Assuming the wall's right side to be at an X position of 831 ($033F), Sonic cannot get closer than an X Position of 842 ($034A). Thus the distance between both sensors inclusive should be 21 pixels, stretching from Sonic's X Position-10 to X Position+10. <br />
<br />
When Sonic collides with a wall, this will set his Ground Speed to 0 if he is moving in the direction of the wall, not away from it.<br />
<br />
The distance value found by the sensor in it's given direction is used to stop Sonic at a wall.<br />
<br />
=====Distance Limits=====<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
'''While Grounded:'''<br />
The distances found by the wall sensors are used slightly differently while grounded.<br />
<br />
Naturally, the game will ignore a positive distance because he will not collide. If the sensor's distance is negative, this means that when Sonic's position actually does change, he will be inside the wall.<br />
<br />
In this case, because the sensor is actually out in front of Sonic (where he will be after he moves) instead of using the distance to reposition Sonic by directly changing his position, the game smartly uses the fact that Sonic has still yet to move within the current frame. All it has to do is add the distance to Sonic's X Speed (if moving right, or ''subtract'' the distance from Sonic's X Speed if moving left. This would be done to Y Speed if in wall mode). This results in Sonic moving when his position changes, right up to the wall, but no further. In the next frame, because Ground Speed has been set to 0 Sonic will have stopped just like in any other situation.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
'''While Airborne:'''<br />
<br />
Like normal, if the distance is negative and the sensor is inside the wall, he will collide. The game will ignore a positive distance.<br />
</div><br />
</div><br />
<br />
===Extra Sensors===<br />
Sonic cannot jump when there is a low ceiling above him. If there is a collision detected with sensors at Sonic's X Position-9 and X Position+9, at Y Position-25, Sonic won't bother jumping at all.<br />
<br />
<br />
===Sensor Activation===<br />
Knowing where the sensors are and what they do is only half the job since they are only sometimes active. This depends while you are grounded or airborne.<br />
<br />
====While Grounded====<br />
<br />
Floor Sensors <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> are always active while grounded, and will use their 1 tile extension to actively search for new floor below Sonic's feet. <br />
<br />
When grounded, Wall 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 Ground Speed is positive, Sonic's <span style="color:#ff38ff; font-weight: bold;">E</span> sensor is inactive, and while Ground Speed is negative, Sonic's <span style="color:#ff5454; font-weight: bold;">F</span> sensor is inactive.<br />
<br />
However this is not always the case, both wall sensors simply don't appear when Sonic's Ground Angle is outside of a 0 to 90 and 270 to 360 (or simply -90 to 90) degree range, meaning when you running around a loop, the wall sensors will vanish for the top half of the loop. In S3K however these sensors will also appear when Sonic's Ground Angle is a multiple of 90 in addition to the angle range.<br />
<br />
While grounded Ceiling Sensors <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> are never active, and Sonic won't check for collision with solid tiles above himself while on the floor.<br />
<br />
====While Airborne====<br />
<br />
While in the air, all sensors play a part to find ground to reattach Sonic to. But rather than have all active at once and risk lag, only 4-5 will be active at any given time.<br />
<br />
As you move, the game will check the angle of your motion (X Speed and Y Speed) through the air. It will then pick a quadrant by rounding to the nearest 90 degrees. (this is different to the Mode, this is simply a measurement of if you are going mostly left, right up or down). The quadrant can be more easily found by simply comparing the X Speed and Y Speed and finding which is larger or smaller.<br />
<br />
if absolute X Speed is larger then or equal to absolute Y Speed then<br />
if X Speed is larger than 0 then<br />
Sonic is going mostly right<br />
else<br />
Sonic is going mostly left<br />
else<br />
if Y Speed is larger than 0 then<br />
Sonic is going mostly down<br />
else<br />
Sonic is going mostly up<br />
<br />
Depending on the quadrant, different sensors will be active.<br />
<br />
When going '''mostly right''', Sonic's <span style="color:#ff5454; font-weight: bold;">F</span> sensor will be active, along with both <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors and the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors.<br />
<br />
When going '''mostly left''', it is the exact same as going right, but the <span style="color:#ff38ff; font-weight: bold;">E</span> wall sensor instead.<br />
<br />
When going '''mostly up''', both the <span style="color:#00aeef; font-weight: bold;">C</span> and <span style="color:#fff238; font-weight: bold;">D</span> ceiling sensors and the <span style="color:#ff38ff; font-weight: bold;">E</span> and <span style="color:#ff5454; font-weight: bold;">F</span> wall sensors are active.<br />
<br />
When going '''mostly down''', it is the same as going up, but the <span style="color:#00f000; font-weight: bold;">A</span> and <span style="color:#38ffa2; font-weight: bold;">B</span> floor sensors are active instead of the ceiling sensors.<br />
<br />
===Summary===<br />
<br />
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 sensors 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 sensors snap in 90 degree rotations resulting in four modes, this is covered in [[SPG:Slope Physics|Slope Physics]].<br />
<br />
[[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.''<br />
<br />
====Bugs Using This Method====<br />
<br />
Unfortunately, there are a couple of annoying bugs in the original engine because of this method.<br />
<br />
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.<br />
<br />
[[Image:SPGSlopeBug1Animated.gif|link=Special:FilePath/SPGSlopeBug1Animated.gif]]<br />
<br />
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.<br />
<br />
There are only a few areas where this is noticeable, but it applies to all Mega Drive titles and is pretty tacky.<br />
<br />
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 (Sonic the Hedgehog 16-bit)|Green Hill Zone]] and [[Marble Zone]].<br />
<br />
[[Image:SPGSlopeBug2Animated.gif|link=Special:FilePath/SPGSlopeBug2Animated.gif]]<br />
<br />
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.<br />
<br />
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.<br />
<br />
==Notes==<br />
* Find information on how Sonic's momentum and slope handling work in the [[SPG:Slope_Physics|Slope Physics]] guide.<br />
<br />
[[Category:Sonic Physics Guide]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Solid_Objects&diff=323854SPG:Solid Objects2021-03-28T10:28:39Z<p>Lapper2: /* General Solid Object Collision */ Changing nonsense to usable info</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
<br />
==Introduction==<br />
<br />
There are many objects in Sonic games and they interact with Sonic in many different ways and are a very different beast than Solid Tiles. Object-player collision doesn't work the same way as [[SPG:Solid_Tiles|Solid Tiles]]. This guide will go over the collision of various objects found in the Sonic trilogy.<br />
<br />
==Object Hitboxes==<br />
<br />
Objects such as rings, enemies, and bumpers have a hitbox, which is a general area that will trigger some kind of reaction with Sonic when both their hitboxes overlap. Object hitboxes are centered on the object's X and Y Positions. <br />
<br />
Sometimes objects which seem solid (like bosses or bumpers) actually only have a hitbox, and when they overlap, simply push Sonic in the other direction. As a general rule, any seemingly solid object that Sonic cannot stand on is most likely actually using a hitbox rather than real solidity.<br />
<br />
===Hitbox Reaction===<br />
<br />
If Sonic's hitbox touches an object's hitbox, some kind of reaction will occur. Usually this is totally specific to the object, like collecting a ring or bursting a balloon. Though, the object can set specific flags which change the "type" of reaction they will have. The two most consistent reaction types are as follows:<br />
<br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Hurt Hitboxes====<br />
While these aren't solid, any contact with them will immediately give damage to Sonic. Examples are the spikes on the GHZ log bridge, the spikes under a MZ Spike Trap, and certain projectiles.<br />
</div><br />
<div class="large-6 columns"><br />
====Badnik Hitboxes====<br />
These are very similar to hurt hitboxes, with the obvious difference being that while rolling, you won't take damage and will instead destroy the enemy.<br />
</div><br />
</div><br />
<br />
Ahead, objects with hurt hitboxes will be coloured differently.<br />
<br />
===Sonic's Hitbox===<br />
<br />
In order to interact with other object's hitboxes, Sonic needs his own.<br />
<br />
[[Image:SPGHitBoxes.png]]<br />
<br />
Sonic's hitbox is much like that of any other object. It sits at Sonic's X Position and Y Position. It has a width radius of 8, and its height radius is always 3 pixels shorter than Sonic's Height Radius, making it 17 X 33 pixels in size while standing.<br />
<br />
When crouching, Sonic's hitbox needs to shrink. Problem is, Sonic's position and Height Radius don't actually change at all while crouching. So to tackle this it manually updates the hitbox's size and position while Sonic crouches, where 12px is added to the hitbox's Y position, and the hitbox's height radius is set to 10.<br />
<br />
===Quirks With Hitboxes===<br />
<br />
Because these hitboxes aren't even numbered in size, and because object origins don't lay perfectly centered between pixels (rather they are 1px left and down) most hitboxes will also appear 1px too big on the right side and the bottom side. This is simply how things work in-game and for that reason won't be ignored. Sprites like rings are even-numbered sizes (such as 16 X 16) so an anomaly like the following can (and does, always) occur.<br />
<br />
[[Image:SPGRingTest.gif]]<br />
<br />
Rings can be collected from one direction sooner than the other, you can try it yourself via debug mode. As long as the sprite of the ring is 4px out from the tiles on each side, you'll experience this inconsistency.<br />
A Ring's hitbox is defined as a radius of 6, but this results in a box with a width of 13 rather than 12. Because all sprites like this are an even size, but their hitbox must be odd, the box cannot be perfectly set on the sprite and will be larger to the left and bottom.<br />
<br />
This is the case with any object's hitboxes.<br />
<br />
The same is true for solid object boxes, so Sonic will push against objects 1px further away when facing leftwards than he will tiles.<br />
<br />
==Solid Objects==<br />
<br />
As stated above, object collision is totally separate from tile collision. Sonic does not collide with objects using his solid tile sensors, instead, special calculations are used to check if Sonic's general shape is inside an object's solid box, and push him out.<br />
<br />
This all occurs after Sonic's code has been executed for that frame, including his tile collision and movement. Since objects run their code after Sonic, it's the job of the objects to push Sonic out of themselves.<br />
<br />
''Note: These collisions deal with floored positions (integer values), ignoring any fractional/subpixel amount.''<br />
<br />
===General Solid Object Collision===<br />
<br />
Solid object collision does away entirely with hitboxes and instead uses the ''actual'' size of the objects. The Width Radius and Height Radius. For normal objects that is, Sonic will use his Height Radius for this too, but horizontally he of course needs to use his Push Radius instead.<br />
<br />
The following is long. It is written in a way very close to how the original game has it coded because accuracy requires it. To orient yourself, a brief overview of the long process below goes as follows:<br />
* Sonic will check if he is overlapping the object.<br />
* Sonic will decide which side of the object he is nearest to n both axis (either left or right and either top or bottom).<br />
* Then check how close in pixels he is to being outside of the object on that side (distance to left or right and distance to top or bottom).<br />
* The game then decides whether he's closer to a horizontal side to be pushed out on the x axis or a vertical side to be pushed out on y axis.<br />
* He will then be pushed out towards that side on that axis by the distance he overlaps. <br />
<br />
Now, let's get into the details.<br />
<br />
The first thing the object collision code does is check if Sonic is standing on the object (using a flag which is set if he lands on one). If he is, it will skip straight to checking if Sonic has walked off the edges rather than general object collision. Otherwise, it will continue as follows.<br />
<br />
====Checking For An Overlap====<br />
To check for an overlap the game needs to know the boundaries that Sonic's positions need to be within.<br />
<br />
Horizontally, the object combines its own X radius with Sonic's Push Radius and adds 1px extra (so Push Radius + 1).<br />
<br />
Vertically, it very similar. The object combines its own Y radius with Sonic's current Height Radius to get a combined radius. 1px isn't added here, but it is (kind of) later after a collision has occurred.<br />
<br />
''Note: Objects don't always use their real Y radius when acting solid, and will just pass in a hard-coded value. Sometimes this value is slightly different than you'd expect, like 17 instead of 16. Point being, it's worth checking each individual object for the specific values they use. Generally speaking, the above applies to most.''<br />
<br />
Here's a demonstration of how these new radiuses relate to Sonic's size (while standing in this case) for a block.<br />
<br />
[[Image:SPGSolidObjectOverlap.gif]]<br />
<br />
From this point, when I refer to the object's combined radiuses I will call them '''combined X radius''' and '''combined Y radius'''.<br />
<br />
Now all the game needs to worry about is Sonic's X Position and Y Position being within this new box, it no longer needs to worry about what Sonic's sizes are.<br />
<br />
<div class="large-12 columns"><br />
<div class="large-6 columns"><br />
=====Horizontal Overlap=====<br />
<br />
The game will calculate the difference between Sonic's X Position and the object's left side.<br />
<br />
left_difference = sonic's X Position - object's x position<br />
left_difference += combined_x_radius //add combined radius<br />
<br />
Then, it will check if this new difference value has passed the boundaries, and exit the object collision if it has.<br />
<br />
//sonic is too far to the left to be touching<br />
if (left_difference < 0) exit;<br />
<br />
object_x_diameter = combined_x_radius * 2 //get object's x diameter<br />
<br />
//sonic is too far to the right to be touching<br />
if (left_difference > object_x_diameter) exit; <br />
<br />
If no exit occurred, Sonic is overlapping on the X axis, and it will continue. The game will remember this '''left difference'''. <br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Overlap=====<br />
<br />
Then for vertical overlap, it calculates the difference between Sonic's Y Position and the object's top side.<br />
<br />
top_difference = sonic's Y Position - object's y position<br />
top_difference += 4 //add 4<br />
top_difference += combined_y_radius //add combined radius<br />
<br />
The game also allows Sonic to be slightly above the object by 4 pixels and still overlap, extending the top of the object 4 pixels for extra overlap. This is likely just in case the object moves down slightly or the object is slightly lower than a previous ledge Sonic was standing on. The game does this by effectively pretending Sonic is 4px lower than he really is when checking the Y overlap. This is subtracted later.<br />
<br />
Then, it will check if this new difference value has passed the boundaries, and exit the object collision if it has.<br />
<br />
//sonic is too far above to be touching<br />
if (top_difference < 0) exit;<br />
<br />
object_y_diameter = combined_y_radius * 2 //get object's y diameter<br />
<br />
//sonic is too far down to be touching<br />
if (top_difference > object_y_diameter) exit; <br />
<br />
If no exit occurred, Sonic is overlapping on the Y axis, and it will continue. The game will remember this '''top difference'''. <br />
</div><br />
</div><br />
<br />
====Finding The Direction of Collision====<br />
If Sonic is found to be touching the object, the game will then decide whether he is to be popped out the top or bottom, or the left or right of the object. The game will compare Sonic's position to the object's position to determine which side he is on. <br />
<br />
To do this, the game will first determine which side Sonic is in comparison with the object's position. <br />
<br />
If Sonic's X Position is greater than the object's X position, he's on the right, otherwise, he's on the left. If Sonic's Y Position is greater than the object's Y position, he's on the bottom, otherwise, he's on the top.<br />
<br />
After the side is determined for each axis, the game will calculate a distance to the nearest edge. <br />
<br />
<div class="large-12 columns"><br />
<div class="large-6 columns"><br />
=====Horizontal Edge Distance=====<br />
If Sonic is on the left, the edge distance is simply equal to the '''left difference''' which is the distance to the left side and will be a positive number. <br />
<br />
If Sonic is on the right, the distance will be flipped around.<br />
<br />
x_distance = left_difference - object_x_diameter<br />
<br />
This is effectively the distance to the object's right side and will be a negative number. <br />
<br />
Whichever side it is, we will call this new distance the '''x distance'''.<br />
</div><br />
<div class="large-6 columns"><br />
=====Vertical Edge Distance=====<br />
It is the same along the Y axis. If Sonic is on the top, the edge distance is simply equal to the '''top difference''' which is the distance to the left side and will be a positive number. <br />
<br />
If Sonic is on the bottom, the distance will be flipped around (and that extra 4px from before will be subtracted).<br />
<br />
top_difference -= 4<br />
y_distance = top_difference - object_y_diameter<br />
<br />
This is effectively the distance to the objects bottom side and will be a negative number. <br />
<br />
Whichever side it is, we will call this the '''y distance'''.<br />
<br />
''Note: If Sonic is on the top, the extra 4px isnt subtracted yet. It will be subtracted upon landing.''<br />
</div><br />
</div><br />
<br />
=====Choosing The Direction=====<br />
Finally, with all of this information, the game can decide which way Sonic should be popped out. <br />
<br />
It does this by finding which side Sonic is nearer to, which makes sense.<br />
<br />
if (absolute(x distance) > absolute(y distance))<br />
{<br />
collide vertically<br />
}<br />
else<br />
{<br />
collide horizontally<br />
}<br />
<br />
Here's a visual example of what axis Sonic would collide depending on his X Position and Y Position within the solid area of a block.<br />
<br />
[[Image:SPGSolidObjectNearerSide.png]]<br />
<br />
The horizontal axis is favoured just a little more than the vertical. Keep in mind this exact pattern is only valid for an object of this exact size and while Sonic is standing.<br />
<br />
From there, the game can easily tell which way to pop out Sonic on either axis depending on the sign of the distance value. When colliding vertically, the game knows that Sonic is on top if the '''y distance''' is positive, and underneath if the '''y distance''' is negative. Same goes for left and right and the '''x distance'''.<br />
<br />
====Popping Sonic Out====<br />
Once a collision has occurred and the game had decided the direction Sonic then needs to be "popped out" of the object so that he is no longer in it, and his speeds need to be changed. But where does it put Sonic? Well, there's also an even greater use for the '''x distance''' or '''y distance'''. They are the exact distance Sonic needs to move to exit the object, but reversed. So when he is popped out, they will simply be subtracted from his position.<br />
<br />
=====Popped Left and Right=====<br />
Popping Sonic out left or right will simply reset his speeds and position, and set him to pushing if he is grounded.<br />
<br />
There are a couple of conditions. The game will only bother popping Sonic out if absolute '''y distance''' is greater than 4. The game will also only bother touching Sonic's speeds if '''x distance''' is not 0.<br />
<br />
If the game does decide to affect Sonic's speeds, this also depends on a few factors. If Sonic is on the left and the game has decided he needs to be popped out to the object's left side, it will only stop Sonic's speeds if Sonic is moving right (X Speed > 0), towards the object. The same is true for colliding with the right, but if Sonic is moving to the left (X Speed < 0). Basically, he must be moving towards the object. When his speeds are stopped, X Speed and Ground Speed are set to 0.<br />
<br />
Regardless, '''x distance''' will be subtracted from Sonic's position, popping Sonic out of the object.<br />
<br />
A few other things happen behind the scenes, such as the object is told it is being pushed, and Sonic is told he is pushing.<br />
<br />
=====Popped Downwards=====<br />
If Sonic bumps the bottom of an object, the game will check if Sonic is moving vertically (Y Speed is not 0). If not, the game then checks if Sonic is standing on the ground, and if he is, kills him from crushing, then exits. <br />
<br />
Otherwise, the game checks if 'Y Speed' is less than 0. If not, it will exit as Sonic is moving down and away from the object. <br />
<br />
Finally, if the '''y distance''' is smaller than 0, the game will subtract '''y distance''' from his Y Position and set his Y Speed to 0. <br />
<br />
=====Popped Upwards=====<br />
If the game decides Sonic is to be popped out upwards, Sonic will land on the object.<br />
<br />
Before it does this, it checks if '''y distance''' is less than 16. If it is, the game will exit the landing code.<br />
<br />
Then the game subtracts the 4px it added earlier from '''y distance'''.<br />
<br />
Next, it will scrap the '''combined X radius''' we were using before, and find a new X radius, this being the actual X radius of the object, not combined with anything at all. So 16 in the case of a push block for example. It will then compare Sonic's position using this radius.<br />
<br />
action_radius = 16 //an example<br />
action_diameter = action_radius*2<br />
<br />
x_comparison = action_radius plus object's X position<br />
x_comparison -= sonic's X Position<br />
<br />
//if sonic is too far to the right<br />
if (x_comparison is less than 0)<br />
{<br />
exit;<br />
}<br />
<br />
//if sonic is too far to the left<br />
if (x_comparison is greater than or equal to action_diameter)<br />
{<br />
exit;<br />
}<br />
<br />
This means Sonic will exit the landing and will just slip off the side keep falling if his "X Position" position isn't directly above the object, which is actually quite strange as it's as if Sonic is only 1 pixel thick.<br />
<br />
The last check is if Sonic's Y Speed is negative, he wont land and it will exit.<br />
<br />
Finally, if it reaches this far, he will land. From this point, it's rather simple.<br />
<br />
The game subtracts '''y distance''' from Sonic's Y Position. It also subtracts an extra 1px afterwards to align him correctly.<br />
<br />
Then it resets Sonic to be grounded, similarly to normal [[SPG:Solid_Tiles#Reacquisition_Of_The_Ground|Reacquisition Of The Ground]] but simply sets Sonic's Y Speed to 0, and his ''ang'' to 0. Also, the game will set a flag telling the game Sonic is on the object. <br />
<br />
Finally, Sonic's Ground Speed is set to equal his X Speed.<br />
<br />
====Specifics====<br />
<br />
As mentioned in [[SPG:Basics|Basics]], Sonic's collisions with tiles and objects only concern themselves with Sonic's floored position (his pixel position), and the same applies to the object itself. So, upon the point of contact, Sonic's floored X Position finds himself overlapping the object. He is then pushed out by this difference. Since this difference only accounts for the distance between floored values, it's a whole number. Meaning if Sonic was 1px inside the object's right side while he has an X Position of 1.75, after being pushed out he'd have an X Position of 2.75, as a rough example. <br />
<br />
So after being popped out, if Sonic keeps trying to walk towards it, he has to cover the rest of the distance of the pixel he's currently in before his pixel position overlaps the object again. This amounts to contact being made every 4 frames or so.<br />
<br />
===Standing On Solid Objects===<br />
Unlike tiles, which are an organised simple grid of data that can be easily checked each frame, objects are more expensive to check for. <br />
<br />
So when standing on top of an object, rather than check beneath Sonic each frame to ensure he's still touching it and to move him with it, the game sets a '''standing-on-object flag''' which will effectively glue Sonic to an object when he lands on it. <br />
<br />
The flag's job is making him stick to the object's surface and stay grounded, even though he's not touching any Solid Tiles (as far as his tile sensors are concerned, Sonic is in the air while standing on an object). This flag will only be unset when walking off the edge of an object or jumping/getting hurt.<br />
<br />
====Walking Off The Edges====<br />
If Sonic is standing on an object, the game will only check if Sonic has walked off of it.<br />
<br />
First, it calculates a distance to the object's left side.<br />
<br />
x_left_distance = sonic's X Position - the object's X //get the position difference<br />
x_left_distance += combined X radius //add the combined X radius<br />
<br />
Sonic will have walked off the edge if this distance is less than 0 or is greater than or equal to ('''combined X radius''' * 2). When this happens, the '''standing-on-object flag''' is unset and Sonic is no longer grounded.<br />
<br />
====Moving On Platforms====<br />
After all checks are complete and if Sonic is still on it, the game handles moving Sonic with the object and keeping him stuck to it.<br />
<br />
===Bugs Using This Method===<br />
Overall, this method for collision with objects is pretty well made. However, there are a few obvious problems that become apparent when you mess with objects enough.<br />
<br />
====Slipping====<br />
As mentioned, since landing on the top of objects doesn't measure using the same radius as the rest of object collision, bizarrely this means if you jump down towards the corner of an object, you'll slip right off the sides because it exits the landing code if Sonic's position isn't right above the object. This appears to be deliberate as the smaller radius is very explicitly used, but doesn't add any benefit as far as I can tell.<br />
<br />
[[Image:SPGObjectBugSlipping2.gif]]<br />
<br />
The way the object collision code is executed, being from inside each object in order, there's effectively a priority system in place. If two objects want to push Sonic two conflicting ways, the one who executes their solid object code last will win out. Sonic cannot be pushed by two or more objects at the same time in 2 different ways. The result of this, and partly thanks to the edge slipping mentioned above, Sonic can very easily slip between two objects which haven't been placed perfectly touching next to each other.<br />
<br />
[[Image:SPGObjectBugSlipping1.gif]]<br />
<br />
Sonic will collide on top with both spikes, but his position isn't directly over either of them, so he will slip down the sides. Next, both spikes will try and push him with their sides, but only the last spike to do so will actually result in a net position change.<br />
<br />
====Bottom Overalap====<br />
When the vertical overlap is being checked, the game pretends Sonic is 4px lower than he actually is. This allows 4px of extra "grip" to the top of objects, however it also effectively removes 4px from underneath them. When jumping up into an object, Sonic will be able to enter it by around 4px before being popped out. Though, this is hard to notice during normal gameplay.<br />
<br />
[[Image:SPGObjectBugBottom.gif]]<br />
<br />
====False Object Standing Flag====<br />
This final bug is less of a design flaw and more of a major bug.<br />
<br />
If for some reason the object you are standing on is deleted or otherwise unloaded, and the game fails to reset the '''standing-on-object flag''' you can then start walking through the air. This is because the flag is telling the game that Sonic is still grounded even though there's no longer any object to be grounded to. Because Sonic's grounded, he won't fall. Additionally, he also won't be able to walk off the object's sides as the object isn't even there to check for it.<br />
<br />
==Object Specific Collision==<br />
<br />
While a general description of Solid Object collision may cover a pushable block or a solid rock, not all objects behave the same. Some objects have slopes, and some will change what kind of solidity they have to suit different situations.<br />
<br />
===Objects That Collide===<br />
<br />
Some objects like walking enemies, pushable blocks, and item monitors all have to land on and stick to solid ground. They typically do this by casting a single downward sensor, much like Sonic does, at their central bottom point. The same applies to wall collision.<br />
<br />
===Sloped Objects===<br />
<br />
You may have noticed some objects in the classic games are sloped, rather than box shaped. Like the Collapsing GHZ platform, the large platforms from marble, diagonal springs, or even the Spring Ramps in S2.<br />
<br />
This is achieved by using the same code as normal, but injecting a different value to use as the surface y position of the object. To get this y position, the game checks against a sloped object's height array:<br />
<br />
[[Image:SPGSlopedObjects.png]]<br />
<br />
This height array is relative to the object's Y Position, and is centred on it's X Position. The game stores these height arrays compressed at half the size, as shown above. This is possible because the slopes never need to be steeper than a step of 2 pixels, so the game simply "stretches out" the array information when the array is read.<br />
<br />
When a sloped object is acting solid to sonic, instead of using the y position of the top of the solid box, it instead reads the value from the array, and from there as far as the game is concerned, the object is as high as the array tells it. This continuously happens as Sonic passes over the object, resulting in smooth motion.<br />
<br />
====Differences To Tiles====<br />
<br />
There are no real angle values because the array is height information only, and no sensors are being used here. This means that Sonic will have an angle value of 0 as he walks on a sloped object, and won't jump off or be affected by slope physics at all. In addition, Sonic will be slightly "deeper" into the slopes than he would on solid tiles. This is because his centre point is always snapped to the slope, rather than one of his side floor sensors. It's most likely for these reasons that objects do not have angles steeper than what is shown above.<br />
<br />
===Jump Through Platforms===<br />
<br />
Jump through platforms are small objects which are only solid from the top. Since all Sonic can do with platforms is land on them, they use their own code to check for just that, and in a more scrutinised way.<br />
<br />
First, it will check if Sonic's Y Speed is less than 0. If it is, it will exit the collision. This means it will only check for overlap with Sonic while he is moving down or staying still. This is why Sonic can jump right up through it.<br />
<br />
====Horizontal Overlap====<br />
Next, it will check for X overlap in the exact same way that it does when landing on a normal solid object, using the object's normal X radius. Complete with all it's issues. If there's an overlap, it will continue.<br />
<br />
====Vertical Overlap====<br />
<br />
Next, the Y overlap is where things get interesting.<br />
<br />
The game calculates the platform's surface Y coordinate by subtracting the Y radius from the Y position.<br />
<br />
Then Sonic's bottom Y is calculated by adding his Height Radius to his Y Position. It also adds 4 to this bottom Y for much the same reason as the normal solid object collision, it allows Sonic to collide even when he's 4 pixels above.<br />
<br />
The first check is if the platform's surface Y is greater than Sonic's bottom Y. If it is, it will exit as the platform is too low.<br />
<br />
Next, it will check a distance between Sonic's bottom and the platform's surface (platform's surface Y minus Sonic's bottom Y). If the distance is less than -16 or is greater than or equal to 0, it will exit as Sonic is too low.<br />
<br />
If it reaches past all those checks, Sonic will land.<br />
<br />
====Popping Sonic Out====<br />
<br />
The distance from before is added to Sonic's Y position, plus an extra 3px. After this the normal landing-on-object things occur, such as setting his speeds and '''standing-on-object flag'''.<br />
<br />
====Walking Off Edges====<br />
<br />
Platforms also use a different walking off edges code to normal Solid Objects. And since it's up to objects what width radius they want to use, things can get a little inconsistent. It's mentioned above that objects add Sonic's radius to get a combined radius. This actually isn't always the case. Sometimes objects will just provide their unaltered width radius which is the case with platforms. This means not only will Sonic fall through the corners of platforms like any other object, but he will also walk off them just as easily, way sooner than he really should, unlike the normal object collision.<br />
<br />
This was probably missed because Sonic doesn't need to push against these platforms, so it's much harder to notice if Sonic's Push Radius hasn't been applied. <br />
<br />
After this of course, Sonic is still standing on it, so the game handles updating Sonic's position on the object and moving him if the object is moving.<br />
<br />
Worthy of note, is that many objects share the platform's "walking off edges" code.<br />
<br />
Sloped objects, like those that fall in Green Hill, and these platforms also provide the actual X radius rather than a combined one. There's also a likely reason for this too.<br />
<br />
These have slope data, which is a height array, and the value returned from this is based on Sonic's X Position. So Sonic stands on them, and his current height on the platform is decided based on his X position relative to the object. If Sonic's Push Radius was added to the x radius used for Sonic to walk off the edges, and if Sonic could then hang off the edges slightly, the current height of the object's slope would be invalid causing odd results like Sonic being sent somewhere weird vertically due to an invalid height being read. This could be solved by just using the first or last height array value if Sonic's X Position is outside of the platform boundary.<br />
<br />
''Note: The code itself isn't the issue, the issue is moreso that the objects can far more easily pass in a radius that isn't combined when they use this because the general solid object code also uses the radius for pushing and for walking off, which requires it to be combined.''<br />
<br />
===Pushable Blocks===<br />
<br />
Pushable blocks (specifically the type found in Marble Zone) are essentially normal solid objects, except for the fact when you are pushing them move. They move rather slowly, and you might assume that it sets the block and Sonic's speeds to some value like 0.3, but this is not the case.<br />
<br />
The block actually moves 1 entire pixel whenever you touch it from the side. But that sounds much faster than they actually move right? Well, in practice the block will only move once around every 3 frames. And the reason for this is rather technical to say the least and requires that you properly emulate the way the original game's positions work.<br />
<br />
====Upon Contact====<br />
When Sonic has contacted the push block, Sonic has been popped out, and his speeds have been set to 0, the push block will then do some extra things. If Sonic pushed to the left, both Sonic and the block will move 1 pixel to the left, Sonic's X Speed is set to 0 and Ground Speed is set to -0.25. If he pushed to the right, both Sonic and the block will move 1 pixel to the right, Sonic's X Speed is set to 0 and Ground Speed is set to 0.25.<br />
<br />
After being popped out Sonic is no longer touching the object. When this happens, Sonic's pixel position has been altered, but his subpixel position remains the same. So if Sonic was half a pixel into the object before, he's now half a pixel outside of it. Before he makes contact with the object again, he needs to cover this subpixel distance. This would normally take around 4 frames for a static wall, but here it instead takes 2-3 frames because he is given a headstart when his Ground Speed is set to .25.<br />
<br />
Because the mechanics of movement within 256 subpixels are difficult to explain or visually demonstrate, here's what a few frames of pushing a pushable block to the right would look like:<br />
<br />
Frame 0:<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.97265625 -- added X Speed to X Position. Sonic's subpixel position (.972) is very close to entering the next pixel, which is where he will collide again.<br />
<br />
Frame 1:<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.36328125 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2669.36328125 -- 1 subtracted from X Position<br />
<br />
-- The push block runs its own code and both are moved to the right by 1 pixel, and Sonic's Ground Speed is set.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.36328125 -- 1 added to X Position<br />
<br />
At this point, Sonic has just pushed the block and has been moved out of it, then along with it. The fractional part of his position is currently .363 , just left of halfway through the pixel.<br />
<br />
Frame 2 (1 frame since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2670.66015625 -- added X Speed to X Position<br />
<br />
Frame 3 (2 frames since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.00390625 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2670.00390625 -- 1 subtracted from X Position<br />
<br />
-- Sonic makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This only took 2 frames, because Sonic's subpixel was positioned just right on the previous push, which is very rare.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.00390625 -- 1 added to X Position<br />
<br />
Sonic has just pushed the block again, and has been moved out of it, then along with it. It took 2 frames. This time, the fractional part of his position is currently .003 , the very left of the pixel. This means he has farther to travel to reach the block again.<br />
<br />
Frame 4 (1 frame since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.296875 -- added acc to Ground Speed<br />
X Speed: 0.296875 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.30078125 -- added X Speed to X Position<br />
<br />
Frame 5 (2 frames since last push):<br />
-- Sonic gains speed along the floor naturally<br />
Ground Speed: 0.34375 -- added acc to Ground Speed<br />
X Speed: 0.34375 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2671.64453125 -- added X Speed to X Position<br />
<br />
Frame 6 (3 frames since last push):<br />
-- Sonic gains speed along the floor naturally and moves his position<br />
Ground Speed: 0.390625 -- added acc to Ground Speed<br />
X Speed: 0.390625 -- X Speed set to Ground Speed (on flat ground)<br />
X Position: 2672.03515625 -- added X Speed to X Position. Sonic's X pixel has changed<br />
<br />
-- Sonic makes contact with push block and is popped out to the left.<br />
Ground Speed: 0 -- Ground Speed set to 0<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2671.03515625 -- 1 subtracted from X Position<br />
<br />
-- Sonic makes contact with push block and both are moved to the right by 1 pixel.<br />
-- This time, it took 3 frames, which is far more common.<br />
Ground Speed: 0.25 -- Ground Speed set to 0.25<br />
X Speed: 0 -- X Speed set to 0<br />
X Position: 2672.03515625 -- 1 added to X Position<br />
<br />
Sonic has just pushed the block again, and has been moved out of it, then along with it. This time it took 3 frames thanks to his subpixel/fractional positions being allowed to wrap around and never reset. This 3 frame delay is the most common and is effectively the push speed.<br />
<br />
The reverse would have the exact same timings. It seems they deliberately controlled this delay by adding .25 to his Ground Speed. <br />
<br />
If you simply want to ''roughly'' copy it without the specifics or nuances of this system or you are using different object collision, just make a timer which triggers a movement every 3 frames while Sonic is pushing.<br />
<br />
To see what happens to a push block once it is pushed off a ledge, see [[SPG:Game_Objects#Pushable_Blocks|Game Objects]].<br />
<br />
===Item Monitor===<br />
<br />
Item Monitors, as you may have noticed, are not always solid. While you can stand on them you can also go right through them while jumping or rolling. The game is actually checking what Sonic is doing and changing how the Item Monitor will react.<br />
<br />
While not curled up in a ball, the Item Monitor acts as solid. The Item Monitor's hitbox isn't accessible at this time.<br />
<br />
While curled, however, the item box will no longer have any solidity at all, and its hitbox is active and accessible. The hitbox collision is what destroys the Item Box and bounces Sonic off (Details in [[SPG:Rebound#Monitors|Monitors Rebound]]).<br />
<br />
However, there is an exception. If Sonic is moving up (Y Speed < 0) while curled, the Item Box will in fact still act solid. Additionally, if Sonic's Y Position-16 is smaller than the item box's Y Position, the item box will bounce up with a Y Speed of -1.5 knocking the Item Box upwards, and Sonic's Y Speed will be reversed.<br />
<br />
[[Category:Sonic Physics Guide|Solid Objects]]</div>Lapper2https://info.sonicretro.org/index.php?title=SPG:Game_Objects&diff=323833SPG:Game Objects2021-03-25T18:29:17Z<p>Lapper2: /* Sonic 1 Objects */ Badnik tile collision info</p>
<hr />
<div>Notes: <br />
*Research applies to all four of the [[Mega Drive]] games, and [[Sonic CD]]. If there are any varying differences between the games, this will be covered below.<br />
*Variables and constants for Sonic and other characters such as X Position and ''acc'' will be referenced frequently, they can be found in [[SPG:Basics|Basics]].<br />
*An object's actual Width Radius and Height Radius are separate to an object's hitbox width radius and height radius.<br />
<br />
==Introduction==<br />
<br />
Objects move in various ways, some simple and some rather complex. It may be enough to simply observe an object to know how it acts, but this isn't the case most of the time where greater depth is required. <br />
<br />
==Hitboxes==<br />
<br />
Hitboxes are the game's simplified way of giving an object a size. Each object has it's own size defined with a Width Radius and a Height Radius, and they aren't always obvious. Visual examples will be given for most objects in this guide.<br />
<br />
''Note: More detailed information about how hitboxes and solid objects work, as well as Sonic's hitbox, can be found at [[SPG:Solid_Objects|Solid Objects]].<br />
<br />
==General Objects==<br />
General objects appear in many zones and games and the code should be the same between them.<br />
<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Rings===<br />
<br />
[[Image:SPGRingHitbox.png]]<br />
<br />
Rings have a hitbox with a width radius of 6 and a height radius of 6, resulting in a 13 x 13 rectangle. (while their sprite is larger, at 16px), so Sonic can get quite close to a ring before a collision occurs and he collects it.<br />
<br />
When a ring is bouncing around, it has a Width Radius of 8 and a Height Radius of 8 resulting in a 17 x 17 rectangle.<br />
<br />
===Springs===<br />
<br />
Red [[Springs|springs]] propel [[Sonic]] at a speed of 16, and yellow springboards at a speed of 10. If the spring faces up or down, the value is either negative or positive, respectively, and Y Speed is set to it. If the spring faces left or right, the value is either negative or positive, respectively, and X Speed is set to it. Vertical springboards don't affect X Speed and likewise horizontal springs don't affect Y Speed.<br />
<br />
For the most part Springs are simple solid objects which activate if Sonic touches the correct side.<br />
<br />
====Horizontal Springs====<br />
Much like vertical springs, horizontal springs will activate when you push into them. <br />
<br />
However in [[Sonic 2 (16-bit)]] onwards, if Sonic is standing on a spring facing right and you slowly step off it and land on the floor nearby (moving right without turning) the spring will activate, even though it is impossible to have pushed into the spring. So something extra is occurring.<br />
This happens because if Sonic is not moving towards the spring (so either standing still or moving away), the game checks if his X and Y positions are within a box which surrounds the spring. This box is much larger than the spring itself, spanning vertically from the spring's Y - 24 to the spring's Y + 24<br />
and horizontally from the spring's X to the spring's X + (40 in the spring's direction).<br />
<br />
The result of this is you can walk up to a spring, stop, and if you are within 40 pixels of it you will be propelled away. Keeping in mind the normal Width Radius for the solid area of these Springs is 8, this can happen incredibly and noticeably early. This is all in addition to the normal spring activation by touching the actual side of it.<br />
<br />
Horizontal springs also only propel sonic while he is grounded.<br />
<br />
=====Control Lock=====<br />
<br />
When Sonic bounces away from a horizontal spring (red or yellow), he cannot brake or otherwise affect his X Speed for 16 steps. The engine achieves this by setting the same horizontal control lock as when sliding back down steep inclines (in S3&K, bytes $32-33 in the player object's status table). Why lock the horizontal controls? The player is likely to be pressing in the direction of the spring as they run into it, and this would cause Sonic to bounce away in his braking animation. Temporarily ignoring input is a quick and elegant solution.<br />
<br />
====Diagonal Springs====<br />
<br />
There are no diagonal springs in [[Sonic the Hedgehog (16-bit)]]. But there are in [[Sonic 2 (16-bit)]], [[Sonic 3 & Knuckles|3, K]], and [[Sonic CD|CD]]. Sonic 2, 3, and K work the same way, but Sonic CD is different.<br />
<br />
In Sonic 2, 3, and K, a diagonal spring sets both X Speed and Y Speed to the spring's value, with the appropriate sign. So a red spring facing up and to the right sets Y Speed to -16 and X Speed to 16. The trouble with this method is that Sonic is technically bounced faster diagonally than horizontally or vertically. This is because they didn't bother to calculate the sine functions.<br />
<br />
In Sonic CD, they do however. Conveniently, the absolute sine and cosine of a 45 degree angle are the same, so you only need one value. It comes out to 11.3125 for Red springs and 7.0703125 for Yellow ones.<br />
<br />
===Item Monitors===<br />
<br />
When bumped from the bottom, Item monitors are given a Y speed of -1.5. They have a gravity of 0.21875 while falling.<br />
<br />
Item Monitors can act solid and also have a hitbox, depending on if Sonic is curled up or not, as well as other factors. They have a Width Radius of 16 and a Height Radius of 16, resulting in a 33 x 33 rectangle. The hitbox is the same.<br />
<br />
</div><br />
<div class="large-6 columns"><br />
<br />
===Bumpers===<br />
<br />
Bumpers such as those in [[Spring Yard Zone]] set Sonic's X Speed to 7*cosine(p), and Y Speed to 7*-sine(p), where p is the angle measured from the bumper's centre to Sonic's. This is regardless of Sonic's velocity when he hits the bumper. <br />
<br />
[[Image:SPGBumperHitbox.png]]<br />
<br />
Bumpers have a hitbox with a width radius of 8 and a height radius of 8, resulting in a 17 x 17 rectangle. Other than the hitbox speed repulsion, there is no solidity to bumpers.<br />
<br />
===Breakable Blocks and Rocks===<br />
<br />
When Sonic jumps on top of breakable objects, such as the rocks in [[Hill Top Zone]], blocks in [[Marble Zone]], or the tube caps in [[Chemical Plant Zone]], he bounces away with a Y Speed of -3. X Speed is unaffected.<br />
<br />
The block produces 4 segments. These segments have a gravity of 0.21875. Their initial x and y speeds are (-2, -2) & (2, -2) for the top two, and (-1, -1) & (1, -1) for the bottom two.<br />
<br />
===Breaking Walls===<br />
<br />
In Sonic 1, 2, 3, & K, the character's absolute X Speed must exceed '''4.5''' in order to break through destructible walls when rolling (except for [[Knuckles]], who busts walls on contact, and doesn't need to be rolled up). X Speed is unaffected by the collision, as well.<br />
<br />
However, when Knuckles breaks walls in Sonic 3 & Knuckles, though his X Speed is unaffected, he doesn't move during the frame in which he hits the wall. The same thing is true when Sonic [[Spindash|spindashes]] through a wall in Sonic 3 & Knuckles.<br />
<br />
In Sonic CD, the X Speed threshold is removed. Sonic can break through destructible walls by simply jumping near them, or rolling into them at any speed.<br />
<br />
===Buttons===<br />
<br />
[[Image:SPGButtonHitbox.png]]<br />
<br />
Buttons simply act solid but have a Width Radius and Height Radius which is smaller than the button when not depressed. When Sonic is standing on it, the subimage changes to depressed and the switch is activated.<br />
<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Bridges===<br />
The bridges in Sonic 1, 2 and 3 are known for their dynamic movement as Sonic moves over them.<br />
Bridges are set up with a controller object and an array of log objects which the controller object creates, though this can just be an array of values to represent the segments in most engines now. The controller object contains a few variables: There's the length (in segments) of the bridge, which is usually 12, but it can be longer; there's the index of the segment Sonic is standing on, which starts at 0 for the leftmost segment and ends at length-1.<br />
<br />
====Depression Amount====<br />
The depression amount is the lowest the bridge can go at any given time. This changes depending on the log Sonic is standing on.<br />
To get the current maximum depression amount in pixels the bridge controller uses predetermined values for each log.<br />
<br />
The values go up in 2's, starting at 2 and continuing to the middle, with the other side being a mirror of the first half. For example, a bridge with length 5 will have the values 2,4,6,4,2 and a bridge with length 6, the values would be 2,4,6,6,4,2. The bridges commonly found in Sonic (12 segments in length) would have the values 2,4,6,8,10,12,12,10,8,6,4,2. <br />
<br />
To get the current maximum depression for the bridge, the game uses the value from the log currently being stood on. We'll call the current value MaxDepression.<br />
<br />
====Calculating Each Log Depression====<br />
The Y Position of the segments in the bridge depend on the log that Sonic is currently standing on, as so:<br />
<br />
[[Image:SPGBridge.png]]<br />
Sonic is on the 5th segment, we'll call this the CurrentSegment and it's value is 5.<br />
<br />
In this example, the depressions would look as follows: 2,4,6,8,10,12,12,10,8,6,4,2<br />
So the current MaxDepression for the bridge will be the 5th log's value, which is 10.<br />
<br />
To calculate the position of each log, we calculate how far it is from CurrentSegment relative to the edge it's near. We will call this value LogDistance.<br />
<br />
Segment 0 is 1/5 of the way to CurrentSegment, so it's LogDistance is 1/5 or 0.2.<br />
<br />
Segment 1 is 2/5 of the way to CurrentSegment, so it's LogDistance is 2/5 or 0.4. <br />
<br />
Segment 4 is 5/5 of the way to CurrentSegment, so it's LogDistance is 5/5 or 1. <br />
<br />
<br />
Working from the other side, we use the distance from the end to CurrentSegment, rather than from the start.<br />
<br />
Segment 11 is 1/8 of the way to CurrentSegment, so it's LogDistance is 1/8 or 0.125.<br />
<br />
Segment 6 is 6/8 of the way to CurrentSegment, so it's LogDistance is 6/8 or 0.75.<br />
<br />
<br />
(Since we've already calculated segment 4 from one direction, there's no need to do it from the other).<br />
<br />
We then use LogDistance to calculate it's position: <br />
<br />
LogY = BridgeY + MaxDepression * sine(90 * LogDistance).<br />
<br />
<br />
Some code to calculate these automatically may go as follows:<br />
<br />
This assumes first segment index starts at 0 in the loop, but CurrentSegment (the log currently stood on) would start at 1.<br />
<br />
for (i = 0; i < SegmentAmount; i++)<br />
{<br />
//get difference of log<br />
var log_difference = abs((i+1)-CurrentSegment);<br />
<br />
//get opposite distance from current log to a side, depending if before or after CurrentSegment<br />
if (i < CurrentSegment) LogDistance = log_difference / CurrentSegment; else LogDistance = log_difference / ((SegmentAmount - CurrentSegment) + 1);<br />
<br />
//get y of log using max depression and log distance (reversed)<br />
LogY = BridgeY + ((MaxDepression) * sine(90 * (1 - LogDistance)));<br />
}<br />
<br />
<br />
Meanwhile, all these depression values are multiplied by the sine of the angle in the controller object that counts to 90 when Sonic is standing on top, and down to 0 when Sonic gets off, so the depression will smoothly increase with time when Sonic jumps on to the bridge, and then smoothly decrease when he leaves it.<br />
<br />
===End of Level Capsules===<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
====Sonic 1 Method====<br />
<br />
=====Explosion=====<br />
For 60 steps, every 8 steps, spawn explosion at capsule position plus random x,y offset (Max horizontal offset of 31 pixels, and according to calculations, vertical is the same). At end of those 60 steps, start with the animals<br />
<br />
=====Animals=====<br />
Switch to exploded frame. Spawn 8 animals at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (animals don't jump out until their alarm reaches zero).<br />
<br />
For 150 steps, every 8 steps, spawn animal at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12. <br />
<br />
When all animal objects have disappeared, run "Got Through" message. <br />
</div><br />
<div class="large-6 columns"><br />
====Sonic 2 Method====<br />
=====Explosion=====<br />
An explosion spawns at lock's position, move lock at +8x, -4y. Wait 29 steps.<br />
<br />
=====Animals=====<br />
8 animals spawn at capsule position -28x, +32y, horizontally separated by 7, with alarms starting from 154 and decreasing by 8 per animal (the animals don't jump out until their alarm reaches zero).<br />
<br />
For 180 steps, every 8 steps, an animal will spawn at random X Position amongst the existing animal group (but tighter in, not near edges), with their alarm set to 12.<br />
<br />
When all animal objects have disappeared, the game will run the 'Got Through' message.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
==Sonic 1 Objects==<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
===Badniks===<br />
Here the hitboxes and movements of enemies will be detailed. <br />
<br />
====Motobugs====<br />
Motobugs move at a speed of 1. Once they touch a wall, they wait for 1 second before starting off in the other direction.<br />
<br />
[[Image:SPGMotobugHitbox.png]]<br />
<br />
Motobugs have a Width Radius of 8 and a Height Radius of 14, resulting in a 17 x 29 rectangle.<br />
<br />
They have a hitbox with a width radius of 20 and a height radius of 16, resulting in a 41 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor in at it's X Position and Y Position + Height Radius. If this sensor doesn't find floor (and if it is already moving), it will trigger a turn. Motobugs don't check for floor while turning.<br />
<br />
====Choppers====<br />
<br />
Choppers have a gravity of 0.09375 and bounce with a speed of -7 at the Y Position where they spawned.<br />
<br />
[[Image:SPGChopperHitbox.png]]<br />
<br />
Choppers have a hitbox with a width radius of 12 and a height radius of 16, resulting in a 25 x 33 rectangle.<br />
</div><br />
<div class="large-6 columns"><br />
====Buzz Bombers====<br />
<br />
Buzz Bombers move at a speed of 4 (or -4).<br />
<br />
[[Image:SPGBuzzBomberHitbox.png]]<br />
<br />
Buzz Bombers have a hitbox with a width radius of 24 and a height radius of 12, resulting in a 49 x 25 rectangle.<br />
<br />
====Crabmeats====<br />
<br />
Crabmeats move at a speed of 0.5 (or -0.5) while walking. They will walk for up to 127 frames. When they do turn they simply multiply their speed by -1. When shooting, they will wait 59 frames.<br />
<br />
[[Image:SPGCrabmeatHitbox.png]]<br />
<br />
Crabmeats have a Width Radius of 8 and a Height Radius of 16, resulting in a 17 x 33 rectangle.<br />
<br />
They have a hitbox with a width radius of 16 and a height radius of 16, resulting in a 33 x 33 rectangle.<br />
<br />
They check for the floor with 1 downwards facing sensor which moves depending on the direction it is walking. When walking right the sensor is at it's X Position + Width Radius and Y Position + Height Radius, and when moving left the sensor is at it's X Position - Width Radius and Y Position + Height Radius. This means it will never step too far off a cliff before turning.<br />
<br />
=====Projectiles=====<br />
When shot, Crabmeat's projectiles are given a Y Speed of -4, and an X Speed of either positive or negative 1. They have a gravity of 0.21875.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
====Caterkillers====<br />
<br />
Caterkillers are comprised of 4 segments, the head, and 3 body segments. The '''Head Segment''' is the only vulnerable part, while the body segments have hitboxes which damage the player (these also trigger the scattering upon being touched, but their main job is to hurt Sonic and they cannot be destroyed like a Badnik normally can). They head will also trigger scattering if not destroyed when touched.<br />
<br />
[[Image:SPGCaterkillerHitBox.png]]<br />
<br />
Each segment has a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle. The hitboxes are the same.<br />
<hr><br />
<div class="large-12 columns" style="padding:0px"><br />
<div class="large-6 columns" style="padding:0px"><br />
The way they move is rather complex compared to other enemies. Firstly, let's go over timings.<br />
<br />
Caterkillers scrunch up then flatten/stretch out, on a loop. They spend 16 frames moving, then 8 frames staying still. So this would be scrunching up for 16 frames, then not moving for 8, then stretching out for 16, then not moving for 8, and repeat.<br />
<br />
Firstly this will go over how they work while on open ground, then what they do to track properly on slopes, then cover what they do when meeting a wall or a ledge.<br />
<br />
For easier reference, we will number the segments like so:<br />
<br />
[[Image:SPGCaterkillerSegments.png]]<br />
<br />
=====Scrunching Up=====<br />
<br />
When starting to scrunch (and if the Cater killer isn't turning at all), each body part should already be spaced 12px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 62, or 38 if it is facing right, and so on. It's mouth is also set to open.<br />
<br />
The '''Head Segment''' won't proceed (its X speed is 0), instead this is where the back end catches up with the front end. <br />
<br />
'''Segment 1''' will proceed with the head's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will move with Segment 1's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 3''' will move with Segment 2's X speed plus -0.25 (0.25 when the segment is moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
Both the '''Head Segment''' and '''Segment 2''' will animate upwards 7 pixels within the 16 frames. The other segments remain flat to the floor. ''Animate'' being the keyword here, the actual position and hitbox do not rise at all.<br />
<br />
=====Stretching Out=====<br />
<br />
When starting to stretch out (and if the Caterkiller isn't turning at all), each body part should already be spaced 8px apart. So if the '''Head Segment''''s X position was 50, the next body segment would have an X position of 58, or 42 if it is facing right, and so on. It's mouth is also set to closed.<br />
<br />
Stretching out is basically the opposite, where the head moves forward and the back end stays still.<br />
<br />
The '''Head Segment''' will proceed with an X speed of -0.75 (0.75 when moving right).<br />
This results in a movement of 12 pixels within the 16 frames.<br />
<br />
'''Segment 1''' will proceed with the head's speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 8 pixels within the 16 frames.<br />
<br />
'''Segment 2''' will proceed with Segment 1's X speed plus 0.25 (-0.25 when the segment is moving right).<br />
This results in a movement of 4 pixels within the 16 frames.<br />
<br />
'''Segment 3''' doesn't proceed, it has Segment 2's X speed plus 0.25 (-0.25 when the segment is moving right). <br />
This results in 0 X speed.<br />
<br />
This time, both the '''Head Segment''' and '''Segment 2''' will animate downwards 7 pixels within the 16 frames, back to being flat to the floor.<br />
<br />
=====Animation=====<br />
As a segment rises, it does so in a particular way. <br />
<br />
Across the 16 frames, this is how many pixels the raised segments will be from the ground while scrunching up. <br />
0, 0, 0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7<br />
This will be reversed as they move back down while stretching out.<br />
<br />
In your framework this could be baked into frames of animation or as an extra y offset subtracted from the draw position.<br />
</div><br />
<div class="large-6 columns"><br />
=====Slopes=====<br />
<br />
The '''Head Segment''' sticks to the floor using a downwards sensor much like Sonic does as it moves.<br />
<br />
Well, each body part needs to also stick to slopes as they move.<br />
This is done by having the '''Head Segment''' record and remember all of it's Y offsets in a small array (only on the movement frames) and having the next body part read from this as they move. This saves the game having to read data from tiles for each body segment. This array should be around 16 entries long.<br />
<br />
=====Y Offset Arrays=====<br />
''Note: We will assume that position 0 of an array is the oldest value, while new values are added to the end.''<br />
<br />
After colliding with the floor, X Speed is added to it's X Position, the '''Head Segment''' will check each frame whether it has moved a pixel - this being if it's floored X position after moving does '''not''' equal it's floored X position ''before'' moving. <br />
<br />
If movement has occurred, the current Y offset will be added to it's Y offset array. <br />
<br />
The other segments will use this array to reposition themselves. Using '''Segment 1''' as an example, it has an index which it will read from the array, this should be at around 12 back from the most recent value. This is because the segments are 12 pixels away from each other when fully stretched.<br />
<br />
When '''Segment 1''' moves a pixel (which occurs at different frames to the '''Head Segment'''), it will trim the oldest value from the '''Head Segment''''s array (effectively moving 1 Y offset array entry into the future). Then, it will read a value from the '''Head Segment''''s array at the index position. If this value is valid, the Y position of the segment will be adjusted according to the read value. After this happens, whatever the value is, '''Segment 1''' adds this value to it's '''own''' array for '''Segment 2''' to use in the exact same way.<br />
<br />
'''Segment 2''' and '''Segment 3''' work in the exact same way, except '''Segment 2''' will use '''Segment 1''''s array, and '''Segment 3''' will use '''Segment 2''''s array (also, '''Segment 3''' doesn't need to record any values to it's own array, being the last segment).<br />
<br />
The result of this is while the arrays are all added to at the rate of that segment's motion, it's read from at the rate of the next segment's motion. This ensures that each segment will meet the same Y offset value when arriving at a particular X position. <br />
<br />
''Note: There's a possibility of this de-syncing if the timing of the states aren't perfectly in sync or speeds of the segments aren't perfectly balanced.''<br />
<br />
=====Ledges And Walls=====<br />
<br />
When the Caterkiller has to turn, it doesn't suddenly stop or even turn all at once. Each body segment independently changes direction. <br />
<br />
On the frames that the '''Head Segment''' meets a ledge or touches a wall, instead of recording a Y offset from the ground, a value of 128 is stored in the array. This will signal the next segment to turn around also, when they read it. Segments will not align themselves to the ground if nothing is found below or they have encountered a turning signal. The segment may spend 2 or more frames off the side of a ledge by 1 pixel however since the array only updates upon a pixel of movement, a turn signal isn't recorded multiple times.<br />
<br />
So, one by one, the segments will change direction as they encounter this signal in the arrays, and each reach the point of turn. <br />
<br />
On the movement frame a segment (including the head) turns, the fractional part of it's X position needs to be flipped. This is to ensure the segments are always perfectly pixel aligned after the 16 frame movement has passed. So, if the '''Head Segment''' has been moving left, and is at an X position of 50.75 when it turns, the 0.75 portion of the position is flipped (1-0.75 = 0.25) resulting in an X position of 50.25 afterwards. This happens for any segment and will help keep the segment at the same position within a pixel relative to it's direction, so nothing de-syncs.<br />
<br />
''Notes:''<br />
* ''1 pixel seems to be subtracted from a segment's X position when it turns to face left.<br />
* ''The segment will not stop at any point during a turn, and will continue to move in whichever direction it's now facing as if nothing happened.''<br />
<br />
=====Scattering=====<br />
When you touch any part of the Caterkiller (unless in doing so you destroy it's head), it will break apart and each segment will bounce away.<br />
<br />
======Segment Scatter Speed======<br />
Upon scattering, each segment first is given its own X speed. Here, starting at the '''Head Segment''' and ending with '''Segment 3'''.<br />
2, 1.5, -1.5, -2<br />
This speed is negated if the segment is facing to the right. On this frame, their Y speed is set to -4<br />
<br />
These will fall with a gravity of (0.21875), and upon contact with the floor their Y speed is set to -4 each time as a bounce.<br />
</div><br />
</div><br />
<hr><br />
<br />
<br />
===Green Hill===<br />
====S Tunnels====<br />
The S Tunnels in [[Green Hill Zone (Sonic the Hedgehog 16-bit)|Green Hill Zone]] simply keep Sonic rolling at all times. If his speed reaches 0 and he stands up, the game acts as if you have pressed down and he rolls again instantly. Since the S tunnels have no flat ground, Sonic will always roll down it and should never just crouch. However, as part of the function making Sonic roll, if his ''gsp'' does happen to be 0, it will set his ''gsp'' to 2.<br />
<br />
===Marble===<br />
<br />
====Pushable Blocks====<br />
Pushable blocks move 1 pixel at a time while being pushed (the mechanics of which can be found in [[SPG:Solid Objects|Solid Objects]]). They are also constantly checking below themselves to ensure there is floor nearby. If there is no floor directly below their centre when they get pushed, they will change their behaviour. To avoid falling too soon and clipping the corner, they will begin to move at a speed of 4 in the direction of the push until they have moved 16 pixels. At this point they are completely over the ledge that they originally detected. They will then proceed to fall and land as normal.<br />
<br />
====Spike Traps====<br />
When [[Marble Zone]] Spike traps fall, their Y Speed increases by 0.4375 each frame. When they reach full length, they spend about 60 frames there. After this they begin to rise by 0.5px per frame, or 1 pixel every 2 frames. The length they can fall varies. <br />
<br />
[[Image:SPGSpikeTrapHitbox.png]]<br />
<br />
They have a solid box at the top (which Sonic can walk on & push against) however the spike area is a damage hit box which will simply hurt Sonic upon contact but doesn't have solidity.<br />
<br />
===Scrap Brain===<br />
<br />
=====Conveyor Belts=====<br />
A [[Scrap Brain Zone]] conveyor belt will simply add the belt speed to Sonic's X Position, Sonic's speeds are unaffected.<br />
<br />
==Sonic 2 Objects==<br />
===Chemical Plant===<br />
<br />
====Spring Ramps====<br />
Spring ramps aren't quite as simple as normal springs. Firstly, they have a specific region where they actuate.<br />
<br />
[[Image:SPGSpringRampActuationRegion.png]]<br />
<br />
The ramp will activate if his X Position is within the green region as he stands on it.<br />
<br />
When a Spring ramp activates they don't bounce Sonic instantly, instead, Sonic moves down a bit as the animation plays. There are 2 subimages, normal and down, and these both have different collision height arrays as shown below. <br />
<br />
[[Image:SPGSpringRampSolidty.png]] <br />
<br />
On the left is how the solidity appears in-game, on the right is the height array as it is stored, it simply gets scaled by 2 horizontally. How slope data is used for solid objects is detailed in [[SPG:Solid_Objects#Sloped_Objects|Sloped Objects]].<br />
<br />
Once activated it plays the down subimage for 4 frames, and Sonic will lower with it but is otherwise unaffected and will keep walking. On the next frame it's back up and Sonic is raised again but still unaffected, on the frame after this Sonic will actually be in the air.<br />
<br />
So how fast do they bounce Sonic? Well, that's not perfectly simple either. It will bounce Sonic up with a Y Speed of -4, and depending on Sonic's X Position position along the ramp it will subtract a second modifier from his Y Speed. This is all dependant on the positions where Sonic's X Position is right now as he is bounced, not where he was when activated it.<br />
<br />
[[Image:SPGSpringRampPowerSteps.png]]<br />
<br />
From left to right this modifier can be 0, 1, 2, 3 or 4.<br />
<br />
So if Sonic happened to be in section 3, his Y Speed would become -4, minus the modifier of 2, resulting in -6.<br />
<br />
His X Speed is also affected, if it's absolute value happens to be larger than or equal to 4, the modifier will be added to (or subtracted from) X Speed. If Sonic is in section 3, and he has a speed of 5, his speed would become 5+2. This gets capped at 6 in Sonic 2 due to the speed cap still being present in the air.<br />
<br />
====Spring Caps====<br />
The red spring caps that cover the tubes in [[Chemical Plant Zone]] work like springboards, but are slightly stronger than a Yellow springboard. They set Sonic's Y Speed to -10.5 upon collision.<br />
<br />
====Spinners====<br />
The black spinners that impel you forward in [[Chemical Plant Zone]] set X Speed to 16. They don't seem to slow you down if you're already moving faster than that, though.<br />
<br />
===Hill Top===<br />
<br />
====Ski Lifts====<br />
<br />
The ski lifts in [[Hill Top Zone]] move with an X Speed of 2, and a Y Speed of 1.<br />
<br />
==Sonic 3 Objects==<br />
<br />
===Carnival Night===<br />
<br />
====Balloons====<br />
<br />
The balloons in [[Carnival Night Zone]] set Y Speed to -7 when Sonic collides with them, no matter what his angle of collision. X Speed is not affected. <br />
<br />
[[Image:SPGBalloonHitbox.png]]<br />
<br />
Balloons have a hitbox with a Width Radius of 8 and a Height Radius of 8, resulting in a 17 x 17 rectangle.<br />
<br />
====Cannons====<br />
The cannons in [[Carnival Night Zone]] set Sonic's X Speed to 16*cosine(p), and Y Speed to 16*-sine(p), where p is the angle of the cannon.<br />
<br />
==Sonic and Knuckles Objects==<br />
<br />
===Mushroom Hill===<br />
<br />
====Mushrooms====<br />
<br />
The mushrooms in [[Mushroom Hill Zone]] work just like springboards, only each successive bounce is higher than the last, up to three bounces. The first bounce sets Y Speed to -6.5, the second, -7.5, and the third, -8.5.<br />
<br />
==Points==<br />
When you hit a Badnik or other point-bearing item (such as Bumpers), a small score notification will fly up out of it. After it spawns at the object's X and Y Position, it begins with a Y Speed of -3, and will slow down by 0.09375 each frame. Once it is no longer moving, it will vanish. This will take around 32 frames.<br />
<br />
[[Category:Sonic Physics Guide|Game Objects]]</div>Lapper2