지상 점검 문제를 해결하는 방법?


12

Unity의 3 인칭 컨트롤러의 지상 점검에 문제가있는 것으로 나타났습니다.

지상 점검은 플레이어가 지상에 서 있는지 여부를 감지해야합니다. 플레이어 아래에 광선을 보내면됩니다.

그러나 플레이어가 두 상자의 중간에 서 있고이 상자 사이에 공간이 있으면 광선이 틈에 닿아 플레이어가지면과 접촉하지 않는다고 생각합니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

움직일 수 없습니다. 광선이 틈새에 있음을 분명히 알 수 있으므로 플레이어 애니메이터 공수 블렌드 트리가 활성화됩니다.

이 문제를 해결하는 가장 좋은 방법은 무엇입니까?

같은 원점에서 다른 각도로 여러 광선을 촬영할 생각이었습니다. 그리고 OnGround이러한 광선의 X의 %가 "땅"을 명중 경우에만, 진실해야합니다. 아니면 더 좋은 방법이 있습니까?

답변:


18

다른 답변에서 설명한 것처럼 대부분의 경우 다중 광선이 제대로 작동합니다.

구형 또는 박스 캐스트와 같이 더 넓은 검사를 사용할 수도 있습니다. 이것들은 레이 캐스트와 같은 개념을 사용하지만, 볼륨이 약간 큰 기하학적 프리미티브를 사용하므로 캐릭터가 겪을 수있는 것보다 더 좁은 균열에 빠질 수 없습니다. 또한 Shadows In Rain에서 언급 한 사례를 포착합니다. 여기서 캐릭터가 좁은 파이프 위에 서서 양쪽에 레이 캐스트로 놓칠 수 있습니다.

캐릭터 콜 라이더의 바닥 아래에서 조금만 돌출되는 트리거 콜 라이더도 비슷한 작업을 수행 할 수 있습니다. 캐스트 구면과 같이 간격의 양쪽에서 접지를 감지하기위한 폭이 있습니다. 여기에서는 OnTriggerEnter를 사용하여이 접지 센서가 접지와 접촉 한시기를 감지합니다.


2
항상 그렇듯이 훌륭한 답변이지만이 방법이 성능에 "더 무겁지"않습니까? 이 방법으로 Unity가 구 / 상자 캐스트 및지면과의 교차점을 계산해야한다고 가정합니다. 그렇기 때문에 레이 캐스트가 더 성능이 좋은 방법이 아닙니까?

9
엄밀히 말하지는 않습니다. 구면 캐스트는 수학적으로 레이 캐스트와 매우 유사합니다. 하나의 이동 지점으로 생각할 수 있지만 "두께"오프셋이 있습니다. 내 프로파일 링에서는 평균적으로 단일 광선 대신 전체 구를 검사하는 데 약 30-50 %가 소요됩니다. 이는 두 개의 광선 대신 하나의 구체를 발사하는 것이 최대 ~ 25 %의 성능으로 순 절약 될 수 있음을 의미합니다. 한 프레임에 몇 번만 수행하는 짧은 검사를 위해 큰 차이를 만들 가능성은 없지만 몇 가지 옵션을 프로파일 링하여이를 항상 확인할 수 있습니다.
DMGregory

구형 검사는 아바타에서 캡슐 콜 라이더를 사용하는 방법입니다.
Stephan

이것에 대한 디버그 기능이 있습니까? 예를 들어 Debug.DrawLine? 시각화하기 어렵고 스크립트를 작성할 수 없습니다.
Black

1
@Black은 항상 Debug.DrawLine을 빌딩 블록으로 사용하여 자체 시각화 루틴작성할 수있었습니다 . :)
DMGregory

14

솔직히 말해서 "다중 광선"접근 방식은 아주 좋은 생각입니다. 그래도 각도로 쏘지 않고 대신에 광선을 오프셋합니다.

여기에 이미지 설명을 입력하십시오

플레이어는 파란색 stickman입니다. 녹색 화살표는 추가 광선을 나타내며 주황색 점 (RaycastHits)은 두 광선이 상자를 치는 지점입니다.

플레이어의 접지 여부를 가장 정확하게 확인하려면 두 개의 녹색 광선을 플레이어의 발 바로 아래에 배치해야합니다.)


7
가장자리 나 파이프와 같은 얇은 물체에 서 있으면 작동하지 않습니다. 기본적으로 동일한 결함 접근 방식의 무차별 버전입니다. 어쨌든 그것을 사용하려는 경우, 누락 된 광선의 원점을 향하여 미끄러 져서 폰에서 가장자리에서 미끄러지는지 확인하십시오 (각각, 최소한 그 수가있는 경우에만).
Shadows In Rain

2
이 방법을 사용하려면 "운이 좋은"방향을 향한 경우 두 광선이 균열 속으로 빠지는 것을 방지하기 위해 3 이상이 필요합니다.
Stephan

3
내가 작업했던 PS2 게임에서, 나는 플레이어의 아래지면이 어디인지를 결정하기 위해 각 프레임 아래로 25 개의 구체 캐스트 (플레이어 아래의 5x5 그리드 패턴)를 수행했습니다. 어쩌면 그것은 어리석은 일이지만 PS2에서 할 수 있다면 현대 컴퓨터에서 몇 가지 추가 충돌 테스트를 사용할 수 있습니다. :)
Trevor Powell

@TrevorPowell 그래, 내가 성능에 "무거운"이라고 말했을 때 나는 "" ""무거운 "" ""을 의미했다. 왜냐하면 그것이 게임에 큰 영향을 미치지 않을 것이라는 것을 알았 기 때문에 여전히 가장 효율적인 것이 무엇인지 알고 싶었다. 이 :)을하는 방법

2
(정직하게 말해서, 그 이후로 많은 충돌 테스트를 사용할 수 없었습니다 .PS2 게임 엔진에는 미친듯한 레이 캐스트 / 스피어 캐스트가 있었으며 어떻게 관리했는지 알고 싶습니다.) 그러나 많은 구체 방송을 갖는 것이 좋았습니다. 그것은 플레이어가 어느 높이에 서 있는지에 대해 조금 더 똑똑해지기 위해 절벽과 다른 지형 특징을 감지 할 수 있다는 것을 의미했습니다.
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

Unity의 OnCollisionStay 기능을 사용하지 않는 이유는 무엇 입니까?

장점 :

  • 레이 캐스트를 만들 필요가 없습니다.

  • 레이 캐스트보다 정확합니다. 레이 캐스트는 촬영 확인 방법입니다. 레이 캐스트 촬영이 적용 범위가 충분하지 않으면이 질문을하는 이유가 버그로 이어집니다. OnCollisionStay메소드는 말 그대로 무언가가 닿아 있는지 확인합니다. 플레이어가지면에 닿아 있는지 (또는 플레이어가 착륙 할 수있는 것) 확인하는 목적에 완벽하게 맞습니다.

코드 및 데모를 보려면 다음 답변을 확인하십시오. http://answers.unity.com/answers/1547919/view.html

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.