【问题标题】:Properly attach to a GameObject after collision?碰撞后正确附加到游戏对象?
【发布时间】:2016-02-24 01:53:39
【问题描述】:

如何正确在碰撞后将游戏对象附加(或“粘贴”)到另一个游戏对象?问题:我希望游戏对象在碰撞后附加,即使它正在改变比例。

“碰撞时附加”代码:

protected Transform stuckTo = null;
protected Vector3 offset = Vector3.zero;

public void LateUpdate()
{
    if (stuckTo != null)
        transform.position = stuckTo.position - offset;
}  

void OnCollisionEnter(Collision col)
{
    rb = GetComponent<Rigidbody>();
    rb.isKinematic = true;

    if(stuckTo == null 
       || stuckTo != col.gameObject.transform)
        offset = col.gameObject.transform.position - transform.position;

    stuckTo = col.gameObject.transform;
}

此代码使游戏对象在碰撞后完美连接。但是当这个游戏对象改变比例时(当它被附加时),它在视觉上看起来不再附加到它所碰撞的任何东西上。基本上,此代码使游戏对象在碰撞时仅保持原始比例。我怎样才能让游戏对象始终坚持它所碰撞的东西?在这个过程中它有什么规模?我想避免养育:“虽然有点不安全,养育碰撞器可能会导致奇怪的结果,例如随机传送或物体开始疯狂移动和旋转等。” - Samed Tarık ÇETİN:comment.

缩放脚本:

public Transform object1; //this is the object that my future-scaling GameObject collided with.
public Transform object2; //another object, the same scale as object1, somewhere else 
//(or vice versa)

void Update () 
{
    float distance = Vector3.Distance (object1.position, object2.position);
    float original_width = 10;
        if (distance <= 10) 
    {
        float scale_x = distance / original_width;
        scale_x = Mathf.Min (scale_x, 3.0f);
        transform.localScale = new Vector3 (scale_x * 3.0f, 3.0f / scale_x, 3.0f);
    }
}

【问题讨论】:

  • 您是否尝试过附加父母而不是这种方式。我从来没有做过这样的事情,但是我在互联网上搜索并发现了与您的情况相似的东西answers.unity3d.com/questions/55068/…
  • 是的,我已经尝试过了,但总是得到意想不到的结果。我想避免为人父母。 “但这有点不安全,育儿对撞机可能会导致奇怪的结果,例如随机传送或物体开始疯狂移动和旋转等。” - Samed Tarık ÇETİN:comment
  • 在检测到孩子的对撞机后,您是否尝试仅缩放父级。比如 if (collision condition) {transform.parent.localScale ++} ?

标签: c# unity3d collision-detection collision unity5


【解决方案1】:

改变全局

protected Collider stuckTo = null;    

///// 使用 Collider 而不是变换对象。你可能会得到更好的解决方案。如果它有效或给出任何错误,请通知我,因为我没有尝试过它是否有效,我想知道。

void OnCollisionEnter(Collision col)
{
    rb = GetComponent<Rigidbody>();
    rb.isKinematic = true;

    if(stuckTo == null  || stuckTo != col.gameObject.transform)
       offset = col.collider.bounds.center - transform.position;

    stuckTo = col.collider;
}
    public void LateUpdate()
    {
        if (stuckTo != null)
          { 

         Vector3 distance=stuckTo.bounds.extents + GetComponent<Collider>().bounds.extents;
         transform.position = stuckTo.bounds.center + distance;

            }
}

