วิธีการแก้ปัญหาการตรวจสอบพื้นดิน?


12

ฉันสังเกตเห็นปัญหาในการตรวจสอบภาคสนามบุคคลที่สามของ Unity

การตรวจสอบภาคพื้นดินควรตรวจสอบว่าผู้เล่นยืนอยู่บนพื้นดินหรือไม่ มันทำได้โดยการส่งรังสีออกมาด้านล่างของเครื่องเล่น

อย่างไรก็ตามหากผู้เล่นยืนอยู่ด้านบนและตรงกลางของสองกล่องและมีช่องว่างระหว่างกล่องเหล่านี้รังสีจะยิงเข้าสู่ช่องว่างและผู้เล่นคิดว่าเขาไม่ได้สัมผัสกับพื้นดินซึ่งมีลักษณะดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่

ฉันไม่สามารถเคลื่อนไหวได้ คุณสามารถเห็นได้อย่างชัดเจนว่ารังสีอยู่ในช่องว่างและทำให้ต้นไม้ของผู้ผสมภาพเคลื่อนไหวในอากาศเคลื่อนไหว

วิธีที่ดีที่สุดในการแก้ปัญหานี้คืออะไร?

ฉันคิดถึงการถ่ายภาพรังสีหลายดวงจากแหล่งกำเนิดเดียวกัน แต่มีมุมต่างกัน และOnGroundควรเป็นจริงถ้า X% ของรังสีเหล่านี้กระทบกับ "พื้นดิน" หรือมีวิธีที่ดีกว่า

คำตอบ:


18

รังสีส่วนใหญ่ทำงานได้ดีในกรณีส่วนใหญ่ดังที่อธิบายไว้ในคำตอบอื่น ๆ

นอกจากนี้คุณยังสามารถใช้การตรวจสอบที่กว้างขึ้นเช่น spherecast หรือ boxcast สิ่งเหล่านี้ใช้แนวคิดเดียวกันกับ raycast แต่มีรูปทรงเรขาคณิตดั้งเดิมที่มีปริมาตรบางส่วนดังนั้นจึงไม่สามารถแอบเข้าไปในรอยแตกที่แคบกว่าตัวละครของคุณ นอกจากนี้ยังจับเคส Shadows In Rain ที่กล่าวถึงตัวละครของคุณยืนอยู่บนท่อแคบ ๆ ที่อาจถูก raycast พลาดไปในแต่ละด้านของมัน

ทริกเกอร์คอลไลเดอร์ที่ยื่นออกมาเล็กน้อยด้านล่างด้านล่างของคอลไลเดอร์ตัวละครของคุณสามารถทำงานที่คล้ายกันได้ เช่นเดียวกับกล่องทรงกลมที่มีความกว้างในการตรวจจับพื้นดินทั้งสองด้านของช่องว่าง ที่นี่คุณจะต้องใช้ OnTriggerEnter เพื่อตรวจสอบเมื่อเซ็นเซอร์ภาคพื้นดินนี้ได้สัมผัสกับพื้นดิน


2
คำตอบที่ยอดเยี่ยมเช่นเคย แต่วิธีนี้ไม่ "หนัก" กับประสิทธิภาพหรือไม่ ฉันคิดว่าวิธีนี้มีความเป็นเอกภาพในการคำนวณทางแยกที่มีทรงกลม / กล่องและพื้นดินดังนั้น .. ไม่ได้ raycasts วิธีที่มีประสิทธิภาพมากขึ้นในการทำเช่นนี้?

9
ไม่พูดอย่างเคร่งครัด Spherecast นั้นมีลักษณะทางคณิตศาสตร์ค่อนข้างคล้ายคลึงกับรังสีแคสต์เราสามารถคิดได้ว่ามันเป็นเพียงจุดเดินทางเดียว แต่มีค่าความหนา "ชดเชย" ในการจัดทำโปรไฟล์ของฉันมีค่าใช้จ่ายเพียงประมาณ 30-50% พิเศษในการตรวจสอบทรงกลมเต็มรูปแบบแทนที่จะเป็นแสงเดียวโดยเฉลี่ย ซึ่งหมายความว่าการยิงหนึ่งทรงกลมแทนสองรังสีสามารถประหยัดสุทธิในประสิทธิภาพการทำงานสูงถึง ~ 25% เป็นไปได้ยากที่จะสร้างความแตกต่างอย่างมากไม่ว่าจะเป็นการตรวจสอบระยะสั้นที่คุณแสดงอยู่เพียงไม่กี่ครั้งต่อเฟรม
DMGregory

การตรวจสอบของทรงกลมเป็นวิธีที่จะไปด้วย collider แคปซูลใน avatar
เตฟาน

มีฟังก์ชั่นการแก้ปัญหาสำหรับสิ่งนี้หรือไม่? เช่นชอบDebug.DrawLine? มันยากที่จะมองเห็นฉันไม่สามารถเขียนสคริปต์ได้
Black

