Hello, i’m trying to create a sliding system similar to Rayman 2 but in first person, in which you can slide down slopes, move left and right and fly off ramps.
I managed to do all of that except the left and right movement with acceleration while sliding since i get some weird behaviour when the character is on the intersection of 2 planes, it flickers back and forth like it doesn’t know where to move. If i turn off the acceleration it works fine, but it’s not exactly realistic sliding.
Here’s the script:
Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class SlidingController : MonoBehaviour
{
public float groundSpeed = 20; // Ground and left/right movement speed while sliding
public float airAcceleration = 20;
public float jumpForce = 15;
public float gravityPower = 10;
public float airDrag = 0.2f;
public float slideMovementAcceleration = 40; // How fast do we accelerate left and right while sliding
public float slideMovementDrag = 0.5f;
public LayerMask slideMask; // Assign this layermask to objects you want the player to slide on
public string rampTag = "Finish"; // Put this tag on objects you want the player to slide on upwards (ramps)
public bool slidingStrafeAccelEnabled = true; // If has to apply strafe acceleration while sliding
float ccHeight;
float ccWidth;
float slideForce;
float strafeMagnitude;
bool grounded;
bool sliding;
bool lastFrameFlying;
bool lastFrameGrounded;
bool lastFrameSliding;
Vector3 gravity = Vector3.zero;
Vector3 movement = Vector3.zero;
Vector3 slideDirection = Vector3.zero;
Vector3 airForce = Vector3.zero;
CharacterController cc;
Vector3 hitNormal;
// Use this for initialization
void Start()
{
cc = GetComponent<CharacterController>();
ccHeight = GetComponent<CharacterController>().bounds.extents.y;
ccWidth = GetComponent<CharacterController>().bounds.extents.x;
}
// Update is called once per frame
void Update()
{
CheckGroundCondition(); // Check if i'm sliding, grounded or flying
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
input.Normalize();
input = transform.TransformDirection(input); // Transform the input to be relative to the player's rotation
if (sliding)
{
if (lastFrameFlying) // If i was flying the last frame
{
slideForce = gravity.y; // Set my slide speed based on the player's falling speed
gravity = Vector3.zero;
movement = Vector3.zero;
airForce = Vector3.zero;
}
slideForce -= gravityPower * Time.deltaTime; // Add slide speed over time
slideDirection *= slideForce;
Quaternion rot = new Quaternion();
rot = Quaternion.FromToRotation(Vector3.up, hitNormal); // Create a rotation from the input direction and the normal of the surface
input = rot * input; // Apply the rotation to the input so it matches the surface normal we're standing on
Vector3 slidePerpendicular = Vector3.Cross(slideDirection.normalized, hitNormal).normalized; // Get the plane perpendicular for the left/right movement while sliding
float strafeDot = Vector3.Dot(slidePerpendicular, input); // Determine if we should move left or right and how much
if(slidingStrafeAccelEnabled)
{
strafeMagnitude += slideMovementAcceleration * strafeDot * Time.deltaTime; // Accelerate our movement magnitude
strafeMagnitude /= 1 + slideMovementDrag * Time.deltaTime; // Apply strafe drag
movement = slidePerpendicular * strafeMagnitude;
}
else
{
movement = slidePerpendicular * slideMovementAcceleration * strafeDot;
}
}
else if (grounded)
{
if(lastFrameFlying)
{
gravity = Vector3.zero;
airForce = Vector3.zero;
}
movement = input * groundSpeed;
if (Input.GetButtonDown("Jump"))
{
gravity.y = jumpForce;
}
}
else
{
if (lastFrameSliding)
{
airForce = slideDirection; // Set the air force to the slide direction so we keep our speed after jumping off ramps
slideDirection = Vector3.zero;
}
if(lastFrameGrounded)
{
airForce = movement;
movement = Vector3.zero;
}
airForce += input * airAcceleration * Time.deltaTime; // Accelerate air speed over time
airForce /= 1 + airDrag * Time.deltaTime; // Apply air drag
gravity.y -= gravityPower * Time.deltaTime; // Apply gravity
}
cc.Move((movement + gravity + slideDirection + airForce) * Time.deltaTime); // Finally move the CC
lastFrameGrounded = grounded;
lastFrameSliding = sliding;
lastFrameFlying = !grounded && !sliding;
}
void CheckGroundCondition()
{
RaycastHit check;
bool upwards = false;
RaycastHit[] hits = Physics.SphereCastAll(transform.position, ccWidth / 4, -Vector3.up, ccHeight + 0.5f, slideMask);
if (hits.Length > 0) // If the spherecast hit a least one object with the slideMask
{
Vector3 norm = Vector3.zero;
Vector3 upNorm = Vector3.zero;
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider.tag == rampTag && hits[i].collider.gameObject != this.gameObject) // Determine if we should slide downwards or upwards
{
upwards = true;
upNorm += hits[i].normal; // blend upwards normals
}
else if (hits[i].collider.gameObject != this.gameObject)
{
norm += hits[i].normal; // blend downwards normals
}
}
norm.Normalize();
upNorm.Normalize();
if (!upwards) {
Vector3 cross = Vector3.Cross(Vector3.up, norm); // Get the slide direction perpendicular
slideDirection = Vector3.Cross(norm, cross).normalized; // Get the slide direction
hitNormal = norm;
}
else {
Vector3 cross = Vector3.Cross(Vector3.up, upNorm);
slideDirection = -Vector3.Cross(upNorm, cross).normalized;
hitNormal = upNorm;
}
grounded = false;
sliding = true;
// Debug rays
Vector3 slidePerpendicular = Vector3.Cross(Vector3.up, hitNormal);
Debug.DrawRay(hits[0].point, slidePerpendicular * 30, Color.blue);
Debug.DrawRay(hits[0].point, -slidePerpendicular * 30, Color.blue);
Debug.DrawRay(hits[0].point, -slideDirection.normalized * 30, Color.red);
}
else if (Physics.Raycast(transform.position, -Vector3.up, out check,ccHeight + 0.1f) && check.collider.gameObject != this.gameObject) // Raycast downwards to see if there's an object to stand on
{
grounded = true;
sliding = false;
}
else // If i'm flying
{
grounded = false;
sliding = false;
}
}
}
Did anybody have had any experience with similar stuff? Is the right approach to get the slide direction through a double cross product between Vector3.up and the normal and then between the acquired vector and the normal again? Is there a way to have more control over the slide direction?
Because sometimes this happens(red ray is the slide direction which is inverted because it’s a ramp, blue rays are strafe movement) and what’s supposed to be a ramp that launches you forward it launches you sideways because the plane is tilted on both the Z and the X axis.
How do skiing games approach this for example?
Thanks in advance.