Unity 2D游戏中奇怪的碰撞错误

19

Github代码库(脚本文件夹,包含所有.cs文件)

我在Unity中遇到了这个奇怪的碰撞错误,这是它的GIF演示:

GIF

重现方法:在此GIF中,例如,我同时按下左箭头上箭头,直到速度归一化,然后我会被困在一个块中。

在我用XNA开发这个游戏时,我曾经有过自己的碰撞算法出现过这种问题,但我希望在Unity中不会出现。

这是玩家脚本PlayerMovement

using UnityEngine;
using UnityEngine.UI;

namespace Assets.Scripts
{
    public enum Directions
    {
        Back,
        Left,
        Front,
        Right,
        Idle = -1
    }

    public class PlayerMovement : MonoBehaviour
    {
        #region Public Members

        /// <summary>
        /// Maximum speed of the player (Accelerated to over a period of time)
        /// </summary>
        public float speed;

        /// <summary>
        /// Debug UI.Text element
        /// </summary>
        public Text debugText;

        #endregion

        #region Constants

        /// <summary>
        /// Constant for decaying the velocity on updates
        /// </summary>
        private const float VELOCITY_DECAY_FACTOR = 0.85f;

        /// <summary>
        /// Constant to convert normal speed sizes to fit the scale
        /// Of UnityEngine.Vector2
        /// </summary>
        private const float HUMAN_TO_VECTOR_SCALE_FACTOR = 850f;

        /// <summary>
        /// Constant to set the base speed of the player animation
        /// </summary>
        private const float BASE_ANIM_SPEED = 0.7f;

        /// <summary>
        /// Constant to slightly reduce the animation speed after 
        /// It is multiplied by the velocity of the player
        /// </summary>
        private const float POST_VELOCITY_MULTIPLICATION_ANIM_SPEED_FACTOR = 0.5f;

        /// <summary>
        /// Constant to set the animation speed
        /// </summary>
        private const float ANIM_SPEED_MODIFIER = BASE_ANIM_SPEED * POST_VELOCITY_MULTIPLICATION_ANIM_SPEED_FACTOR;

        /// <summary>
        /// Epsilon before velocity zerofication
        /// </summary>
        private const float VELOCITY_EPSILON = 0.1f;

        #endregion

        #region Private Members

        private Rigidbody2D rb2D;
        private Vector2 velocity;
        private Animator animator;
        private Directions dir = Directions.Idle;

        #endregion

        #region Game Loop Methods

        private void Awake()
        {
            animator = GetComponent<Animator>();
            rb2D = GetComponent<Rigidbody2D>();
        }

        private void FixedUpdate()
        {
            float vertical = Input.GetAxisRaw("Vertical");
            float horizontal = Input.GetAxisRaw("Horizontal");
            UpdateVelocity(horizontal, vertical);
            UpdateAnimation(horizontal, vertical);
            UpdateMovment();
        }

        #endregion

        #region Animation Methods

        private void UpdateAnimation(float horizontal, float vertical)
        {
            UpdateAnimation(new Vector2(horizontal, vertical));
        }

        private void UpdateAnimation(Vector2 input)
        {
            Directions direction;

            if (input.y > 0)
                direction = Directions.Back;
            else if (input.y < 0)
                direction = Directions.Front;
            else if (input.x > 0)
                direction = Directions.Right;
            else if (input.x < 0)
                direction = Directions.Left;
            else
                direction = Directions.Idle;

            SetDirection(direction);
        }

        private void SetDirection(Directions value)
        {
            animator.SetInteger("Direction", (int)value);
            dir = value;
        }

        #endregion

        #region Movement Methods

        private void UpdateMovment()
        {
            rb2D.MovePosition(rb2D.position + velocity * Time.fixedDeltaTime);
            KinematicsDebugPrints();
            ApplySpeedDecay();
        }

        private string GetDebugPrintDetails()
        {
            return string.Format("HOR : {0}\nVER : {1}\nDIR : {2}:{3}\nX : {4}\nY : {5}",
                velocity.x,
                velocity.y,
                animator.GetInteger("Direction").ToString().PadLeft(2),
                (Directions)animator.GetInteger("Direction"),
                rb2D.position.x,
                rb2D.position.y);
        }

        private void KinematicsDebugPrints()
        {
            var details = GetDebugPrintDetails();
            debugText.text = details;
            Debug.Log(details);
        }

        private void UpdateVelocity(float horizontal, float vertical)
        {
            if (vertical != 0)
                velocity.y += Mathf.Sign(vertical) * speed / HUMAN_TO_VECTOR_SCALE_FACTOR;
            if (horizontal != 0)
                velocity.x += Mathf.Sign(horizontal) * speed / HUMAN_TO_VECTOR_SCALE_FACTOR;
            animator.speed = ANIM_SPEED_MODIFIER * velocity.MaxOfXandY() ;
        }

        private void ApplySpeedDecay()
        {
            if (velocity == Vector2.zero) return;

            velocity *= VELOCITY_DECAY_FACTOR;
            velocity = velocity.ZerofiyTinyValues(0.1f);
        }

        #endregion
    }
}

当前的播放器对象如下:

player

这是墙体对象(除图像外所有预设都相同):

WALL

这是我另一个错误的gif:

bug two

这是碰撞框和圆圈的样子:

new collision boxes

这是检查器中的细节:

new inspector


在与Hamza Hasan交谈后,他帮助我将所有外墙框碰撞器转换为四个连续的碰撞器,每个面一个(上,下,左,右)。

其代码位于BoardManager脚本的CreateWallsColliders方法中。

这是在场景编辑器中当前的样子:

