0
\$\begingroup\$

I have a "Floating Capsule Collider" setup, but it has an issue when the Collider stands on the very edge of a platform-- the player Mesh ends up looking 'stuck' inside of the platform. How might this be solved?

I tried this approach: Detect if Mesh is 'floating' longer than x seconds; if it is, then add downward force. However, this effect feels unnatural when playing; I was curious if anyone had different suggestions.

(Left) is an image of the issue I'm referencing:

enter image description here

If useful, here's the current controller code. It uses a SpringJoint to keep the Capsule floating above ground. But I think this problem would exist with any floating Collider approach.

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

public class Valet1 : MonoBehaviour
{
    Rigidbody r;
    public CapsuleCollider capsule;
    public Animator animator;
    public GameObject maleModel;
    [SerializeField] float stepsSinceLastGrounded = 0;

    public float maleModelCapsuleOffset;
    public float maleModelRigidbodyOffset;
    public float sphereCheckNumber;

    public float raycastDistance;
    public bool _rayDidHit;
    
    public Vector3 DownDir;
    public float RideSpringDamper;
    public float RideSpringStrength;
    public float RideHeight;
    RaycastHit _rayHit;

    //standard
    //Base Movement
    public float horizontalInput;
    public float verticalInput;
    public Vector3 moveDirection;
    public Vector3 moveDirectionRaw;
    public int rotDegPerSecond = 720;
    public float speed;
    public Transform cameraTransform;


    //GroundChecking
    public bool isGroundedCloser;
    public Transform groundCheckTransform;
    public float groundCheckRadius = 0.25f;
    public LayerMask groundLayerMask;
    public float distanceToGround;

    //FloatChecking
    public bool isFloating;
    public float stepsDistanceToGroundGreater = 0.0f;
    public float stepsSinceLastJump = 0.0f;

    //jump
    public bool jump;
    public ultimate_timer jumpTimer;
    [SerializeField] float jumpForce = 500f;
    [SerializeField] float snapAfterJumpLimit = 1.6f;

    // Start is called before the first frame update
    void Awake()
    {
        r = GetComponent<Rigidbody>();
        capsule = GetComponent<CapsuleCollider>();
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        horizontalInput = Input.GetAxis("Horizontal");
        verticalInput = Input.GetAxis("Vertical");
        if (Physics.SphereCast(transform.position, sphereCheckNumber, -transform.up, out _rayHit, raycastDistance))
        {
            _rayDidHit = true;
        }
        else
        {
            _rayDidHit = false;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            stepsSinceLastJump = 0;
            jump = true;
        }
    }

    private void FixedUpdate()
    {
        GroundCheck();
        FloatCheck();

        

        Ray();
        

        moveDirection = new Vector3(horizontalInput, 0.0f, verticalInput).normalized;
        moveDirection = (Quaternion.AngleAxis(cameraTransform.rotation.eulerAngles.y, Vector3.up) * moveDirection).normalized;
        moveDirectionRaw = new Vector3(Input.GetAxisRaw("Horizontal"), 0.0f, Input.GetAxisRaw("Vertical")).normalized;
        moveDirectionRaw = (Quaternion.AngleAxis(cameraTransform.rotation.eulerAngles.y, Vector3.up) * moveDirectionRaw).normalized;



        if (jump == true)
        {
            raycastDistance = 0f;
            r.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
            jump = false;
        }

        if (distanceToGround <= snapAfterJumpLimit)
        {
            raycastDistance = 82.5f;
        }

        


        if (moveDirectionRaw != Vector3.zero && moveDirection != Vector3.zero)
        {
            animator.SetBool("isMoving", true);
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection); //or moveDirectionRaw
            targetRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotDegPerSecond * Time.deltaTime);
            r.MoveRotation(targetRotation);
        }
        else
        {
            animator.SetBool("isMoving", false);
        }

        Vector3 force = moveDirection * speed;
        r.AddForce(force, ForceMode.VelocityChange);


        GroundStopSlide();
        UpdateState();
    }

    void UpdateState()
    {
        stepsSinceLastJump += 1;

        stepsSinceLastGrounded += 1;


        if (distanceToGround > 1.9)
        {
            stepsDistanceToGroundGreater += 1;
        }
        else
        {
            stepsDistanceToGroundGreater = 0;
        }
    }

    void GroundStopSlide()
    {
        if (moveDirectionRaw == Vector3.zero)
        {
            Vector3 zeroMe = new Vector3(0, r.velocity.y, 0);
            r.velocity = zeroMe;
        }
    }

    void Ray()
    {
        if (_rayDidHit)
        {
            Vector3 vel = r.velocity;
            Vector3 rayDir = transform.TransformDirection(DownDir);

            Vector3 otherVel = Vector3.zero;
            Rigidbody hitBody = _rayHit.rigidbody;

            if (hitBody != null)
            {
                otherVel = hitBody.velocity;
            }

            float rayDirVel = Vector3.Dot(rayDir, vel);
            float otherDirVel = Vector3.Dot(rayDir, otherVel);

            float relVel = rayDirVel - otherDirVel;

            float x = _rayHit.distance - RideHeight;

            float springForce = (x * RideSpringStrength) - (relVel * RideSpringDamper);

            Debug.DrawLine(transform.position, transform.position + (rayDir * springForce), Color.yellow);

            r.AddForce(rayDir * springForce);

            if (hitBody != null)
            {
                hitBody.AddForceAtPosition(rayDir * -springForce, _rayHit.point);
            }
        }

    }
    public void GroundCheck()
    {
        isGroundedCloser = Physics.CheckSphere(groundCheckTransform.position, groundCheckRadius, groundLayerMask);

        RaycastHit groundHit = new RaycastHit();
        if (Physics.Raycast(transform.position, -Vector3.up, out groundHit))
        {
            distanceToGround = groundHit.distance;
        }

        maleModel.transform.position = new Vector3(capsule.transform.position.x, Mathf.Max(capsule.transform.position.y, 1.69346f), capsule.transform.position.z)
            + new Vector3(0, maleModelRigidbodyOffset, 0);


        if (isGroundedCloser)
        {
            stepsSinceLastGrounded = 0;
        }
    }

    public void FloatCheck()
    {

        if (stepsSinceLastJump > 60 && stepsDistanceToGroundGreater > 10)
        {
            r.AddForce(Vector3.down * 100f, ForceMode.Force);
            isFloating = true;
            print("Floating for too long- Adding force");
        }
    }


}
\$\endgroup\$
2
  • \$\begingroup\$ May I ask what do you mean with floating capsule collider setup? What is for exactly? Are you by any chances following this tutorial? \$\endgroup\$
    – YoshGJ
    Commented May 3, 2022 at 3:09
  • \$\begingroup\$ Sorry about delay in response. No, this was with Very Very Valet tutorial. It is really cool: youtube.com/watch?v=qdskE8PJy6Q \$\endgroup\$
    – yunum
    Commented Feb 4, 2023 at 17:08

1 Answer 1

1
\$\begingroup\$

Thanks for addressing this problem. I'm also working on my custom floating capsule controller and mine has the same problem.

Adding more downward force would be very costly as it affects the play experience. A more effective way to solve this is to add a sphere collider at the bottom of the capsule collider and create a frictionless physics material for the platforms. Now, the bottom is more round due to the smaller radius of the sphere collider, making it easier for the character to slide past the edge.

enter image description here

enter image description here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .