1
votes

I'm developing a 3d FPS game in Unity. At the moment I'm implementing a wall-running mechanic. The logic behind this is very simple - if player is pushing forward, and not grounded, and touching a wall, I constrain the Y/Z direction (but player can still run forward as I ignore the X direction though) and turn off gravity. It seems to work fine, a little bit clumsy but ok for me. Except, when the wall is left behind player is still able to run in mid-air until he runs out of inertia (here's the example: https://imgur.com/a/LtbWs9J). Here's the code:

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class WallRunning : MonoBehaviour
{
    public AudioClip audioClip;
    private CharacterMovement cm;
    private Rigidbody rb;
    private bool isJumping;
    public bool isWall;
    private bool playAudio;
    private AudioSource audioSource;
    public float energyLimit = 3.5f;


    private void Start()
    {
        //Get attached components so we can interact with them in our script.
        cm = GetComponent<CharacterMovement>();
        rb = GetComponent<Rigidbody>();
        audioSource = GetComponent<AudioSource>();
    }

    private void FixedUpdate()
    {
        bool jumpPressed = Input.GetButtonDown("Jump");
        float verticalAxis = Input.GetAxis("Vertical");
        //Check if the controller is grounded.
        if (cm.Grounded)
        {
            isJumping = false;
            isWall = false;
        }
        //Has the jump button been pressed.
        if (jumpPressed)
        {
            StartCoroutine(Jumping());
        }
        //If we are pushing forward, and not grounded, and touching a wall.
        if (verticalAxis > 0 && isJumping && isWall)
        {
            StartCoroutine(Energy());
            //We constrain the Y/Z direction to defy gravity and move off the wall.
            //But we can still run forward as we ignore the X direction.
            rb.useGravity = false;
            rb.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezeRotation;
            //We also telegraph to the player by playing a sound effect on contact.
            if (audioClip != null && playAudio == true)
            {
                audioSource.PlayOneShot(audioClip);
                //We block more audio being played while we are on the wall.
                playAudio = false;
            }
        }
        else
        {
            StopCoroutine(Energy());
            //We need to make sure we can play audio again when touching the wall.
            playAudio = true;
            rb.useGravity = true;
            rb.constraints = RigidbodyConstraints.FreezeRotation;
        }
    }

    void OnCollisionEnter(Collision other)
    {
        //Are we touching a wall object?
        if (other.gameObject.CompareTag("Walls"))
        {
            isWall = true;
        }
    }


    void OnCollisionExit(Collision other)
    {
        //Did we stop touching the wall object?
        if (!other.gameObject.CompareTag("Walls"))
        {
            isWall = false;
        }
    }


    IEnumerator Jumping()
    {
        //Check for 5 frames after the jump button is pressed.
        int frameCount = 0;
        while (frameCount < 5)
        {
            frameCount++;
            //Are we airbourne in those 5 frames?
            if (!cm.Grounded)
            {
                isJumping = true;
            }
            yield return null;
        }
    }

    IEnumerator Energy()
    {
        yield return new WaitForSeconds(energyLimit);
        isWall = false;
    }
}

Notice: walls have box colliders on them ("Is Trigger" checkbox is unchecked), and player has non-kinematic rigidbody and capsule collider attached. Walls aren't marked as "static" and assigned to Default layer, while player is assigned to the Player layer.

What am I doing wrong? I'm sure I screwed up with the code, but can't figure out the problem.

1

1 Answers

1
votes

Replace


    void OnCollisionExit(Collision other)
    {
        //Did we stop touching the wall object?
        if (!other.gameObject.CompareTag("Walls"))
        {
            isWall = false;
        }
    }

With


    void OnCollisionExit(Collision other)
    {
        //Did we stop touching the wall object?
        if (other.gameObject.CompareTag("Walls"))
        {
            isWall = false;
        }
    }