Skip to main content
edited tags
Link
doppelgreener
  • 7.3k
  • 7
  • 44
  • 69
Source Link
Magrias
  • 101
  • 1
  • 2

Snowboarding character controller in Unity

I am currently trying to create a character controller that will allow the player to slide down slopes while maintaining control of their direction - in other words, a fairly simple snowboarding game. (I'm actually going to be using it for penguins sliding on their stomach but this has the same requirements). Note that ramps and cliffs should work as expected, with the player becoming airborne until they hit the ground.

I posted a thread on Twitter detailing my struggles here, but here is the best-functioning code I've managed so far:

// First, calculate our old trajectory
Vector3 oldTrajectory = transform.position - oldPosition;

// Objects in motion tend to stay in motion, so we start with that same motion first
Vector3 newTrajectory = oldTrajectory;

// If we're grounded, we'll take that into account for sliding purposes
if (cc.isGrounded)
{
    // First get the slope underneath us
    RaycastHit hit;
    if (Physics.Raycast(transform.position, -oldNormal, out hit, 2f) || Physics.Raycast(transform.position, Vector3.down, out hit, 2f))
    {
        oldNormal = hit.normal;
        Vector3 downSlopeRight = Vector3.Cross(hit.normal, GRAVITY_DIRECTION);
        Vector3 downSlopeDirection = Vector3.Cross(downSlopeRight, hit.normal);

        Vector3 movementForce = downSlopeDirection *
            (gravity * Time.fixedDeltaTime * Vector3.Dot(downSlopeDirection, GRAVITY_DIRECTION)) *
            (1 + (newTrajectory.magnitude * Vector3.Dot(downSlopeDirection, newTrajectory.normalized)));
                
        newTrajectory += movementForce * slopeImpact * Time.fixedDeltaTime;
    }
}
// Otherwise, normally account for gravity
{
    newTrajectory += gravity * GRAVITY_DIRECTION * Time.fixedDeltaTime * Time.fixedDeltaTime;
}

oldPosition = transform.position;
cc.Move(newTrajectory)  

Essentially: Take our current momentum, add gravity directed by the slope, and accelerate even more if the slope matches our trajectory (I can't even remember why I put that part in). This kind of works but especially when rocking back and forth in a divot, you notice that the momentum isn't really being handled properly (see here).

I attempted a second approach, where the player simply moves in the direction they're facing, and they gain speed while moving down a slope or lose it while going up. I couldn't figure out a way to get this to work at all:

// First, take the real distance we moved, and convert it to fairy numbers (time-independant measurements)
Vector3 newMomentum = (transform.position - oldPosition) / Time.fixedDeltaTime;

// If we're grounded, we'll take that into account for sliding purposes
if (cc.isGrounded)
{
    // First get the slope underneath us
    RaycastHit hit;
    if (Physics.Raycast(transform.position, -oldNormal, out hit, 2f) || Physics.Raycast(transform.position, Vector3.down, out hit, 2f))
    {
        oldNormal = hit.normal;
        Vector3 downSlopeRight = Vector3.Cross(hit.normal, GRAVITY_DIRECTION);
        Vector3 downSlopeDirection = Vector3.Cross(downSlopeRight, hit.normal);
        Vector3 facingDirectionOnSlope = Vector3.Cross(transform.right, hit.normal);

        // All we want to do is move the player faster if they're facing down the slope, or slower if facing up
        // (will also slide them backwards eventually which is funny)
        float speed = newMomentum.magnitude;
        speed += gravity * slopeImpact * Vector3.Dot(downSlopeDirection, newMomentum.normalized);
        newMomentum = facingDirectionOnSlope * speed;
    }
}
else
{
    // WE ADD GRAVITY BECAUSE THE GRAVITY DIRECTION IS DOWN
    newMomentum += gravity * Time.fixedDeltaTime * GRAVITY_DIRECTION;
}

// Update oldPosition after everything else (just in case we use it) but BEFORE CHANGING IT
oldPosition = transform.position;

cc.Move(newMomentum * Time.fixedDeltaTime);  

I tried a few different variations on this but I couldn't get it to work.

This approach is ideal, where the player can choose their direction and slopes give them speed (later I'd gently rotate them towards facing down the slope), as opposed to the other one relying on momentum and directing gravity. I just can't seem to figure out how to get it to work.