SA1 constantly has gravity apply to sonic every frame (even when on the ground!), but when jumping and holding “A” you push against gravity (as in your Y velocity is added to) by a factor of a parameter known as “jump_addit,” multiplied by 0.8. This happens over a period of "Hang time" which in SA1 is a whole second. Finally, SA1 has 2 speed caps that hard cut-off your speed; you can't go faster than 16 decimeters per frame, and--importantly--if your speed is UNDER 0.02 decimeters per second, it caps out to 0.
Like much of the code in SA1 / 2, the jump is very much "hacked-together till it feels right," haha. There's quite a few things you have to keep in mind if you want total accuracy, especially with general ground and air movement.
Here's a stripped-down example of how to *JUST* get the SA1 style upward thrust to work in SRB2 space, in C pseudocode, just to get an idea of how the logic works.
// Character parameters
const int hangTimeFrames = TICRATE; // Is supposed to be one second; 60 frames in SA, 35 in SRB2.
const float jmp_y_spd = 16.6;
const float jmp_addit = 0.76;
const float weight = 0.8; // SA1 uses "weight" as gravity.
const float lim_v_spd_min = 0.2; // Fun fact: not an actual parameter. This is hard-coded in SA1.
const float air_resist_y = -0.01; // This is a multiplier so no need to scale this x10.
int hangTimer;
bool hasJumped = false; // Make sure to set to false whenever the character's state is not "Jump."
// This SHOULD convert 60FPS logic to 35FPS logic.
// Multiply w/ velocity modifying parameters (NOT multipliers, check UpwardThrust).
const float timeScale = 60.0 / TICRATE;
// The following are utility functions so the code logic is more clear.
void StartJump()
{
hasJumped = true;
hangTimer = 0;
player.velocity.y += (jmp_y_spd * timeScale);
}
void UpwardThrust()
{
player.velocity.y += (jmp_addit * timeScale) * 0.8;
}
void JumpAcceleration()
{
float jumpFriction = player.velocity.y * air_resist_y; // NOTE: not scaled w/ time.
float jumpAccel = jumpFriction + (-weight * timeScale);
player.velocity.y += jumpAccel;
}
// Our actual jump logic.
void DoJump()
{
if (!hasJumped)
{
StartJump();
}
hangTimer++;
bool inHangTime = hangTimer < hangTimeFrames;
if(inHangTime)
{
UpwardThrust();
}
JumpAcceleration();
// OPTIONAL: SA1 style speed truncation. Keeps you in place at the peak of your jump.
if (player.velocity.y < (lim_v_spd_min * timeScale) && inHangTime)
{
player.velocity.y = 0.0;
}
}
NOTE: SA1's unit scale is in decimeters, and it's code assumes a 60FPS time step. To combat this, all character parameters (except air_resist_y, which is a multiplier) have been scaled up x10 since, as far as I know, SRB2 is in standard CM unit scale; they've also been multiplied by
(60.0 / 35.0)
which should be
1.714285714285714
. Not the prettiest number in the world, but it should get the job done.
Also worth keeping in mind, "JumpAcceleration" handles the force of gravity as well. Not sure how you'd go about this, but basically you'd want to nullify the effects of SRB2's gravity if you're in the jump state. If you don't want the *exact* feel of SA1's jump and are okay using SRB2's gravity which is, AFAIK, less floaty, you can go ahead and change JumpAcceleration to this:
void JumpAcceleration()
{
float jumpFriction = player.velocity.y * air_resist_y;
player.velocity.y += jumpFriction;
}
I didn't set up the FRACUNIT conversion or how SRB2 handles applying player velocity; not familiar with that as I haven't touched SRB2 lua in a while. This is just an outline on how to get the desired effect.
If you want more stuff like this to get SA1 style movement in SRB2, shoot me a message. Hope this all helps!