Як вирішити проблему наземної перевірки?


12

Я помітив проблему в наземній перевірці контролера третьої особи Unity.

Наземна перевірка повинна виявити, чи гравець стоїть на землі. Це робиться, надсилаючи промінь під програвач.

Однак якщо гравець стоїть зверху і посередині двох ящиків і між цими скриньками є пробіл, то промінь стріляє в проміжок і гравець думає, що він не контактує з землею, що виглядає так:

введіть тут опис зображення

введіть тут опис зображення

Я не в змозі рухатись. Ви чітко бачите, що промінь знаходиться в проміжку, і таким чином дерево аніматорів аніматорів суміші повітря.

Який найкращий спосіб вирішити цю проблему?

Я думав знімати кілька променів, того самого походження, але з різними кутами. І OnGroundмає бути правдою лише в тому випадку, якщо X% цих променів потрапляють на "землю". Або є кращий спосіб?

Відповіді:


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 кульових закидів вниз кожного кадру (за схемою сітки 5х5 під програвач), просто щоб визначити, де знаходиться земля під гравцем. Можливо, це було трохи абсурдно, але якщо ми могли б дозволити собі це зробити на PS2, ви можете дозволити собі використати кілька додаткових тестів на зіткнення на сучасних машинах. :)
Тревор Пауелл

@TrevorPowell Так, коли я сказав "важче" на продуктивність, я мав на увазі "" "важче" "" "тому, що я знав, що це не зробить великого впливу на гру, але я все-таки хотів знати, що найефективніше шлях до цього :)

2
(Чесно кажучи, я ніколи не міг використати стільки тестів на зіткнення з того часу, що ігровий движок PS2 мав божевільні швидкі промені / сферакасти, і я б хотів, щоб я знав, як це вдалося). Але мати багато і багато сферакастів було чудово; це означало, що я можу виявити скелі та інші наземні особливості, щоб бути трохи розумнішими щодо того, на якій висоті повинен стояти гравець.
Тревор Пауелл

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 - це метод перевірки зйомки, якщо зйомка з радіомовлення не має достатнього покриття, то це призводить до помилок, через що ви задали це питання. OnCollisionStayметод буквально перевіряє, чи щось торкається - він ідеально підходить для мети перевірки, чи гравець торкається землі (або чого-небудь, на що гравець може приземлитися).

Щодо коду та демонстрації, перевірте цю відповідь: http://answers.unity.com/answers/1547919/view.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.