【讨论】:

  • 我忘了更新偏移量。编辑了我的答案。如果它有效,我将尝试使代码更好。
  • error CS0103: The name `col' does not exist in the current context 此行出现错误:offset = col.collider.bounds.center - transform.position;
  • ofc 它给出了错误。我犯了一个简单的错误 offset =stuckTo.bounds.center - transform.position;
  • 我必须提醒你,在 GameObject 碰撞后,它一直在变化
  • hmm。如果作品需要优化,请编辑一些内容。因为它总是在同一个位置。
【解决方案2】:

确保您正在缩放 stuckTo 变换(附加了对撞机的变换)而不是它的任何父级,否则这将不起作用。

如果stuckTo的比例是统一的:

protected Transform stuckTo = null;
protected Vector3 originalPositionOffset = Vector3.zero;
protected Vector3 positionOffset = Vector3.zero;
protected Vector3 originalScaleOfTheTarget = Vector3.zero;

public void LateUpdate()
{
    if (stuckTo != null){
        positionOffset *= stuckTo.localScale.x;
        transform.position = stuckTo.position - positionOffset;
    }
}  

void OnCollisionEnter(Collision col)
{
    rb = GetComponent<Rigidbody>();
    rb.isKinematic = true;

    if(stuckTo == null 
       || stuckTo != col.gameObject.transform){
        originalScaleOfTheTarget = col.gameObject.transform.localScale;

        originalPositionOffset = col.gameObject.transform.position - transform.position;
        originalPositionOffset /= originalScaleOfTheTarget.x;
    }

    stuckTo = col.gameObject.transform;
}

但如果 stuckTo 的比例不均匀:

protected Transform stuckTo = null;
protected Vector3 originalPositionOffset = Vector3.zero;
protected Vector3 positionOffset = Vector3.zero;
protected Vector3 originalScaleOfTheTarget = Vector3.zero;

public void LateUpdate()
{
    if (stuckTo != null){
        positionOffset.x = originalPositionOffset.x * stuckTo.localScale.x;
        positionOffset.y = originalPositionOffset.y * stuckTo.localScale.y;
        positionOffset.z = originalPositionOffset.z * stuckTo.localScale.z;

        transform.position = stuckTo.position - positionOffset;
    }
}  

void OnCollisionEnter(Collision col)
{
    rb = GetComponent<Rigidbody>();
    rb.isKinematic = true;

    if(stuckTo == null 
       || stuckTo != col.gameObject.transform){
        originalScaleOfTheTarget = col.gameObject.transform.localScale;

        originalPositionOffset = col.gameObject.transform.position - transform.position;
        originalPositionOffset.x /= originalScaleOfTheTarget.x;
        originalPositionOffset.y /= originalScaleOfTheTarget.y;
        originalPositionOffset.z /= originalScaleOfTheTarget.z;
    }

    stuckTo = col.gameObject.transform;
}

但仍然 - 你为什么要听从ÇETİN 的建议?只要您知道自己在做什么,对父对撞机和刚体以及任何东西都是完全安全的。只需将您的粘性变换放在目标和 bam 下即可!如果出现问题,只需删除您的刚体组件或禁用您的对撞机组件。

【讨论】:

  • 感谢您的回答!你说“确保你正在缩放stuckTo变换”你到底是什么意思?
  • 考虑一个案例:stuckTo 有一个父变换。如果您缩放父级,则视觉效果可能与缩放stuckTo 变换相同。但是stuckTo的实际规模不会改变。所以请确保您缩放stuckTo,而不是父级。
【解决方案3】:

您的基本想法是正确的,您的代码可以稍作修改以支持这一点。

这里有诀窍:不是将您的对象粘到它碰撞的对象上,而是创建一个虚拟游戏对象,在碰撞点将其称为“胶水”,然后将您的对象粘在胶水上。然后,胶水对象作为我们碰撞对象的父对象。

由于glue只是一个只有组件变换和一些脚本的虚拟对象,所以父级没有问题。

另外,请注意,我们在哪个接触点创建胶水并不重要,以防我们有多个接触点,并且也很容易扩展它以支持旋转,见下文。

所以在碰撞时,我们现在唯一要做的就是创建胶水。代码如下:

void CreateGlue(Vector3 position, GameObject other) {
    // Here we create a glue object programatically, but you can make a prefab if you want.
    // Glue object is a simple transform with Glue.cs script attached.
    var glue = (new GameObject("glue")).AddComponent<Glue>();

    // We set glue position at the contact point
    glue.transform.position = position;

    // This also enables us to support object rotation. We initially set glue rotation to the same value
    // as our game object rotation. If you don't want rotation - simply remove this.
    glue.transform.rotation = transform.rotation;

    // We make the object we collided with a parent of glue object
    glue.transform.SetParent(other.transform);

    // And now we call glue initialization
    glue.AttachObject(gameObject);
}

void OnCollisionEnter(Collision col)
{
    // On collision we simply create a glue object at any contact point.
    CreateGlue(col.contacts[0].point, col.gameObject);
}

这是 Glue.cs 脚本的外观,它将处理 LateUpdate 和修改转换。

public class Glue : MonoBehaviour {

    protected Transform stuckTo = null;
    protected Vector3 offset = Vector3.zero;

    public void AttachObject(GameObject other)
    {
        // Basically - same code as yours with slight modifications

        // Make rigidbody Kinematic
        var rb = other.GetComponent<Rigidbody>();
        rb.isKinematic = true;

        // Calculate offset - pay attention the direction of the offset is now reverse
        // since we attach glue to object and not object to glue. It can be modified to work
        // the other way, it just seems more reasonable to set all "glueing" functionality
        // at Glue object
        offset = transform.position - other.transform.position;

        stuckTo = other.transform;
    }

    public void LateUpdate()
    {
        if (stuckTo != null) {
            // If you don't want to support rotation remove this line
            stuckTo.rotation = transform.rotation;

            stuckTo.position = transform.position - transform.rotation * offset;
        }
    }

    // Just visualizing the glue point, remove if not needed
    void OnDrawGizmos() {
        Gizmos.color = Color.cyan;
        Gizmos.DrawSphere(transform.position, 0.2f);
    }
}

另外,请注意,按照此处的建议简单地设置对象的父级会给您带来一些额外的麻烦,因为缩放父级也会缩放子级,因此您必须将子级重新缩放回其原始大小。问题是这些缩放操作与不同的锚点相关,因此您还必须对对象位置进行额外调整。还是可以的。

我还创建了一个小示例项目,请参见此处(Unity v5.2.f3): https://www.dropbox.com/s/whr85cmdp1tv7tv/GlueObjects.zip?dl=0

附:我看到你混合了变换和刚体语义,因为它是在运动学刚体上完成的,这没什么大不了的,而只是一个建议:想想你是否真的需要在已经“卡住”其他人的物体上拥有刚体,如果不需要的话- 可能只是移除或禁用刚体,而不是使其成为 Kinematic。

【讨论】:

  • 您好!谢谢您的回答。在我自己的个人项目中实现您的代码时似乎存在问题。当游戏对象发生碰撞时,确实会创建“胶水”,但不会在其碰撞对象的 “边缘” 上创建。它是在中心创建的,因此无法看到,因为它穿透了内部。
  • 这很奇怪。基本上,胶水是在您说要创建的地方创建的。在上面的示例中,我只是将其位置设置为第一个碰撞点 col.contacts[0].point。您没有忘记在创建碰撞点后将胶水位置设置为碰撞点(与示例中的CreateGlue() 相同)?如果不是这种情况,那么我唯一能想到的就是碰撞点不在表面上,这不太可能,基本上物理引擎应该处理它......
  • 没关系,我找到了问题的根源,稍后我会告诉你。更重要的是:您的代码使游戏对象在碰撞时附加,但在不断变化的比例时不会保持连接...
  • 如果您提供有关您尝试做的事情的更多信息,我将很乐意提供帮助。据我检查,它没有问题,请参阅此处的工作演示:dl.dropboxusercontent.com/u/16950335/glue/WebBuild.html 所以我目前不知道为什么这对你不起作用。也许如果您发布您尝试过的代码,我将能够提供帮助
  • 好的,这里:dropbox.com/s/rumkxhwkz4nvig8/GlueObjects1.zip?dl=0其实和我之前上传的差不多,只是在cube中加了一个动画
【解决方案4】:

您想要做的是围绕碰撞点进行缩放。您可以通过将枢轴点设置为碰撞点来实现这一点,这样当您缩放对象时,它将基于枢轴进行缩放。最好的模拟方法是在 Unity 中使用精灵。

在上图中,左上角的 crate 在中心有一个轴心点,因此当您沿 x 缩放它时,它会围绕该点缩放,从而增加轴心点两侧的宽度。但是当轴心点设置在一边时,比如左下角的图像,当你缩放它时,它只能向左延伸(当然除非你负向缩放)。

现在的问题是您无法轻松更改网格的轴心点,因此有许多不同的解决方法。一种这样的方法是将网格附加到一个空的新游戏对象并调整网格相对于父级的局部变换,模拟移动网格的枢轴点。

以下代码的作用是确定碰撞点。由于可能有多个碰撞点,我得到了它们的平均碰撞点。然后我将网格的父级移动到碰撞点并调整网格的局部位置,以便立方体的侧面位于该点,这相当于将网格的轴心点设置为碰撞点。

现在当你缩放时,它会围绕碰撞点缩放,就像上图一样。

public MeshRenderer _meshRenderer;
public float _moveXDirection;
public Rigidbody _rigidBody;
public Transform _meshTransform;
public bool _sticksToObjects;
public ScalingScript _scalingScript;

protected Transform _stuckTo = null;
protected Vector3 _offset = Vector3.zero;

void LateUpdate() 
{
    if (_stuckTo != null)
    {
        transform.position = _stuckTo.position - _offset;
    }
}

void OnCollisionEnter(Collision collision)
{
    if (!_sticksToObjects) {
        return;
    }

    _rigidBody.isKinematic = true;

    // Get the approximate collision point and normal, as there
    // may be multipled collision points
    Vector3 contactPoint = Vector3.zero;
    Vector3 contactNormal = Vector3.zero;
    for (int i = 0; i < collision.contacts.Length; i++) 
    {
        contactPoint += collision.contacts[i].point;
        contactNormal += collision.contacts[i].normal;
    }

    // Get the final, approximate, point and normal of collision
    contactPoint /= collision.contacts.Length;
    contactNormal /= collision.contacts.Length;

    // Move object to the collision point
    // This acts as setting the pivot point of the cube mesh to the collision point
    transform.position = contactPoint;

    // Adjust the local position of the cube so it is flush with the pivot point
    Vector3 meshLocalPosition = Vector3.zero;

    // Move the child so the side is at the collision point.
    // A x local position of 0 means the child is centered on the parent,
    // a value of 0.5 means it's to the right, and a value of -0.5 means it to the left
    meshLocalPosition.x = (0.5f * contactNormal.x);
    _meshTransform.localPosition = meshLocalPosition;

    if (_stuckTo == null || _stuckTo != collision.gameObject.transform) 
    {
        _offset = collision.gameObject.transform.position - transform.position;
    }

    _stuckTo = collision.gameObject.transform;

    // Enable the scaling script
    if (_scalingScript != null)
    {
        _scalingScript.enabled = true;
    }
}

这是一个带有上述代码的示例项目: https://www.dropbox.com/s/i6pdlw8mjs2sxcf/CubesAttached.zip?dl=0

【讨论】:

  • 能否请您创建另一个简单的项目,以便我更好地理解您的代码是如何工作的?当前的没有清楚地展示你的代码...
  • 抱歉,我不确定发生了什么,但看起来我对场景的更改没有保存。我已经重新上传了项目,它现在应该可以工作了,两个立方体发生碰撞,一个粘在另一个上并开始上下缩放,而另一个立方体沿着 x 位置移动。有机会我会尽快用一些图表添加更详细的解释。
  • 您的代码不太适合我的缩放脚本,在我编辑的问题中提到。你能用我自己的缩放脚本更新你的答案和 Unity 项目吗?
  • 我不确定我是否理解您对缩放所做的工作,但我已将其添加到我的项目中,并且它似乎可以正常工作,尽管它确实可以奇怪地缩放。我还更新了我的答案,以更好地解释发生了什么。
  • 啊,对了,子对象的定位不对。我现在已经在我的答案中更正了它,现在无论立方体大小如何,它都应该可以工作。我还更新了示例项目。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-26
相关资源
最近更新 更多