So I found this great tutorial on how to make a free running game which goes into a lot of detail about how to do the acual code for it. However now I want to take a look at doing the aesthetics behind everything, like for instance, two things I want to do is have the camera and the player rotate on the Z axis by about 45 degrees while doing wall running, because of course, that’s what your body does normally because otherwise your body would just end up flat against the wall and wouldn’t move.
The second thing is for me to put in some hand animations, now i can do all the modelling myself and so on, that’s not the worry, but I just need to know where to put this so I don’t fuck things up. I thought at least for the angle change I should put it in with my raycasts for when the script finds out if the player has hit the wall which sort of worked but it seemed to interfere with all the other code.
Here’s the tutorial for those who want to take a look: Scavenger Stuff: Unity3D parkour, wall running, and you! Part 1: Basic Movement.
using UnityEngine;
using System.Collections;
[RequireComponent ( typeof ( CharacterController ) ) ]
public class jumpMotor : MonoBehaviour {
private CharacterController controller;
public Camera camera;
void Start ()
{
camera = Camera.main;
controller = GetComponent<CharacterController> ();
}
public float TurnSpeed = 8f;
public float MouseSensitivity = 2.5f;
float cameraRotX = 0f;
private Vector3 moveDirection;
public float BaseSpeed = 4.0f;
public float JumpSpeed = 8.0f;
public float Gravity = 20.0f;
public float RunSpeedIncrease = 4.0f;
public float RampUpTime = 0.75f;
private bool moveKeyDown = false;
private float moveDownTime = 0f;
private float friction = 15.0f;
private Vector3 lastDirection;
private MotorStates motorState = MotorStates.Default;
private bool CanWallRun = true;
private float wallRunMaxTime = 1.5f;
private float wallRunTime = 0.0f;
private RaycastHit wallHit;
void Update ()
{
switch (motorState) {
case ( MotorStates.Jumping ):
UpdateJump ();
break;
case ( MotorStates.Wallrunning):
UpdateWallRun ();
break;
default:
UpdateDefault ();
break;
}
controller.Move (moveDirection * Time.deltaTime);
lastDirection = moveDirection;
}
void StandardCameraUpdate ()
{
transform.Rotate ( 0f, ( Input.GetAxis ( "Mouse X") * MouseSensitivity ) * TurnSpeed * Time.deltaTime, 0f );
camera.transform.forward = transform.forward;
cameraRotX -= Input.GetAxis ("Mouse Y") * MouseSensitivity;
camera.transform.Rotate ( cameraRotX, 0f, 0f );
}
void UpdateDefault ()
{
if (Input.GetButton ("Jump"))
{
motorState = MotorStates.Jumping;
moveDirection.y = JumpSpeed;
}
moveKeyDown = Input.GetKey (KeyCode.W);
if (moveKeyDown && moveDownTime < RampUpTime) {
moveDownTime += Time.deltaTime;
if (moveDownTime > RampUpTime) {
moveDownTime = RampUpTime;
}
}
StandardCameraUpdate ();
{
transform.Rotate (0f, (Input.GetAxis ("Mouse X") * MouseSensitivity) * TurnSpeed * Time.deltaTime, 0f);
camera.transform.forward = transform.forward;
cameraRotX -= Input.GetAxis ("Mouse Y") * MouseSensitivity;
camera.transform.Rotate (cameraRotX, 0f, 0f);
}
if (controller.isGrounded) {
if (!moveKeyDown) {
moveDownTime = 0f;
}
moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));
moveDirection = new Vector3 (Input.GetAxisRaw ("Horizontal"), 0f, Input.GetAxisRaw ("Vertical"));
moveDirection = transform.TransformDirection (moveDirection);
moveDirection.Normalize ();
moveDirection *= BaseSpeed;
if (Input.GetButton ("Jump")) {
moveDirection.y = JumpSpeed;
}
}
moveDirection.y -= Gravity * Time.deltaTime;
}
float DoSlowDown (float lastVelocity)
{
if (lastVelocity > 0) {
lastVelocity -= friction * Time.deltaTime;
if (lastVelocity < 0)
lastVelocity = 0;
} else {
lastVelocity += friction * Time.deltaTime;
if (lastVelocity > 0)
lastVelocity = 0;
}
return lastVelocity;
}
// Wall Running Code
public enum MotorStates{
Climbing,
Default,
Falling,
Jumping,
LedgeGrabbing,
MusclingUp,
Wallrunning
}
void UpdateJump ()
{
StandardCameraUpdate ();
wallHit = DoWallRunCheck ();
if (wallHit.collider != null)
{
motorState = MotorStates.Wallrunning;
return;
}
moveDirection.y -= Gravity * Time.deltaTime;
if (controller.isGrounded)
{
motorState = MotorStates.Default;
}
}
RaycastHit DoWallRunCheck ()
{
Ray rayRight = new Ray (transform.position, transform.TransformDirection (Vector3.right));
Ray rayLeft = new Ray (transform.position, transform.TransformDirection (Vector3.left));
RaycastHit wallImpactRight;
RaycastHit wallImpactLeft;
bool rightImpact = Physics.Raycast (rayRight.origin, rayRight.direction, out wallImpactRight, 1f);
bool leftImpact = Physics.Raycast (rayLeft.origin, rayLeft.direction, out wallImpactLeft, 1f);
if (rightImpact && Vector3.Angle (transform.TransformDirection (Vector3.forward), wallImpactRight.normal) > 90) {
return wallImpactRight;
} else if (leftImpact && Vector3.Angle (transform.TransformDirection (Vector3.forward), wallImpactLeft.normal) > 90) {
wallImpactLeft.normal *= -1;
return wallImpactLeft;
} else {
return new RaycastHit ();
}
}
void UpdateWallRun ()
{
if (!controller.isGrounded && CanWallRun && wallRunTime < wallRunMaxTime)
{
wallHit = DoWallRunCheck ();
if (wallHit.collider == null)
{
StopWallRun ();
return;
}
motorState = MotorStates.Wallrunning;
float previousJumpHeight = moveDirection.y;
Vector3 crossProduct = Vector3.Cross (Vector3.up, wallHit.normal);
Quaternion lookDirection = Quaternion.LookRotation (crossProduct);
transform.rotation = Quaternion.Slerp (transform.rotation, lookDirection, 3.5f * Time.deltaTime);
moveDirection = crossProduct;
moveDirection.Normalize ();
moveDirection *= BaseSpeed + (RunSpeedIncrease * (moveDownTime / RampUpTime));
if (wallRunTime == 0.0f)
{
moveDirection.y = JumpSpeed / 4;
}
else
{
moveDirection.y = previousJumpHeight;
moveDirection.y -= (Gravity / 4) * Time.deltaTime;
}
wallRunTime += Time.deltaTime;
if (wallRunTime > wallRunMaxTime)
{
CanWallRun = false;
}
}
else
{
StopWallRun ();
}
}
void StopWallRun ()
{
if (motorState == MotorStates.Wallrunning)
{
CanWallRun = false;
wallRunTime = 0.0f;
motorState = MotorStates.Default;
}
}
// End of Wall Running code
}
For those wondering, as the tutorial describes, the aim is to make a game very similar to mirror’s edge style of free running, I’ve also noticed that for whatever reason the code seem to have a bit of trouble jumping between two parallel walls like you do to get up to a higher obstacle.