Go back to : Skydrift research homepage

Gensou Skydrift - Ringbounces & air mechanics

Ringbounces are related to a lot of air mechanics, so this document will cover a lot on that subject and then specify more about ringbounces.

Table of contents (clic here to show)

Key parts of air mechanics

State frames

State frames are basically a player internal counter. They start increasing when the race loads (they reset when restarting the race), and they can only go up. As they increment at the start of a player FixedUpdated, we can use them to track the differences between the current state of theplayer with its previous one.

It is important to note that the FixedUpdate only happens 50 times a second in this game. This means that if something that should trigger a condition inside the function happens between those 2 frames, it will not trigger. For example, if you are airborn in state frame 210, touch the ground and become airborn again before state frame 211, the game will not register that you briefly landed. (Spoiler⊁: this one of the "it should have ring bounced !" reasons)

States frames have some uses outside of being an internal counter, such as timing the passive spell meter boost or the autohorizon check.

Y velocity

A player "Y velocity" is its speed on the vertical axis (up / down). This is basically what makes the player fall to the ground.

In Gensou Skydrift, a player Y velocity can not go below -20f and has no upper limit, though it is hard (or impossible) to exceed 10f. Only a few ways exist to change this value :

Clic here to show code

// From class Tank, method dash
if (upperpower > 0f)
{
    this.mVelocity.y = this.mVelocity.y + 10f;
    this.mJumpFrame = 60;
    this.mIsGround = (this.mIsFakeGround = false);
}
// From class Tank, method updateTankMove
if (this.mIsGround)
{
    this.mVelocity.y = 0f;
}
else
{
    float num3 = (this.groundType == GroundTypeList.NoJump ? 32f : 12.8f) * Time.deltaTime;
    if (this.isSpecialState(ESPState.UFO) && !this.tankSector.getCurrentPath().mIsForbiddenRespawn) {
        num3 *= 2f;
    }
    if (this.isSpecialState(ESPState.Rain) && this.getRank() <= this.world.getTankNum() / 2) {
    // Note : this used to have another possible activation condition : this.isSpecialState(ESPState.Redeyes)
        num3 *= 0.9f;
    }
    this.mVelocity.y = this.mVelocity.y - num3;
    if (this.mVelocity.y < -20f)
    {
        this.mVelocity.y = -20f;
    }
}
// From class Tank, method endSpecialState
if (state == ESPState.CourseOut)
{
    this.mVelocity = Vector3.zero;
    // Rest does not matter
}
// From class Tank, method OnCollisionStay
else if (this.mWallFrame > 150)
{
    this.mWallFrame = 0;
    if (this.isLocal()) {this.mpWorld.getAudioManager().playSE(ESeID.otherhit);}
    this.mVelocity = Vector3.zero;
    this.mWallLockFrame = 1;
    this.mRecoilVelocity = contactPoint.normal * 20f * 3f;
    this.mRecoilVelocity += this.mTankform.rotation * (Vector3.down * 10f);
}

Elevator & elevation frames

(See this YouTube video as illustration) -> Elevation frames stopping during jump.

Jump & jump frames

Jump is like a real jump. You are considered in a jump after using a dash with upper power (Cradle, Reverie & Orin Last Word).

Jump frames are the internal counter for the duration of the jump. Only a few ways exist to change this value :

Clic here to show code

// From class Tank, method dash
if (upperpower > 0f)
{
    this.mVelocity.y = this.mVelocity.y + 10f;
    this.mJumpFrame = 60;
    this.mIsGround = this.mIsFakeGround = false;
}
// From class Tank, method updateTankMove
float num9 = this.groundType != GroundTypeList.DisableHorizon ? 45f : 50f;
if (Vector3.Angle(raycastHit.normal, this.mTankform.up) < num9)
{
    // Slope stuff
}
else if (this.mJumpFrame > 0)
{
    this.mJumpFrame = 0;
}

Being (or not) in a jump as multiple consequences :

Clic here to show code

// From class Tank, method updateTankMove
// rayLength is renammed by me
Ray ray = new Ray(this.mTankform.position + this.mTankform.TransformDirection(new Vector3(0f, 1f, 0.7f)), this.mTankform.TransformDirection(Vector3.down));
RaycastHit raycastHit = new RaycastHit();
float rayLength = this.mJumpFrame > 0 || this.mGroundType == EGroundType.NoJump ? 40f : 5f;
// Later in the same method
if (this.mJumpFrame == 0)
{
    this.mIsGround = (this.mGroundHeight < 1.2f);
    this.mIsFakeGround = (this.mGroundHeight < 1.7f);
    if (this.mIsGround)
    {
        this.mWallLockFrame = 0;
    }
}
// Later in the same method
if (Vector3.Angle(raycastHit.normal, this.mTankform.up) < num9)
{
    if (raycastHit.distance < 2f || this.mJumpFrame > 0)
    {
        this.mRoadNormalVec = Vector3.Slerp(this.mRoadNormalVec, raycastHit.normal, 0.4f * cTimeBase);
    }
    else
    {
        this.mRoadNormalVec = Vector3.Slerp(this.mRoadNormalVec, raycastHit.normal, 0.2f * cTimeBase);
    }
}

Gravity ray

Gravity

Ground detection

Slopes

Y rotation

Autohorizon

Air Events

While in the air, some events can happen based on some interactions between the elements previously described, the player actions and the environment. (Some are probably not intended.) Each event will be named and described at its lower level. Another section will details combination of multiple events : some commonly known ones, such as ring bounces, are a combination of multiple lower level ones.

Elevator reset

Ground ring