new scene editor


我指的是 UpdateVelocity 函数。 - AustinWBryan
将玩家的碰撞体更改为椭圆形是否可行? - Tamas Hegedus
如果墙是由瓷砖制成,我怀疑box2d的碰撞引擎有时会与墙壁内侧发生碰撞。 - Tamas Hegedus
@hege_hegedus 是的,问题就是你所说的。它与墙壁的内部上方发生了碰撞。可以通过避免使用锐利边缘的碰撞器来解决这个问题。但是在Unity中没有2D碰撞器像椭圆碰撞器那样,只有圆形和矩形碰撞器。 - Cenkisabi
你说得对,hege。每当我与盒子的底部对齐时,它似乎总是卡住我。 - Giora Guttsait
显示剩余10条评论
2个回答

7
您所遇到的现象是由于角色的矩形碰撞器与下一个墙壁瓷砖的底部边缘碰撞所致。这种“bug”在物理引擎中非常常见。它是由于某些计算错误导致的,并应该预料到。这就是为什么大多数游戏都为角色设置边界椭圆形,因为椭圆形没有拐角或边缘。

消除这种突然停止的一种方法是确保所有连续的墙壁瓷砖都表示为单个碰撞器(矩形或多边形)。这需要一个单独的逻辑来从障碍物创建碰撞器,在加载关卡后必须更新碰撞器以反映关卡的任何变化(例如开门等)。

解决问题的一个更简单的方法是改变角色的碰撞器。如果您的角色的矩形形状并不重要,我建议您使用以下形状的碰撞器:

capsule shaped collider

如果矩形形状很重要,您可以使用圆形扩展角落:

enter image description here


你试过Hamza Hasan建议的吗?如果有gif就更好了。 - Tamas Hegedus
我已经克隆了你的代码库,但是碰撞器仍然是一个矩形。 - Tamas Hegedus
我还没有更新所有内容,只更新了脚本。我马上会更新剩下的。 - Giora Guttsait
也许在git中为它创建一个分支 ;) - Tamas Hegedus
我不介意把所有东西都扔到主分支里。我并没有做太多实际的更改...现在我会把所有东西都放在主分支中 :) 我正在推送所有更改。 - Giora Guttsait
显示剩余5条评论

3
首先,将您的输入代码从 FixedUpdate 移动到 Update ,否则会导致应用程序产生卡顿行为。其次,您可以通过创建具有 摩擦力 = 0弹性 = 0PhysicsMaterial2D并将其附加到玩家和墙体碰撞器的 Material中来实现。希望这能对您有所帮助。
编辑:以下是另一种解决方案,而不是每个块使用1个盒子碰撞器,只使用每侧1个碰撞器,总共4个碰撞器。
这是代码,您可以将其添加到您的BoardManager 类中,并在SetUpScene方法结束时调用它,您可以进一步修改它。
void CreateWallsColliders ()
        {
            GameObject colliders = new GameObject ("Colliders");
            colliders.transform.position = Vector3.zero;

            GameObject leftCollider = new GameObject ("LeftCollider");
            leftCollider.transform.position = Vector3.zero;
            BoxCollider2D bcLeftCollider = leftCollider.AddComponent<BoxCollider2D> ();
            leftCollider.transform.parent = colliders.transform;

            GameObject rightCollider = new GameObject ("RightCollider");
            rightCollider.transform.position = Vector3.zero;
            BoxCollider2D bcRightCollider = rightCollider.AddComponent<BoxCollider2D> ();
            rightCollider.transform.parent = colliders.transform;

            GameObject topCollider = new GameObject ("TopCollider");
            topCollider.transform.position = Vector3.zero;
            BoxCollider2D bcTopCollider = topCollider.AddComponent<BoxCollider2D> ();
            topCollider.transform.parent = colliders.transform;

            GameObject bottomCollider = new GameObject ("BottomCollider");
            bottomCollider.transform.position = Vector3.zero;
            BoxCollider2D bcBottomCollider = bottomCollider.AddComponent<BoxCollider2D> ();
            bottomCollider.transform.parent = colliders.transform;

            // Assuming 15 x 15 tiles. Make it dynamic if you need.
            // Assuming -1 and 15 are the limits on both sides

            int rows = 15;
            int cols = 15;

            int lowerLimit = -1;
            int upperLimit = 15;

            leftCollider.transform.position = new Vector3 (lowerLimit, rows / 2);
            leftCollider.transform.localScale = new Vector3 (1, cols, 1);

            rightCollider.transform.position = new Vector3 (upperLimit, rows / 2);
            rightCollider.transform.localScale = new Vector3 (1, cols, 1);

            topCollider.transform.position = new Vector3 (cols / 2, upperLimit);
            topCollider.transform.localScale = new Vector3 (rows, 1, 1);

            bottomCollider.transform.position = new Vector3 (cols / 2, lowerLimit);
            bottomCollider.transform.localScale = new Vector3 (rows, 1, 1);
        }

我确实提到了仅在“Update”中获取输入 :) - Hamza Hasan
哦,所以只有UpdateInput方法...好的好的,我也会检查一下。谢谢你指出来 :) - Giora Guttsait
而且,hege_hegedus 提出的碰撞器建议是正确的,按照他的第一个建议设置碰撞器,然后应用我的答案... 这肯定会解决你的问题 :) - Hamza Hasan
添加了新的错误描述,我按照你的建议将输入移动到更新中,并更改了碰撞框,以符合hege的建议。 - Giora Guttsait
将玩家的刚体的Interpolate设置为Extrapolate,将Collision Detection设置为Continuous。:)) - Hamza Hasan
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接