如何解决地面检查问题?


12

我注意到Unity的第三人称控制员的地面检查出现问题。

地面检查应检测玩家是否站在地面上。它是通过在播放器下方发出光线来实现的。

但是,如果玩家站在两个盒子的顶部和中间,并且在两个盒子之间有一个空间,则射线会射入空隙中,并且玩家会认为他没有与地面接触,如下所示:

在此处输入图片说明

在此处输入图片说明

我无法动弹。您可以清楚地看到射线在间隙中,因此播放器动画师的机载混合树处于活动状态。

解决此问题的最佳方法是什么?

我当时正在考虑从同一来源但以不同角度拍摄多条光线。并且OnGround仅当这些光线的X%到达“地面”时才应为真。或者,还有更好的方法?

Answers:


18

如其他答案所述,多射线在大多数情况下都可以正常工作。

您还可以使用更广泛的支票-例如球形或盒式广播。它们使用与光线投射相同的概念,但具有一定体积的几何图元,因此它不会滑入比角色可能掉入的狭窄裂缝。它还抓住了Shadows in Rain提到的情况,您的角色站在狭窄的管道上,可能会被其两边的射线投射所错过。

触发对撞机仅突出到角色对撞机底部的下方,即可完成类似的任务。像盒式铸造球体一样,它具有一定的宽度以检测间隙两侧的地面。在这里,您将使用OnTriggerEnter来检测该地面传感器何时与地面接触。


2
一如既往的出色答案,但是这种方法在性能上是否“较重”?我想这是Unity必须计算与球体/盒子模型和地面的交点的方法,所以..射线投射不是这样做的更有效的方法吗?

9
不严格地说。球面广播在数学上与射线广播非常相似-我们可以将其视为单个传播点,但具有“厚度”偏移。在我的剖析中,检查整个球体仅花费大约30-50%的额外费用,而不是平均只检查一条光线。这意味着发射一个球体而不是发射两个射线可以节省多达25%的性能。对于仅一次执行几次帧的简短检查,这两种方法都不会产生很大的不同,但是您始终可以通过分析几个选项来验证这一点。
DMGregory

球体检查绝对是在化身上使用胶囊对撞机的方法。
斯蒂芬

是否有调试功能?例如喜欢Debug.DrawLine?很难想象,我无法编写脚本。
黑色

1
@Black我们总是可以使用Debug.DrawLine作为构建块来编写自己的可视化例程。:)
DMGregory

14

老实说,我认为“多射线”方法是一个好主意。不过,我不会以一定角度拍摄它们,而是会有点偏移光线,如下所示:

在此处输入图片说明

玩家是蓝色火柴人;绿色箭头表示其他光线,橙色点(RaycastHits)是两条光线击中方框的点。

理想情况下,两条绿线应位于播放器脚下,以便最精确地检查播放器是否接地;)


7
站在边缘或细小的物体(例如管道)上时将无法工作。它基本上是相同缺陷方法的蛮力版本。如果您仍要使用它,请确保将棋子向着漏掉的射线的原点滑动(对于每个漏斗,并且至少在它们很少的情况下),以确保它从边缘滑落。
雨中的阴影

2
如果要面对“幸运”方向,则至少需要使用3种方法来防止两条光线跳入裂纹。
斯蒂芬,

3
在我从事的PS2游戏中,我每帧向下投射25个球体(以玩家下方的5x5网格模式),只是为了确定地面在玩家下方的位置。也许这有点荒谬,但是如果我们能够在PS2上做到这一点,那么您就可以在现代机器上承受一些额外的碰撞测试。:)
Trevor Powell

@TrevorPowell是的,当我对性能说“更重”时,我的意思是“””“更重””””,因为我知道这不会对游戏产生重大影响,但是我仍然想知道什么是最有效的的方法:)

2
(老实说,自那以后,我再也无法使用这么多碰撞测试;该PS2游戏引擎拥有疯狂的快速射线/球形广播,我希望我知道它是如何做到的)。但是,有很多的球形广播是很棒的。这意味着我可以检测到悬崖和其他地面特征,以更加智能地了解玩家应该站立的高度。
Trevor Powell

