Yer kontrol problemi nasıl çözülür?


12

Unity'nin üçüncü şahıs denetleyicisinin yer kontrolünde bir sorun fark ettim.

Zemin kontrolü, oyuncunun yerde durup durmadığını tespit etmelidir. Bunu oynatıcının altına bir ışın göndererek yapar.

Ancak, oyuncu iki kutunun üstünde ve ortasında durursa ve bu kutular arasında bir boşluk varsa, ışın boşluğa vurur ve oyuncu zemine temas etmediğini düşünür:

resim açıklamasını buraya girin

resim açıklamasını buraya girin

Hareket edemiyorum. Işının boşlukta olduğunu ve böylece oyuncu animatörü havadaki karışım ağacının aktif olduğunu açıkça görebilirsiniz.

Bu sorunu çözmenin en iyi yolu nedir?

Aynı kaynaktan ama farklı açılardan birden fazla ışın çekmeyi düşünüyordum. Ve OnGroundbu doğru olmalı, eğer bu ışınların% X'i "yere" çarparsa. Yoksa daha iyi bir yol var mı?

Yanıtlar:


18

Birden fazla ışın diğer cevapta açıklandığı gibi çoğu durumda iyi çalışır.

Spherecast veya boxcast gibi daha geniş bir kontrol de kullanabilirsiniz. Bunlar bir raycast ile aynı kavramı kullanır, ancak bir miktar hacme sahip geometrik bir ilkel ile, karakterinizin düşebileceğinden daha dar çatlaklara kayamaz. Ayrıca, karakterin her iki yanındaki bir raycast tarafından kaçırılabilecek dar bir boru üzerinde durduğu Shadows In Rain'den bahsediyor.

Karakterinizin çarpıştırıcısının altından biraz daha küçük bir çıkıntı yapan bir tetikleyici, benzer bir görevi başarabilir. Kutu dökme küresi gibi, boşluğun her iki tarafındaki zemini tespit etmek için biraz genişliğe sahiptir. Burada, bu toprak sensörünün toprağa ne zaman temas ettiğini tespit etmek için OnTriggerEnter kullanırsınız.


2
Her zamanki gibi mükemmel cevap, ancak bu yöntem performansta "daha ağır" değil mi? Bu şekilde Unity'nin küre / kutu dökümü ve zemin ile kesişmeleri hesaplaması gerekiyor, bu yüzden .. bunu yapmak için daha performanslı bir yol değil mi?

9
Kesinlikle konuşma. Bir küreselleşme matematiksel olarak bir raycast'a oldukça benzer - bunu sadece tek bir seyahat noktası olarak düşünebiliriz, ancak "kalınlık" ofseti ile. Benim profilimde, ortalama olarak tek bir ışın yerine tam bir küreyi kontrol etmek sadece yaklaşık% 30-50'ye mal olur. Bu, iki ışın yerine bir kürenin ateşlenmesinin ~% 25'e varan performansta net bir tasarruf olabileceği anlamına gelir. Bir çerçeveden yalnızca birkaç kez yaptığınız kısa kontroller için her iki şekilde de büyük bir fark yaratması olası değildir, ancak bunu her zaman birkaç seçenek oluşturarak doğrulayabilirsiniz.
DMGregory

Küre kontrolü kesinlikle bir avatar üzerinde bir kapsül çarpıştırıcısı ile gitmenin yoludur.
Stephan

Bunun için bir hata ayıklama işlevi var mı? örneğin Debug.DrawLine? Görselleştirmek zor, senaryoyu yazamıyorum.
Siyah

1
@Siyah her zaman Debug.DrawLine'ı yapı taşı olarak kullanarak kendi görselleştirme rutinimizi yazabiliriz. :)
DMGregory

14

Dürüst olmak gerekirse "çoklu ışınlar" yaklaşımının oldukça iyi bir fikir olduğunu düşünüyorum. Onları açılı olarak vurmazdım, bunun yerine ışınları dengelerim, şöyle bir şey:

resim açıklamasını buraya girin

Oyuncu mavi çöp adam; Yeşil oklar ek ışınları yansıtır ve turuncu noktalar (RaycastHits) iki ışının kutulara çarptığı noktalardır.

İdeal olarak, iki yeşil ışın, oyuncunun topraklanıp topraklanmadığını kontrol etmek için en hassas olanı elde etmek için oynatıcının ayaklarının altına yerleştirilmelidir;)


7
Kenarlarda veya ince nesnelerde (borular gibi) dururken çalışmaz. Temelde aynı kusurlu yaklaşımın kaba kuvvet versiyonu. Yine de kullanacaksanız, piyonun kaçırılan ışının kaynağına doğru kaydırılarak kenarlardan kaydığından emin olun (her biri için ve sadece en az birkaç tane varsa).
Shadows In Rain

2
Eğer "şanslı" yöne bakacak olursanız, her iki ışının çatlağa atlamasını önlemek için bu yaklaşımla en az 3 adete ihtiyacınız olacaktır.
Stephan

3
Üzerinde çalıştığım bir PS2 oyununda, sadece oynatıcının altında zeminin nerede olduğunu belirlemek için her bir kareye aşağı doğru 25 küre atışı yaptım (oyuncunun altında 5x5 ızgara deseninde). Belki de bu biraz saçma, ama bir PS2'de yapmayı göze alabilirsek, modern makinelerde birkaç çarpışma testi kullanmayı göze alabilirsin. :)
Trevor Powell

@TrevorPowell evet, performansta "daha ağır" dediğimde "" "" daha ağır "" "" demek istedim çünkü oyun üzerinde büyük bir etki yaratmayacağını biliyordum, ama yine de en etkili şeyin ne olduğunu bilmek istedim Bunun yolu :)

2
(Dürüst olmak gerekirse, o kadar çok çarpışma testini hiç kullanamadım; PS2 oyun motorunun çılgın hızlı yarışları / küreleri vardı ve keşke bunu nasıl başardığını bilseydim). Ama çok ve çok sayıda küresel olması harikaydı; oyuncunun hangi yükseklikte durması gerektiği konusunda biraz daha akıllı olmak için uçurumları ve diğer zemin özelliklerini tespit edebileceğim anlamına geliyordu.
Trevor Powell

1

Ben değiştirerek çözdük Physics.Raycastiçin Physics.SphereCastkomut ThirdPersonCharacter.cs. Ama yine de test edilmesi gerekiyor.

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
);

Ayrıca, m_GroundCheckDistancedeğeri değiştiren bu çizgiyi yorumlamak zorunda kaldım , aksi takdirde bazı modellerde bazı garip kayarlar vardı:

    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;
    }

Ve ben değiştirdi m_GroundCheckDistance = 0.1f;için 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;
        }
    }

Bütün Senaryo:

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

Neden Unity'nin OnCollisionStay işlevini kullanmıyorsunuz ?

Artıları:

  • Raycast oluşturmak zorunda değilsiniz.

  • Raycast'tan daha doğrudur: Raycast, kontrol etmek için bir çekim yöntemidir, eğer raycast çekiminiz yeterli kapsama alanı değilse, bu soruyu sormanızın nedeni budur. OnCollisionStayyöntemi kelimenin tam anlamıyla bir şeyin dokunup dokunmadığını kontrol eder - oyuncunun yere (veya oyuncunun inebileceği herhangi bir şeye) dokunup dokunmadığını kontrol etmek için mükemmel bir uyum sağlar.

Kod ve demo için şu yanıtı kontrol edin: http://answers.unity.com/answers/1547919/view.html

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.