1
\$\begingroup\$

I tried to make a crouch system. In my script I changed .height and .center properties of collider in CharacterController component. But there are some problems. First, once I press "crouch button" collider is changing its height and center position but when I release the key the collider isn't returning to its previous state. Secondly, if I change its height and position by hand when collider is under a block, it's just stuck. How can I fix this problem?

Here's my script:

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

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour {

    public float moveSpeed;
    public float jumpForce;
    public CharacterController controller;
    public GameObject myCamera;

    private Vector3 moveDirection = Vector3.zero;
    public float gravityScale;

    public Animator anim;

    // Use this for initialization
    void Start () {
        transform.tag = "Player";
        controller = GetComponent<CharacterController>();       
    }

    // Update is called once per frame
    void Update () {
        Vector3 forwardDirection = new Vector3 (myCamera.transform.forward.x, 0, myCamera.transform.forward.z);
        Vector3 sideDirection = new Vector3(myCamera.transform.right.x, 0, myCamera.transform.right.z);
        forwardDirection.Normalize ();
        sideDirection.Normalize ();
        forwardDirection = forwardDirection * Input.GetAxis("Vertical");
        sideDirection = sideDirection * Input.GetAxis("Horizontal");
        Vector3 directFinal = forwardDirection + sideDirection;

        if (directFinal.sqrMagnitude > 1)
        {
            directFinal.Normalize ();
        }

        if (controller.isGrounded)
        {
            moveDirection = new Vector3(directFinal.x, 0, directFinal.z);
            moveDirection *= moveSpeed;
            if (Input.GetButtonDown("Jump"))
            {
                moveDirection.y = jumpForce;
            }

            if(Input.GetButtonDown("Crouch"))
            {
                controller.height = 1f;
                controller.center = new Vector3(0f, -0.5f, 0f);
                moveSpeed = 3f;
            }
        }
        moveDirection.y = moveDirection.y + (Physics.gravity.y * gravityScale * Time.deltaTime);
        controller.Move(moveDirection * Time.deltaTime);

        anim.SetBool("isGrounded", controller.isGrounded);
        anim.SetFloat("Speed", (Mathf.Abs(Input.GetAxis("Vertical")) + Mathf.Abs(Input.GetAxis("Horizontal"))));
    }
}

Thanks for any help!

\$\endgroup\$
0
\$\begingroup\$

Warning: All code in this answer is untested. It only serves to illustrate the concepts. Do not assume it will just work out-of-the-box without problems when you copy it into your game.

In order to return the character to the original state, you need to save the information what that state actually was. You could of course hard-code the original values, but then you run into problems if you ever decide to change them for some reasons. So I would recommend you to read the original values in your Start method and back them up to a bunch of private variables.

(I would also recommend to turn the currently hardcoded crouched height and center into inspector properties, but that just by the way).

Add these variables to the behaviour:

private Vector3 originalCenter;
private float originalHeight;
private float originalMoveSpeed;

Assign their values in your Start method like this:

void Start () {
    transform.tag = "Player";
    controller = GetComponent<CharacterController>();   
    originalCenter = controller.center;
    originalHeight = controller.height;
    originalMoveSpeed = moveSpeed;  
}

Then in your Update-method, don't just set new values when the player crouches, also assign the old values when the player stops crouching:

    if(Input.GetButtonUp("Crouch")) {
        controller.height = originalHeight;
        controller.center = originalCenter;
        moveSpeed = originalMoveSpeed;
    }

To fix the problem with the character "growing" inside an obstacle and getting stuck, we need to further enhance this section of the update method to also check if the area above the character is actually free before restoring the original dimensions. You can do that with the method Physics.OverlapCapsule:

if(Input.GetButtonUp("Crouch")) {
    Vector3 point0 = transform.position + originalCenter - new Vector3(0.0f, originalHeight, 0.0f);           
    Vector3 point1 = transform.position + originalCenter + new Vector3(0.0f, originalHeight, 0.0f);
    if (Physics.OverlapCapsule(point0, point1, controller.radius).Length == 0) {
       controller.height = originalHeight;
       controller.center = originalCenter;
       moveSpeed = originalMoveSpeed;
    }
}
\$\endgroup\$
  • \$\begingroup\$ For some reason it doesn't work, looks like while I press "crouch key" originalHeight, originalCenter and originalMoveSpeed are rewriting \$\endgroup\$ – Hyperka Apr 2 at 13:42
  • \$\begingroup\$ Found my mistake in code. Also thanks for help! \$\endgroup\$ – Hyperka Apr 2 at 17:08
0
\$\begingroup\$

Looks like you forgot to put the collider back the way you found it on key release:

// Let's set up our capsule & speed parameters
// so we can easily tune them in the Unity Inspector.
[System.Serializable]
public struct StateSettings {
    public Vector3 capsuleCenter;
    public float capsuleHeight;
    public float movementSpeed;
}

public StateSettings standing;
public StateSettings crouching;

[Tooltip("Physics layers of obstacles that should stop the crouched player from standing")]
public LayerMask obstacleLayers;

// Keep track of our current state.
bool _isCrouching;

// ...

void Update() {

    // ...

    if(_isCrouching == false) {
        if(Input.GetButtonDown("Crouch"))
        {
            controller.height = crouching.capsuleHeight;
            controller.center = crouching.capsuleCenter;
            moveSpeed = crouching.movementSpeed;
            _isCrouching = true;
        }
    } else {
        // Every frame we're not trying to crouch, see if we can stand.
        // (This way it triggers even if we let go of the button, then crawled free)
        if(Input.GetButton("Crouch") == false) {

            // Watch your head!
            Vector3 newBottom = standing.capsuleCenter + Vector3.down * standing.capsuleHeight * 0.5f;                
            newBottom = transform.TransformPoint(newBottom);       

            RaycastHit hit;
            if(Physics.SphereCast(newBottom, controller.radius, transform.up, out hit, standing.capsuleHeight, obstacleLayers) == false) {
                // Stand up only if we found no hit, otherwise try again next frame.
                controller.height = standing.capsuleHeight;
                controller.center = standing.capsuleCenter;
                moveSpeed = standing.movementSpeed;
                _isCrouching = false;                    
            }
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ It doesn't work, says: "All compiler errors have to be fixed before you can enter playmode". \$\endgroup\$ – Hyperka Apr 2 at 14:27
  • \$\begingroup\$ We'll need to know what the compiler error was before we can correct it. In general, you should treat code examples you find online as examples to learn strategies from, not readymade code to copy & paste. \$\endgroup\$ – DMGregory Apr 2 at 15:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.