Camera Rotation Flickering When Wall-Running

Hey everyone, wondered if you could help me.
I’m working on the foundations for a parkour/fps game and I’ve run into an odd problem. I have implemented wall-running and wall-climbing along with a basic shooting mechanic which fires projectiles in a straight line following the camera’s transform.forward. However, wall-running (performed by holding W + A/D parallel to a wall), or just the player hitting a wall while facing parallel to it, causes the camera’s transform.forward to flicker rapidly each frame between two different directions, normally about 5 degrees apart, and so the shooting starts to be inaccurate and fire along the wrong line. One of the two directions is the correct forward, the other is offset 5ish degrees in the direction of the wall the player hit. What’s even weirder is that wall-climbing (performed by holding W while facing a wall) will then fix the problem. Code as follows.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour {

    public float moveSpeed;
    public float jumpHeight;
    public float climbSpeed;
    public float dashCooldownTime;
    float translation;
    float strafe;
    bool grounded;
    bool canDoubleJump;
    public GameObject shuriken;
    GameObject tempShuriken;
    float shootTimer;
    float requiredShootTimer;
    public Camera cam1;
    public GameObject shurikenSpawn;
    float dashCooldown;
    bool climbing;
    Vector3 side1;
    Vector3 side2;
    bool wallRunning;

    // Use this for initialization
    void Start () {
        Cursor.lockState = CursorLockMode.Locked;
        shootTimer = 1;
        dashCooldown = 3;
        requiredShootTimer = 1;
        climbing = false;
    }
  
    // Update is called once per frame
    void Update () {
        shootTimer += 1 * Time.deltaTime;
        dashCooldown += 1 * Time.deltaTime;
        side1 = transform.TransformDirection(Vector3.right);
        side2 = transform.TransformDirection(-Vector3.right);
        if (climbing == false)
        {
            if (Input.GetAxis("Horizontal") > 0)
            {
                translation = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime / (1 + (Input.GetAxis("Horizontal") / 2));
            }
            else
            {
                translation = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime / (1 + (-Input.GetAxis("Horizontal") / 2));
            }
        }
        else
        {
            GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, climbSpeed, GetComponent<Rigidbody>().velocity.z);
        }
        if ((Physics.Raycast(transform.position, side1, 1) && Input.GetAxis("Horizontal") > 0) || (Physics.Raycast(transform.position, side2, 1) && Input.GetAxis("Horizontal") < 0))
        {
            GetComponent<Rigidbody>().useGravity = false;
            GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, 0, GetComponent<Rigidbody>().velocity.z);
            strafe = 0;
            wallRunning = true;
            canDoubleJump = true;
        }
        else
        {
            wallRunning = false;
            GetComponent<Rigidbody>().useGravity = true;
            if (Input.GetAxis("Vertical") > 0)
            {
                strafe = Input.GetAxis("Horizontal") * moveSpeed * Time.deltaTime / (1 + (Input.GetAxis("Vertical") / 2));
            }
            else
            {
                strafe = Input.GetAxis("Horizontal") * moveSpeed * Time.deltaTime / (1 + (-Input.GetAxis("Vertical") / 2));
            }
        }

        if (grounded == false && wallRunning == false)
        {
            translation /= 1.5f;
            strafe /= 1.5f;
        }

        GetComponent<Rigidbody>().AddForce(transform.forward * translation);
        GetComponent<Rigidbody>().AddForce(transform.right * strafe);

        if (Input.GetKeyDown(KeyCode.LeftShift) && dashCooldown >= dashCooldownTime)
        {
            dashCooldown = 0;
            GetComponent<Rigidbody>().velocity = cam1.transform.forward * 25;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (grounded == true)
            {
                grounded = false;
                GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, jumpHeight, GetComponent<Rigidbody>().velocity.z);
            }
            else if (canDoubleJump == true)
            {
                canDoubleJump = false;
                GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x, jumpHeight, GetComponent<Rigidbody>().velocity.z);
            }
            }


        if (Input.GetMouseButton(0) && shootTimer >= requiredShootTimer)
        {
            StartCoroutine(FireShurikens());
        }
        if (Input.GetMouseButton(1) && shootTimer >= requiredShootTimer)
        {
            StartCoroutine(ShurikenSpam());
        }

        if (Input.GetKeyDown("escape"))
        {
            Cursor.lockState = CursorLockMode.None;
        }
        Debug.DrawRay(shurikenSpawn.transform.position, cam1.transform.forward * 15, Color.green);
    }

    void FixedUpdate()
    {
        GetComponent<Rigidbody>().velocity = new Vector3(GetComponent<Rigidbody>().velocity.x * 0.95f, GetComponent<Rigidbody>().velocity.y, GetComponent<Rigidbody>().velocity.z * 0.95f);
        Vector3 fwd = transform.TransformDirection(Vector3.forward);
        if (Physics.Raycast(new Vector3(transform.position.x, transform.position.y - 0.75f, transform.position.z), fwd, 1))
        {
            climbing = true;
            canDoubleJump = true;
        }
        else
        {
            climbing = false;
        }
    }

    void OnCollisionStay()
    {
        grounded = true;
        canDoubleJump = true;
    }

    void OnCollisionExit()
    {
        grounded = false;
    }


    IEnumerator FireShurikens()
    {
        requiredShootTimer = 1;
        shootTimer = 0;
        for (int i = 0; i < 3; i++)
        {
            tempShuriken = Instantiate(shuriken, shurikenSpawn.transform.position, shurikenSpawn.transform.rotation);
            tempShuriken.GetComponent<Rigidbody>().velocity = cam1.transform.forward * 25;
            yield return new WaitForSeconds(0.1f);
        }
    }
    IEnumerator ShurikenSpam()
    {
        requiredShootTimer = 0.5f;
        shootTimer = 0;
            tempShuriken = Instantiate(shuriken, shurikenSpawn.transform.position, shurikenSpawn.transform.rotation);
            tempShuriken.transform.Rotate(transform.up * -10);
            tempShuriken.GetComponent<Rigidbody>().velocity = tempShuriken.transform.forward * 25;
        tempShuriken = Instantiate(shuriken, shurikenSpawn.transform.position, shurikenSpawn.transform.rotation);
        tempShuriken.GetComponent<Rigidbody>().velocity = tempShuriken.transform.forward * 25;
        tempShuriken = Instantiate(shuriken, shurikenSpawn.transform.position, shurikenSpawn.transform.rotation);
        tempShuriken.transform.Rotate(transform.up * 10);
        tempShuriken.GetComponent<Rigidbody>().velocity = tempShuriken.transform.forward * 25;
        yield return null;
    }

}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControls : MonoBehaviour {

    Vector2 mouseLook;
    public float sensitivity;
    GameObject character;

    // Use this for initialization
    void Start () {
        character = this.transform.parent.gameObject;
    }
  
    // Update is called once per frame
    void Update () {
        var md = new Vector2(Input.GetAxisRaw("Mouse X") * sensitivity, Input.GetAxisRaw("Mouse Y") * sensitivity);
        mouseLook += md;
        transform.localRotation = Quaternion.AngleAxis(-mouseLook.y, Vector3.right);
        character.transform.localRotation = Quaternion.AngleAxis(mouseLook.x, character.transform.up);
    }
}

Any help would be greatly appreciated as I can’t seem to figure out what’s going wrong.
Many thanks!

I’m going to guess something about your physics setup is “snagging” the wall-facing side of the player and rotating him, which causes your camera to rotate, then the next frame you drive the player back to the right orientation, so you get the flicker.

Maybe try making the wall very slippery with a PhysicMaterial, see if the problem goes away. That’s not a great solution, but it might be all you need.

You’re the best! Don’t know why I never thought of that!
This is why I love this community so much. Thanks!

1 Like