Hi I am trying to make a first person character controller which can dynamically wall run, without the use of animations. I am currently using the sample assets character controller and am planning on modifying heavily to my purposes. I have looked around and haven’t found a good way of doing it. I was wondering how I should implement this feature.
My ideas are:
Use a raycast to determine if there is a wall I can wall run on and then if my forward velocity is higher than x then wall run. If velocity decreases stop wall running or if there is velocity away from the wall.
When wallrunning turn off gravity or decrease it (rigidbody).
Any other ideas? Do you think this will work?
Thanks in advance.
Well it depends on how you want your wall run to feel.
First things first, you stop applying a downward move on the character controller. And instead apply a force towards the wall (you can get the walls normal from the collision that occurs, or by raycasting).
I make mine work that you have to maintain your speed to keep wall running. So at the end of each move, I check my current velocity, and if it’s too slow, I leave wall running mode.
The rest is just move your character in the direction the stick points, with what ever limitation on that you want to give it the feel you like.
Thanks for your help
At the moment I have made a crude wall running script based on the FPS controller in the sample assets (beta) package.
It works for now but I will change it and post an updated version.
using UnityEngine;
using System.Collections;
public class FirstPersonCharacter : MonoBehaviour
{
[SerializeField] private float runSpeed = 8f; // The speed at which we want the character to move
[SerializeField] private float strafeSpeed = 4f; // The speed at which we want the character to be able to strafe
[SerializeField] private float jumpPower = 5f; // The power behind the characters jump. increase for higher jumps
[SerializeField] private bool walkByDefault = true; // controls how the walk/run modifier key behaves.
[SerializeField] private float walkSpeed = 3f; // The speed at which we want the character to move
[SerializeField] private AdvancedSettings advanced = new AdvancedSettings(); // The container for the advanced settings ( done this way so that the advanced setting are exposed under a foldout
[SerializeField] private bool lockCursor = true;
[System.Serializable]
public class AdvancedSettings // The advanced settings
{
public float gravityMultiplier = 1f; // Changes the way gravity effect the player ( realistic gravity can look bad for jumping in game )
public PhysicMaterial zeroFrictionMaterial; // Material used for zero friction simulation
public PhysicMaterial highFrictionMaterial; // Material used for high friction ( can stop character sliding down slopes )
public float groundStickyEffect = 5f; // power of 'stick to ground' effect - prevents bumping down slopes.
}
private CapsuleCollider capsule; // The capsule collider for the first person character
private const float jumpRayLength = 0.7f; // The length of the ray used for testing against the ground when jumping
public bool grounded { get; private set; }
private Vector2 input;
private IComparer rayHitComparer;
public LayerMask layermask;
public bool canwalljump;
public float velocity;
void Awake ()
{
// Set up a reference to the capsule collider.
capsule = collider as CapsuleCollider;
grounded = true;
Screen.lockCursor = lockCursor;
rayHitComparer = new RayHitComparer();
}
void OnDisable()
{
Screen.lockCursor = false;
}
void Update()
{
if (Input.GetMouseButtonUp(0))
{
Screen.lockCursor = lockCursor;
}
}
public void FixedUpdate ()
{
checkwall();
float speed = runSpeed;
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
bool jump = Input.GetButton("Jump");
// On standalone builds, walk/run speed is modified by a key press.
bool walkOrRun = Input.GetKey(KeyCode.LeftShift);
speed = walkByDefault ? (walkOrRun ? runSpeed : walkSpeed) : (walkOrRun ? walkSpeed : runSpeed);
input = new Vector2( h, v );
// normalize input if it exceeds 1 in combined length:
if (input.sqrMagnitude > 1) input.Normalize();
// Get a vector which is desired move as a world-relative direction, including speeds
Vector3 desiredMove = transform.forward * input.y * speed + transform.right * input.x * strafeSpeed;
// preserving current y velocity (for falling, gravity)
float yv = rigidbody.velocity.y;
// add jump power
if (grounded jump !canwalljump)
{
yv += jumpPower;
grounded = false;
}
// Set the rigidbody's velocity according to the ground angle and desired move
rigidbody.velocity = desiredMove + Vector3.up * yv;
// Use low/high friction depending on whether we're moving or not
if (desiredMove.magnitude > 0 || !grounded)
{
collider.material = advanced.zeroFrictionMaterial;
} else {
collider.material = advanced.highFrictionMaterial;
}
// Ground Check:
// Create a ray that points down from the centre of the character.
Ray ray = new Ray(transform.position, -transform.up);
// Raycast slightly further than the capsule (as determined by jumpRayLength)
RaycastHit[] hits = Physics.RaycastAll(ray, capsule.height * jumpRayLength );
System.Array.Sort (hits, rayHitComparer);
if (grounded || rigidbody.velocity.y < jumpPower * .5f)
{
// Default value if nothing is detected:
grounded = false;
// Check every collider hit by the ray
for (int i = 0; i < hits.Length; i++)
{
// Check it's not a trigger
if (!hits[i].collider.isTrigger)
{
// The character is grounded, and we store the ground angle (calculated from the normal)
grounded = true;
// stick to surface - helps character stick to ground - specially when running down slopes
//if (rigidbody.velocity.y <= 0) {
rigidbody.position = Vector3.MoveTowards (rigidbody.position, hits[i].point + Vector3.up * capsule.height*.5f, Time.deltaTime * advanced.groundStickyEffect);
//}
rigidbody.velocity = new Vector3(rigidbody.velocity.x, 0, rigidbody.velocity.z);
break;
}
}
}
Debug.DrawRay(ray.origin, ray.direction * capsule.height * jumpRayLength, grounded ? Color.green : Color.red );
// add extra gravity
rigidbody.AddForce(Physics.gravity * (advanced.gravityMultiplier - 1));
}
//used for comparing distances
class RayHitComparer: IComparer
{
public int Compare(object x, object y)
{
return ((RaycastHit)x).distance.CompareTo(((RaycastHit)y).distance);
}
}
public void checkwall()
{
velocity = transform.InverseTransformDirection(rigidbody.velocity).z;
Debug.DrawLine(transform.position + transform.right, transform.position - transform.right, Color.green);
if(Physics.Linecast(transform.position + transform.right, transform.position + -transform.right , layermask) velocity > 6 !grounded)
{
canwalljump = true;
rigidbody.useGravity = false;
}
else
{
canwalljump = false;
rigidbody.useGravity = true;
}
}
}