【问题标题】:Unity3D setting velocity won't take effect immediately in OnCollisionEnterUnity3D 设置速度不会在 OnCollisionEnter 中立即生效
【发布时间】:2017-12-08 11:12:48
【问题描述】:

我有一个简单的脚本可以在与某物碰撞时将速度设置为零,但它似乎不会立即起作用:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveController : MonoBehaviour {

    [SerializeField]
    private float shootVelocity=30f;

    void Start () {

    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(0,0,100,100), "shoot"))
        {
            GetComponent<Rigidbody>().velocity = shootVelocity * Vector3.forward;
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        GetComponent<Rigidbody>().velocity = Vector3.zero;
        GetComponent<Rigidbody>().isKinematic = true;
    }

}

我也发了youtube video来演示这个问题。

在发生碰撞后让球立即停止非常重要。

怎么做?

【问题讨论】:

    标签: c# unity3d rigid-bodies


    【解决方案1】:

    你遇到这个问题是因为物理计算发生在特定的时间步之后,你可以在这里阅读更多关于它的信息https://docs.unity3d.com/Manual/class-TimeManager.html

    想象一下这种情况,对象没有与这一帧发生碰撞,下一帧第一个对象移动了一段距离,现在它正在与第二个对象发生碰撞,但它已经在该对象内部的某个位置,因为它移动得非常快,然后检测到碰撞但已经太晚了。有时,如果对象非常小和/或移动速度非常快,则不会检测到碰撞,因为当它第一次检查对象一在对象二之前时,当它第二次检查对象一时已经通过对象二,所以没有检测到碰撞。

    一种方法是使这个时间步长更小,以便 Unity 更频繁地检查碰撞,但这会增加开销,特别是如果您需要进行大量物理计算并且球体停止时的位置永远不会完美。我的建议是您自己编写计算以与 Unity 的模拟一起使用。

    我将举例说明如何计算完美停止位置(两个对象仅在一个点接触的位置),如果对象在穿过另一个对象时始终沿相同方向移动并且您正在使用两个球体对撞机你知道的世界范围内的半径。我们将它们命名为radius1和radius2,参数“t”表示物体需要向前/向后移动多少才能获得完美的碰撞位置。

    当检测到粘连时,您可以进行以下计算。

    Vector3 firstVector = transform.position-collidedObject.transform.position; Vector3 secondVector = direction; float r = radius1+radius2;

    (firstVector - secondVector*t).sqrMagnitude = r^2;

    展开你的方程式

    或在文本模式下 =\sqrt{2x_2^2t^2+z_2^2t^2-2x_1x_2t-2z_1x_2t-2z_1z_2t+x_1^2+2z_1^2}

    现在求解 t,您将得到两个值,因为对于给定方向,您有两个完美的碰撞位置(一个在第二个对象之前,一个在第二个对象之后)。

    你得到这个等式 Link to Symbolab equations

    它可能看起来很复杂,但您可以看到这两个解决方案中 t(在那个减号中)只有一个区别,其余部分相同,您可以将其拆分为 3 个变量以使其看起来简单,这个只专注于一个方程式,所以看起来很多。

    假设现在你有 "t1" 和 "t2" 的等式值,你现在可以这样做

    float finalT = Mathf.Abs(t1) < Mathf.Abs(t2) ? t1 : t2;
    transform.position += dir*t;
    

    只有在检测到碰撞时才需要计算所有这些,我还建议仅在以下情况下才这样做:

    if((transform.position-collidedObject.transform.position).sqrMagnitude > (radius1+radius2 + someThresholdValue)^2)
    

    因此,如果 Unity 引擎已经很好地定位了对象,则忽略计算。告诉我进展如何,希望对您有所帮助!

    注意:即使对象没有恒定的方向,您也可以通过采用最后知道的方向来近似它,并且为了计算,使其在该值上保持恒定。您几乎不会得到任何差异,它极大地解决了计算问题。

    【讨论】:

    • 谢谢,Neven,我会尝试理解这一点并尝试让您知道(这可能需要一些时间)我已经尝试过的一件事肯定是使固定时间戳更小确实可以获得更精确的位置
    【解决方案2】:

    物理学就是这样运作的。

    如果运动的物体有更高的速度,它就会有更高的动量,因此需要更多的时间来停止。

    您可以尝试像这样更新您的代码以提高效率。

    public class MoveController : MonoBehaviour 
    {
    
        [SerializeField]
        private float shootVelocity=30f;
    
        Rigidbody rb;
        void Start () 
        {
            rb = GetComponent<Rigidbody>();
        }
    
        private void OnGUI()
        {
            if(GUI.Button(new Rect(0,0,100,100), "shoot"))
            {
                rb.velocity = shootVelocity * Vector3.forward;
            }
        }
    
        private void OnCollisionEnter(Collision collision)
        {
            rb.isKinematic = true;  
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
        }
    }
    

    希望这会有所帮助:)

    【讨论】:

    • 感谢您的回答,但似乎添加 rb.angularVelocity=Vect3.zero 并没有多大帮助:-(
    • 这不是我改变的全部,预取刚体参考将在微观层面加快速度。您还可以尝试不同的刚体碰撞模式(离散/连续),增加/减少质量,使用位置约束。在刚体检查器中寻找所有这些选项
    • 嗯,您的意思是将Rigidbody 设为保留引用吗?只需将我的代码替换为您的代码,看起来都一样。
    • 始终将组件引用存储在变量中。 GetComponent 调用需要一些处理。并在碰撞时首先设置 IsKinematic
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多