1

我想我通过更改Physics.RaycastPhysics.SphereCast脚本来解决了ThirdPersonCharacter.cs。但是它仍然需要测试。

bool condition = Physics.SphereCast(
    m_Capsule.transform.position + m_Capsule.center + (Vector3.up * 0.1f),
    m_Capsule.height / 2,
    Vector3.down, 
    out hitInfo,
    m_GroundCheckDistance
);

我还必须注释掉这行更改m_GroundCheckDistance值的行,否则某些模型会出现一些奇怪的变化:

    void HandleAirborneMovement()
    {
        // apply extra gravity from multiplier:
        Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity;
        m_Rigidbody.AddForce(extraGravityForce);

        //m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;
    }

我改m_GroundCheckDistance = 0.1f;m_GroundCheckDistance = m_OrigGroundCheckDistance;

    void HandleGroundedMovement(bool crouch, bool jump)
    {
        // check whether conditions are right to allow a jump:
        if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded"))
        {
            // jump!
            m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);
            m_IsGrounded = false;
            m_Animator.applyRootMotion = false;
            m_GroundCheckDistance = m_OrigGroundCheckDistance;
        }
    }

整个脚本:

using UnityEngine;

namespace UnityStandardAssets.Characters.ThirdPerson
{
    [RequireComponent(typeof(Rigidbody))]
    [RequireComponent(typeof(CapsuleCollider))]
    [RequireComponent(typeof(Animator))]
    public class ThirdPersonCharacter : MonoBehaviour
    {
        [SerializeField] float m_MovingTurnSpeed = 360;
        [SerializeField] float m_StationaryTurnSpeed = 180;
        [SerializeField] float m_JumpPower = 12f;
        [Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;
        [SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others
        [SerializeField] float m_MoveSpeedMultiplier = 1f;
        [SerializeField] float m_AnimSpeedMultiplier = 1f;
        [SerializeField] float m_GroundCheckDistance = 0.1f;

        Rigidbody m_Rigidbody;
        Animator m_Animator;
        bool m_IsGrounded;
        float m_OrigGroundCheckDistance;
        const float k_Half = 0.5f;
        float m_TurnAmount;
        float m_ForwardAmount;
        Vector3 m_GroundNormal;
        float m_CapsuleHeight;
        Vector3 m_CapsuleCenter;
        CapsuleCollider m_Capsule;
        bool m_Crouching;


        void Start()
        {
            m_Animator = GetComponent<Animator>();
            m_Rigidbody = GetComponent<Rigidbody>();
            m_Capsule = GetComponent<CapsuleCollider>();
            m_CapsuleHeight = m_Capsule.height;
            m_CapsuleCenter = m_Capsule.center;

            m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;
            m_OrigGroundCheckDistance = m_GroundCheckDistance;
        }

        public void Move(Vector3 move, bool crouch, bool jump)
        {

            // convert the world relative moveInput vector into a local-relative
            // turn amount and forward amount required to head in the desired
            // direction.
            if (move.magnitude > 1f) move.Normalize();

            move = transform.InverseTransformDirection(move);
            CheckGroundStatus();
            move = Vector3.ProjectOnPlane(move, m_GroundNormal);
            m_TurnAmount = Mathf.Atan2(move.x, move.z);
            m_ForwardAmount = move.z;

            ApplyExtraTurnRotation();

            // control and velocity handling is different when grounded and airborne:
            if (m_IsGrounded) {
                HandleGroundedMovement(crouch, jump);
            } else {
                HandleAirborneMovement();
            }

            ScaleCapsuleForCrouching(crouch);
            PreventStandingInLowHeadroom();

            // send input and other state parameters to the animator
            UpdateAnimator(move);


        }

        void ScaleCapsuleForCrouching(bool crouch)
        {
            if (m_IsGrounded && crouch)
            {
                if (m_Crouching) return;
                m_Capsule.height = m_Capsule.height / 2f;
                m_Capsule.center = m_Capsule.center / 2f;
                m_Crouching = true;
            }
            else
            {
                Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
                float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
                if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength, Physics.AllLayers, QueryTriggerInteraction.Ignore))
                {
                    m_Crouching = true;
                    return;
                }
                m_Capsule.height = m_CapsuleHeight;
                m_Capsule.center = m_CapsuleCenter;
                m_Crouching = false;
            }
        }