1
@ สีดำเราสามารถเขียนขั้นตอนการสร้างภาพของเราเองโดยใช้ Debug.DrawLine เป็นแบบเอกสารสำเร็จรูป :)
DMGregory

14

ฉันคิดอย่างสุจริตว่าวิธี "หลายรังสี" เป็นแนวคิดที่ดีทีเดียว ฉันจะไม่ยิงพวกมันในมุมที่ แต่ฉันจะชดเชยแสงแบบนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ผู้เล่นเป็น stickman สีน้ำเงิน ลูกศรสีเขียวแสดงให้เห็นถึงรังสีเพิ่มเติมและจุดสีส้ม (RaycastHits) เป็นจุดที่รังสีทั้งสองพุ่งเข้าชนกล่อง

ในอุดมคติแล้วรังสีสีเขียวทั้งสองควรอยู่ในตำแหน่งใต้ฝ่าเท้าของผู้เล่นเพื่อให้ได้ความแม่นยำมากที่สุดในการตรวจสอบว่าผู้เล่นมีเหตุผลหรือไม่;)


7
จะไม่ทำงานในขณะที่ยืนอยู่บนขอบหรือวัตถุบาง ๆ (เช่นท่อ) โดยพื้นฐานแล้วมันเป็นวิธีการที่มีข้อบกพร่องเหมือนกัน หากคุณจะใช้มันต่อไปให้แน่ใจว่าจำนำหลุดจากขอบด้วยการเลื่อนไปทางต้นกำเนิดของรังสีพลาด (สำหรับแต่ละอัน
Shadows In Rain

2
คุณจะต้องมีอย่างน้อย 3 ด้วยวิธีนี้เพื่อป้องกันไม่ให้รังสีทั้งสองเข้าสู่รอยแตกหากหันหน้าไปทางทิศทาง "โชคดี"
เตฟาน

3
ในเกม PS2 ที่ฉันทำงานฉันได้ทรงกลม 25 อันหล่อลงแต่ละเฟรม (ในรูปแบบตาราง 5x5 ใต้ผู้เล่น) เพียงเพื่อกำหนดว่าพื้นอยู่ใต้ผู้เล่นหรือไม่ บางทีนั่นอาจเป็นเรื่องไร้สาระเล็กน้อย แต่ถ้าเราสามารถทำได้บน PS2 คุณสามารถที่จะใช้การทดสอบการชนกันเพิ่มเติมเกี่ยวกับเครื่องจักรที่ทันสมัย :)
Trevor Powell

@ TrevorPowell ใช่เมื่อฉันพูดว่า "หนัก" ในการแสดงฉันหมายถึง "" "" หนักกว่า "" "" เพราะฉันรู้ว่ามันจะไม่ส่งผลกระทบต่อเกม แต่ฉันยังอยากจะรู้ว่าอะไรที่มีประสิทธิภาพมากที่สุด วิธีทำสิ่งนี้ :)

2
(จากความจริงทั้งหมดฉันไม่สามารถใช้การทดสอบการชนหลายครั้งตั้งแต่นั้นมาเกมเอ็นจิ้น PS2 นั้นมี raycasts / spherecast ที่เร็วและบ้าคลั่ง แต่การมี spherecast จำนวนมากนั้นยอดเยี่ยมมาก นั่นหมายความว่าฉันสามารถตรวจจับหน้าผาและคุณสมบัติพื้นดินอื่น ๆ ได้อย่างชาญฉลาดเกี่ยวกับความสูงที่ผู้เล่นควรยืนอยู่
Trevor Powell

1

ผมคิดว่าผมแก้ไขได้โดยการเปลี่ยนPhysics.Raycastไปในสคริปต์Physics.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

ทำไมไม่ใช้ฟังก์ชันOnCollisionStayของ Unity

ข้อดี:

  • คุณไม่ต้องสร้าง raycast

  • มีความแม่นยำมากกว่า raycast: Raycast เป็นวิธีถ่ายภาพเพื่อตรวจสอบหากการถ่ายภาพรังสีของคุณไม่ครอบคลุมเพียงพอมันจะนำไปสู่ข้อผิดพลาดซึ่งเป็นสาเหตุที่คุณถามคำถามนี้ OnCollisionStayวิธีการตรวจสอบอย่างแท้จริงว่าสิ่งที่สัมผัส - มันสมบูรณ์แบบสำหรับวัตถุประสงค์ในการตรวจสอบว่าผู้เล่นกำลังสัมผัสพื้นดิน (หรือสิ่งที่ผู้เล่นสามารถลงจอดบน)

สำหรับรหัสและตัวอย่างให้ตรวจสอบคำตอบนี้: http://answers.unity.com/answers/1547919/view.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.