拾起的物体在移动时晃动。

3

我有一个GameObject放置在摄像机前面,所以每当玩家拿起一个物品,它将被放置在GameObject的位置。但是,每当我在拿起物品时移动,物品会抖动。如何防止这种情况发生?

private void FixedUpdate()
{
    if (currentlyPickedUpObject != null)
    {
        currentDist = Vector3.Distance(PickupParent.position, pickupRB.position);
        currentSpeed = Mathf.SmoothStep(minSpeed, maxSpeed, currentDist / maxDistance);
        currentSpeed *= Time.fixedDeltaTime;
        pickupRB.transform.position = PickupParent.position;
        Vector3 direction = PickupParent.position - pickupRB.position;
        pickupRB.velocity = direction.normalized * currentSpeed;
    }
}

if (PickingUp)
{
    if (currentlyPickedUpObject == null)
    {
        if (lookObject != null)
        {
            PickupObject();
            if (lookObject.CompareTag("TargetObj") && !targetObjectsList.Contains(lookObject.gameObject))
            {
                if (aSource)
                {
                    aSource.Play();
                }

                targetObjectsList.Add(lookObject.gameObject);
                if (targetObjectsList.Count == targetObjects.Length)
                {
                    winUI.SetActive(true);
                    Time.timeScale = 0f;
                    //SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
                    //Time.timeScale = 1f;
                }
            }
        }
    }
    else
    {
        // pickupRB.transform.position = PickupParent.position;
        BreakConnection();
        HoldingItemIcon.SetActive(false);
        InteractIcon.SetActive(false);
    }
}

PickingUp = false;

public void BreakConnection()
{
    pickupRB.constraints = RigidbodyConstraints.None;
    currentlyPickedUpObject = null;
    lookObject = null;
    physicsObject.pickedUp = false;
    currentDist = 0;
    pickupRB.useGravity = true;
}

public void PickupObject()
{
    physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
    currentlyPickedUpObject = lookObject;
    pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
    pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
    physicsObject.playerInteractions = this;

    pickupRB.isKinematic = true;
    //  pickupRB.transform.position = PickupParent.position;
    pickupRB.transform.parent = PickupParent.transform;

    //StartCoroutine(physicsObject.PickUp()); 
}

这里是可选对象的检查器: enter image description here

这是附加到可选对象的代码:

public class PhysicsObjects : MonoBehaviour
{
    public float waitOnPickup = 0.1f;
    public float breakForce = 35f;
    [HideInInspector] public bool pickedUp = false;
    [HideInInspector] public ThePlayerInteractions playerInteractions;


    private void OnCollisionEnter(Collision collision)
    {
        if (pickedUp)
        {
            if (collision.relativeVelocity.magnitude > breakForce)
            {
                playerInteractions.BreakConnection();
            }
        }
    }

    //this is used to prevent the connection from breaking when you just picked up the object as it sometimes fires a collision with the ground or whatever it is touching
    public IEnumerator PickUp()
    {
        yield return new WaitForSecondsRealtime(waitOnPickup);
        pickedUp = true;
    }
}

除了震动之外,被拿起来的物体因某种原因失去了它们的碰撞器,它们会穿过任何撞到的物体。当物体被持有时,避免这些问题的最佳方法是什么?


你可以尝试将拾取的对象作为被拾取父物体的子物体 pickupRB.transform.SetParent(PickupParent.transform),然后移除在固定更新中更新其位置的代码行。它可能会晃动,因为你只在固定更新期间更新了它的位置,而普通更新则运行得更加频繁。 - HumanWrites
所以我尝试在我的Update()中添加这行代码,并删除了FixedUpdate()。现在,物体不会抖动,但它的行为很奇怪。它没有飞到玩家面前,而是在地板上被拖动当我移动时。 - Okashi
摄像机在update中渲染,物理更新在fixed update中进行,两者基本上不会同时发生。我认为你需要标记刚体以插值来平滑帧之间的运动,但你也保持着刚体的抖动并设置速度。如果你想要控制刚体到那个程度的细节,就关闭物理引擎。你和物理系统正在对抗。 - Chuck
物体的刚体已经设置为插值。我正在使用角色控制器来控制我的玩家(我不知道这是否会改变任何东西)。当物体被搬运时,我尝试将重力设置为false,但在移动时仍然会有些抖动。 - Okashi
2个回答

2

:EDIT:

有三种可行的选择,我将涵盖这些内容,然后您可以选择最适合您的选项。最终,我仍然认为根本原因在于您正在尝试与物理对象 (Rigidbody) 交互,同时启用 Unity 与物理对象交互。解决这个问题的方法都围绕着禁用 Unity 与它交互 (修复约束条件)、删除您的交互 (添加 FixedJoint) 或尝试不同时交互 (将交互移动到 Update)。

选项1:Fixed Joint

Fixed Joints

不要执行更新位置和速度的步骤,只需添加一个 FixedJoint,然后在放下时删除该关节:

private FixedJoint fixedJoint;
public void BreakConnection()
{
    Destroy(fixedJoint);
}

public void PickupObject()
{
    fixedJoint = gameObject.AddComponent<FixedJoint>();
    fixedJoint.connectedBody = currentlyPickedUpObject.GetComponent<Rigidbody>();
}

方案二:将定位代码移至 Update 函数

Update instead of FixedUpdate

如果您将此操作放在 Update() 中,而不是 FixedUpdate() 中,则似乎也可以去除抖动效果。但是这样做存在可能无法获得所需的物理交互的风险,因此请务必在进行此操作之前进行测试:

