NOTE: First-person crouching is included in the "First Person Player Prefab" package on AC's Downloads page.
Crouching in first-person can be implemented in a number of ways, and this script demonstrates one possible method. It allows for animation, slowdown when crouching, and raycasting to prevent standing in walls.
To use it, set your game's Movement method to First Person, and copy the script below into a file named "PlayerCrouch.cs" and attach it to your Player prefab. Then assign the following in its Inspector:
- The "Crouch Input Button" field entry must be listed in Unity's Input Manager.
- The "Camera Parent" as a new empty GameObject in between your Player's root object and First Person Camera. This new object should be positioned where your First Person Camera is (so that the camera Transform has a local position of 0,0,0).
- The "Normal Collider" and "Crouch Collider" as two separate Collider gameobjects - the first for when standing, the second for when crouching. The script will enable/disable these automaticaly
- The "Stand Box Trigger" as a Box Collider that covers the volume that should be free of any other colliders for standing to be possible. The raycasting will check for layers in the "Stand Layer Mask".
- Optionally, an Animator can be assigned. The "Crouch Mecanim Bool Parameter" refers to a boolean parameter on the Animator that will be set to True or False automatically.
using UnityEngine;
using System.Collections;
using AC;
public class PlayerCrouch : MonoBehaviour
{
public Player player;
public string crouchInputButton = "ToggleCrouch";
public Transform cameraParent;
public Collider normalCollider;
public Collider crouchCollider;
public BoxCollider standBoxTrigger;
public LayerMask standLayerMask;
public Animator animator;
public string crouchMecanimBoolParameter = "Crouch";
[Range(0f,1f)] public float fpsHeightReduction = 0.5f;
[Range(0f,1f)] public float walkSpeedReduction = 0.5f;
[Range(0f,1f)] public float runSpeedReduction = 0.5f;
public float transitionTime = 0.3f;
private float changeTime;
private bool isCrouching = false;
private float normalFPSHeight = 1f;
private float normalWalkSpeed;
private float normalRunSpeed;
private float targetFPSHeight;
private float startFPSHeight;
private void Start ()
{
if (player == null)
{
player = GetComponent <Player>();
}
if (player != null)
{
normalWalkSpeed = player.walkSpeedScale;
normalRunSpeed = player.runSpeedScale;
if (cameraParent)
{
targetFPSHeight = normalFPSHeight = cameraParent.localPosition.y;
}
}
Stand (true);
}
private void Update ()
{
if (KickStarter.playerInput.InputGetButtonDown (crouchInputButton))
{
if (isCrouching)
{
Stand ();
}
else
{
Crouch ();
}
}
if (changeTime > 0f)
{
changeTime -= Time.deltaTime;
if (cameraParent != null)
{
Vector3 cameraPosition = cameraParent.localPosition;
if (changeTime <= 0f)
{
changeTime = 0f;
cameraPosition.y = targetFPSHeight;
}
else
{
float proportion = 1f - (changeTime / transitionTime);
cameraPosition.y = Mathf.Lerp (startFPSHeight, targetFPSHeight, proportion);
}
cameraParent.localPosition = cameraPosition;
}
}
}
private void Crouch (bool force = false)
{
if (force || CanCrouch ())
{
isCrouching = true;
normalCollider.enabled = false;
crouchCollider.enabled = true;
if (player != null)
{
player.walkSpeedScale = normalWalkSpeed * walkSpeedReduction;
player.runSpeedScale = normalRunSpeed * runSpeedReduction;
targetFPSHeight = normalFPSHeight * fpsHeightReduction;
changeTime = transitionTime;
startFPSHeight = normalFPSHeight;
}
if (animator != null && crouchMecanimBoolParameter.Length > 0)
{
animator.SetBool (crouchMecanimBoolParameter, true);
}
}
}
private void Stand (bool force = false)
{
if (force || CanStand ())
{
isCrouching = false;
crouchCollider.enabled = false;
normalCollider.enabled = true;
if (player != null)
{
player.walkSpeedScale = normalWalkSpeed;
player.runSpeedScale = normalRunSpeed;
targetFPSHeight = normalFPSHeight;
changeTime = transitionTime;
startFPSHeight = normalFPSHeight * fpsHeightReduction;
}
if (animator != null && crouchMecanimBoolParameter.Length > 0)
{
animator.SetBool (crouchMecanimBoolParameter, false);
}
}
}
private bool CanStand ()
{
if (changeTime <= 0f)
{
if (standBoxTrigger != null)
{
float boxBottomHeight = standBoxTrigger.bounds.ClosestPoint (transform.position).y;
Vector3 boxBottomCentre = new Vector3 (standBoxTrigger.bounds.center.x, boxBottomHeight, standBoxTrigger.bounds.center.z);
float boxHeight = standBoxTrigger.size.y * standBoxTrigger.transform.localScale.y;
Vector3 crouchHalfExtents = new Vector3 (standBoxTrigger.size.x * standBoxTrigger.transform.localScale.x / 2f, boxHeight / 10f, standBoxTrigger.size.z * standBoxTrigger.transform.localScale.z / 2f);
float crouchRayDistance = boxHeight - crouchHalfExtents.y;
RaycastHit hitInfo;
Debug.DrawRay (boxBottomCentre, Vector3.up * crouchRayDistance, Color.red);
bool hitSomething = Physics.BoxCast (boxBottomCentre, crouchHalfExtents, Vector3.up, out hitInfo, Quaternion.identity, crouchRayDistance, standLayerMask);
if (hitSomething)
{
return false;
}
}
return true;
}
return false;
}
private bool CanCrouch ()
{
if (changeTime <= 0f)
{
if (player != null && player.IsGrounded ())
{
return true;
}
}
return false;
}
}