Devlog: Crouching Ability

After my rant about relying too much on premade Assets, I’m making progress on my new custom character controller. The first and maybe most important Part of stealth locomotion is the Crouching/Sneaking ability. So I opened up Etras Starter Assets, inherited the base ability class and wrote my solution from scratch.

WebGL Demo

Features

  • The crouching ability can be triggered by toggling or holding
  • An “over-the-shoulder” cinemachine camera gets activated, as soon as the view gets narrower
  • The player will only stand up if there is enough headroom, to reduce clipping
  • The component will detect if there is a space in walking direction where crouching is possible
    • Optional Layermask detection
    • Enables crouching automatically
    • Checks if the space is between crouching height and standing height to minimize unwanted detections
C#
    private bool DetectCrouchableObstacle()
    {
        if(m_IsCrouching) return false;
        var rayCastOrigin = autoCrouchRaycastOrigin.position;
        var rayCastOriginlocalPosition = autoCrouchRaycastOrigin.localPosition;
        
        var isAboveStandingHeight = Physics.Raycast(rayCastOrigin, Vector3.up, defaultStandingHeight - rayCastOriginlocalPosition.y, autoCrouchDetectionMask);
        var isAboveCrouchHeight = Physics.Raycast(rayCastOrigin, Vector3.up, crouchHeight - rayCastOriginlocalPosition.y, autoCrouchDetectionMask);
        return isAboveStandingHeight && !isAboveCrouchHeight;
    }

Things to improve

  • Enable Rootmotion
  • Fine tuning of the camera behavior – Update existing camera instead of adding a new one.

Challenges

  • Unity Character Controller behaviour
    • When switching back to standing height too early, the player collided with the walls and got catapulted in walking direction
    • I tried it with lerping from crouch to stand first, but that led to different issues
    • Fix: Check for collisions on the whole character radius with boxcast instead of raycast
C#
    private bool CanStandUp()
    {
        return !Physics.BoxCast(transform.position, characterController.bounds.extents, Vector3.up, transform.rotation, defaultStandingHeight, obstacleMask);
    }

Main Take aways

I set myself the challenge when writing the component and for future components that it must be immediately understandable even to outsiders. That was kind of really fun. Also:

Leave a Comment