private void Update()
{
    fixedJoint.connectedBody
    if (PickupParent != null)
    {
        currentDist = Vector3.Distance(PickupParent.position, pickupRB.position);
        currentSpeed = Mathf.SmoothStep(minSpeed, maxSpeed, currentDist / maxDistance);
        currentSpeed *= Time.fixedDeltaTime;
        pickupRB.transform.position = PickupParent.position;
        Vector3 direction = PickupParent.position - pickupRB.position;
        pickupRB.velocity = direction.normalized * currentSpeed;
    }
}

选项3:应用约束

添加刚体约束

这需要您继续进行更新(通过在上面的选项1中添加FixedJoint可以避免),但不会创建关节,因此它会保持pickupParent不变。添加FixedJoint将链接拾取和父物体在一起,这可能会导致父物体受到推回,如果/当拾取与某些物体碰撞时。

您可以使用全部/无约束选项,或者您可以缓存现有约束并在断开连接时重置它们。我在这里包含了该选项,因为它稍微有点复杂。

private RigidbodyConstraints priorConstraints;    // <--- NEW
public void BreakConnection()
{
    pickupRB.constraints = priorConstraints;    // <--- NEW
    currentlyPickedUpObject = null;
    lookObject = null;
    physicsObject.pickedUp = false;
    currentDist = 0;
    pickupRB.useGravity = true;
}

public void PickupObject()
{
    physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
    currentlyPickedUpObject = lookObject;
    pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
    priorConstraints = pickupRB.constraints;    // <--- NEW
    pickupRB.constraints = RigidbodyConstraints.FreezeAll;    // <--- NEW
    physicsObject.playerInteractions = this;

    pickupRB.isKinematic = true;
    //  pickupRB.transform.position = PickupParent.position;
    pickupRB.transform.parent = PickupParent.transform;

    //StartCoroutine(physicsObject.PickUp()); 
}

我认为你的问题在于试图通过设置位置和速度手动覆盖物理系统。尝试在拿起物品时禁用刚体组件,并将其作为PickupParent的子对象添加进来。你将保留任何存在的碰撞体,只要PickupParent本身有一个刚体组件,这是一种有效的方法,因为刚体组件会使用所有子游戏对象上的所有碰撞体的总和。

一个对象树上应该只有一个刚体组件,并且它应该在根级别。所以我假设你的拾取物品都是没有父级的根对象。

由于你正在将它们附加到自己身上,因此FixedUpdate中实际上没有什么可做的了:

private void FixedUpdate()
{
    if (currentlyPickedUpObject != null)
    {
        //currentDist = Vector3.Distance(PickupParent.position, pickupRB.position);
        //currentSpeed = Mathf.SmoothStep(minSpeed, maxSpeed, currentDist / maxDistance);
        //currentSpeed *= Time.fixedDeltaTime;
        //pickupRB.transform.position = PickupParent.position;
        //Vector3 direction = PickupParent.position - pickupRB.position;
        //pickupRB.velocity = direction.normalized * currentSpeed;
    }
}

您没有提供拾取代码,但我相信您可以通过将isKinematic设置为true有效地禁用刚体,而无需删除它,因此在拾取时更改其父级并设置该属性,然后在断开连接时移除父级并清除isKinematic。

void PickupObject()
{
    pickupRb.isKinematic = true;
    pickupRb.transform.parent = pickupParent.transform;
    // other stuff to do here
}

void BreakConnection()
{
    pickupRb.isKinematic = false;
    pickupRb.transform.parent = null;
    // other stuff to do here
}

如果您仍然有抖动问题,那么另一件要做的事情是检查pickupParent上的Interpolate设置,因为这是您正在捕捉到的对象(在此处和原始代码中)。


1
我将在问题中编辑并分享更多信息。 - Okashi
感谢您的努力,这是非常受欢迎的。在我开始尝试这些选项之前,我有一个问题。这些选项会使得拾取的物体失去其碰撞器并穿过墙壁和其他物体吗? - Okashi
@Okashi - 不,所有这些选项都保留了所有的碰撞器,并在拾取对象上保留了刚体,因此不应该有任何问题。正如我在上面某个时候指出的那样,如果您使用FixedJoint将拾取对象附加到PickupParent上,则这两个对象被视为焊接在一起,这可能会导致与您的PickupParent结构/附加方式相关的一些上游问题。另外两个选项(修复约束并移动到“Update()”)将拾取对象保留为其自己的项目,因此没有机制将反作用力传递给父级。 - Chuck
@Okashi - 你有机会试过这个吗?它有效吗?我一直在刷新,希望能听到点什么哈哈 - Chuck
让我们在聊天中继续讨论 - Okashi
显示剩余12条评论

0
问题在于你试图直接在代码中更改变换位置:
pickupRB.transform.position = PickupParent.position;

但这不是处理刚体运动的正确方式。这是震动的原因,而且,由于每次直接设置变换位置时需要重新计算所有刚体物理,它还会导致巨大的开销。与其这样做,不如在将刚体附加到玩家时使用rigidbody.MovePosition以及使刚体成为运动学:

pickupRB.MovePosition(PickupParent.position)

为了让它更容易,你也可以使用FixedJoint,就像Chuck建议的那样。

我在尝试寻找解决方案时实际上尝试了MovePosition,但它也没有起作用!至少,在FixedUpdate中没有。不过,在Update中移动变换确实有效。 - Chuck
这对我也没有用。 - Okashi

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