        void PreventStandingInLowHeadroom()
        {
            // prevent standing up in crouch-only zones
            if (!m_Crouching)
            {
                Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
                float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
                if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength, Physics.AllLayers, QueryTriggerInteraction.Ignore))
                {
                    m_Crouching = true;
                }
            }
        }

        void UpdateAnimator(Vector3 move)
        {
            // update the animator parameters
            m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime);
            m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime);
            m_Animator.SetBool("Crouch", m_Crouching);
            m_Animator.SetBool("OnGround", m_IsGrounded);
            if (!m_IsGrounded) {
                m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y);
            }

            // calculate which leg is behind, so as to leave that leg trailing in the jump animation
            // (This code is reliant on the specific run cycle offset in our animations,
            // and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5)
            float runCycle =
                Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1);

            float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount;
            if (m_IsGrounded) {
                m_Animator.SetFloat("JumpLeg", jumpLeg);
            }

            // the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector,
            // which affects the movement speed because of the root motion.
            if (m_IsGrounded && move.magnitude > 0) {
                m_Animator.speed = m_AnimSpeedMultiplier;
            } else {
                // don't use that while airborne
                m_Animator.speed = 1;
            }
        }

        void HandleAirborneMovement()
        {
            // apply extra gravity from multiplier:
            Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity;
            m_Rigidbody.AddForce(extraGravityForce);

            //m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;
        }

        void HandleGroundedMovement(bool crouch, bool jump)
        {
            // check whether conditions are right to allow a jump:
            if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded"))
            {
                // jump!
                m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);
                m_IsGrounded = false;
                m_Animator.applyRootMotion = false;
                //m_GroundCheckDistance = 0.1f;
            }
        }

        void ApplyExtraTurnRotation()
        {
            // help the character turn faster (this is in addition to root rotation in the animation)
            float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount);
            transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0);
        }

        public void OnAnimatorMove()
        {
            // we implement this function to override the default root motion.
            // this allows us to modify the positional speed before it's applied.
            if (m_IsGrounded && Time.deltaTime > 0)
            {
                Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime;

                // we preserve the existing y part of the current velocity.
                v.y = m_Rigidbody.velocity.y;
                m_Rigidbody.velocity = v;
            }
        }

        void CheckGroundStatus()
        {
            RaycastHit hitInfo;

#if UNITY_EDITOR
            // helper to visualise the ground check ray in the scene view

            Debug.DrawLine(
                m_Capsule.transform.position + m_Capsule.center + (Vector3.up * 0.1f),
                m_Capsule.transform.position + (Vector3.down * m_GroundCheckDistance), 
                Color.red
            );

#endif
            // 0.1f is a small offset to start the ray from inside the character
            // it is also good to note that the transform position in the sample assets is at the base of the character
            bool condition = Physics.SphereCast(
                m_Capsule.transform.position + m_Capsule.center + (Vector3.up * 0.1f),
                m_Capsule.height / 2,
                Vector3.down, 
                out hitInfo,
                m_GroundCheckDistance
            );

            if (condition) {
                m_IsGrounded = true;
                m_GroundNormal = hitInfo.normal;
                m_Animator.applyRootMotion = true;

            } else {
                m_IsGrounded = false;
                m_GroundNormal = Vector3.up;
                m_Animator.applyRootMotion = false;
            }
        }
    }
}

0

为什么不使用Unity的OnCollisionStay函数?

优点:

  • 您不必创建raycast。

  • 它比raycast更准确:Raycast是一种从检查到检查的方法,如果您的raycast拍摄没有足够的覆盖率,那么它会导致bug,这就是您问这个问题的原因。OnCollisionStay方法从字面上检查是否有东西在触摸-完全适合于目的,以检查玩家是否正在接触地面(或玩家可以降落的任何物体)。

有关代码和演示,请查看以下答案:http : //answers.unity.com/answers/1547919/view.html

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.