低帧率下的碰撞检测缓慢

8
我在我的碰撞检测中遇到了奇怪的问题。我使用Update方法来移动玩家(我不想使用FixedUpdate,因为那会导致奇怪的运动效果)。固定时间步长设置为默认的0.02(我尝试过修改时间设置,但也没有用)。我将两个物体的刚体碰撞检测设置为“连续动态”。此外,我将目标帧速率设置为300,但仍然没有改变任何东西...
当帧率较低或设备本身较慢时,碰撞检测并不总是有效。玩家很容易穿过本该与之碰撞的对象,但有时又会遇到碰撞。
请告诉我如何解决这个问题,因为我发布了一款游戏,许多用户都报告了这个(严重)错误。感谢您的支持。
以下是应该发生的事情:

enter image description here

这就是实际发生的事情:

enter image description here

正如您所看到的,立方体从墙上出来,到达了另一侧。

当用户释放鼠标按钮时,我移动玩家:

脚本1:

public Script2 Jumper;
public float TimeToJump;

public void Update()
{
        if (Input.GetMouseButtonUp(0)) 
    {
            StartCoroutine (Delay (1f/50f)); //Don't mind the time.
    }
}

IEnumerator Delay(float waitTime) 
{
    yield return new WaitForSeconds (waitTime);
    if (Jumper != null) 
    {
        Jumper.SetVelocityToJump (gameObject, TimeToJump);
    }
}

脚本2已附加到玩家(立方体):

public class Script2 : MonoBehaviour {

    GameObject target;
    private float timeToJump;
    public bool isJumping = false;

    public void SetVelocityToJump(GameObject goToJumpTo, float timeToJump)
    {
        StartCoroutine(jumpAndFollow(goToJumpTo, timeToJump));
        this.timeToJump = timeToJump;
        this.target = goToJumpTo;
    }

    private IEnumerator jumpAndFollow(GameObject goToJumpTo, float timeToJump)
    {
        var startPosition = transform.position;
        var targetTransform = goToJumpTo.transform;
        var lastTargetPosition = targetTransform.position;
        var initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);

        var progress = 0f;
        while (progress < timeToJump)
        {
            progress += Time.deltaTime;
            if (targetTransform.position != lastTargetPosition)
            {
                lastTargetPosition = targetTransform.position;
                initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
            }

            float percentage = progress * 100 / timeToJump;  
            GetComponent<Rigidbody>().isKinematic = percentage < 100.0f;  

            transform.position = startPosition + (progress * initialVelocity) + (0.5f * Mathf.Pow(progress, 2) * _gravity);
            yield return null;
        }

        OnFinishJump (goToJumpTo, timeToJump);
    }


    private void OnFinishJump(GameObject target, float timeToJump)
    {
        if (stillJumping)
        {
            this.isJumping = false;
        }
    }

    private Vector3 getInitialVelocity(Vector3 toTarget, float timeToJump)
    {
        return (toTarget - (0.5f * Mathf.Pow(timeToJump, 2) * _gravity)) / timeToJump;
    }
}

立方体的目标是更大的立方体(墙)的子代。

如果需要澄清,请在下面留言。如果您需要更多详细信息,我可能会提供我的游戏链接。

引用自这里(感谢@Logman):“即使使用连续的动态碰撞检测,问题仍然存在,因为快速移动的对象可以移动得太快,以至于它们从一个帧到下一个立即帧之间相距太远。就像它们传送了一样,没有任何碰撞检测会被触发,因为从每个帧的角度来看,没有任何碰撞存在,因此从所有处理的计算中也不存在。”

在我的情况下,立方体并没有快速移动,但您可以理解这个概念。


2
使用FixedUpdate()并修复实际的bug("非预期的奇怪移动")。如果问题仍然存在,可以在慢速设备上扩大碰撞体或设置最低硬件要求或尽可能优化代码。哦!而且调整固定时间戳也没有效果,因为(不出所料)这与FixedUpdate()处理有关。 - user2299169
1
你的“Delay”大纲是错误的。在Unity中使用Invoke来设置定时器即可。 - Fattie
1
你为什么不使用内置的碰撞事件呢?如果只是因为移动不正常,那么我同意其他评论者的建议,应该解决这个问题。之后,你可以简单地使用 FixedUpdate()、连续动态碰撞检测以及MonoBehavior对象模型中内置的标准碰撞事件,来实现你想要的效果。作为额外的好处,如果你正确设置了场景,可能会通过选择更简单的碰撞检测模型来节省一些资源。你最好是提出一个关于你“不正常运动”的不同问题。 - HBomb
你不能使用Unity物理引擎来解决这个问题。看看这个链接:http://answers.unity3d.com/questions/55179/cheapest-way-to-catch-collisions-on-very-fast-movi.html - Logman
是的,但这种情况非常罕见。我的真正观点是“设计实现是否必要?”换句话说,我同意对于非常快速移动的物体,物理学并不够快...但原始问题并没有说明有快速移动的物体。事实上,原始问题指出的是玩家(通常)不会移动得那么快。 - HBomb
这就是为什么我在评论中的第一个问题是“是否有特别原因你不使用内置碰撞事件?”快速移动的对象可能是一个很好的原因,但它并没有明确说明。还有其他可能的原因,实际上并不那么好。 - HBomb
2个回答

7
您的代码存在几个问题。
  1. 您要求Coroutine暂停1/50秒。yield必须至少持续一帧时间,如果Time.deltaTime > 0.02f,则已经出现了一个问题。
  2. 您正在使用Coroutines和yield return null来计算物理计算。实际上,您正在Update()中计算物理,而Update()每帧只调用一次(null等效于new WaitForEndOfFrame():如第1点所述,正在运行的Coroutine不能在帧之间进行暂停)。在低帧率下,对象在两帧之间执行的移动量可能超过目标触发器的碰撞范围。假设是线性、非加速运动:∆S = v∆t,其中v是速度,∆S是当前帧中需要移动的距离,∆t是Time.deltaTime。可以看到,∆S与∆t成比例关系。
  3. 您正在循环中使用GetComponent()调用。应避免这样做:将引用作为成员变量存储(在Start()中初始化)。
我的建议是创建子程序,并从FixedUpdate()中调用它们,并使用成员布尔变量来有条件地测试要“执行”的子程序以及要“跳过”的子程序。您还可以使用成员布尔变量或枚举作为触发器,在各种“状态”之间进行切换。
更好的解决方案是让Unity处理运动学,而您使用刚体变量(而不是transform.position),但这可能对于您的情况完全没有必要。在这种情况下,请坚持上述hack。
如果您真的想手动控制运动学,请使用像SFML这样的引擎。粒子系统教程是一个很好的起点。

4
这是您的浮动百分比,还有其他内容。
“如果启用isKinematic,则不会再影响刚体的力、碰撞或连接。”
这来自Unity文档中isKinematic页面。当进度达到100时,您将其设置为true。因此,在较低的帧速率下,由于Time.deltaTime步骤更高,进度突然 >= 100,isKinematic设置为true,玩家不再受碰撞影响,会出现突然跳跃。
我认为你需要重新思考很多代码并进行一些重要的优化。但其他张贴者已经列出了这些,所以我不需要。
编辑:误解了最初的问题,认为您试图检测碰撞,但代码并不总是检测到它们。没有意识到实际上是首先让碰撞发生。

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