在碰撞后正确地附加到游戏对象?

7

如何在发生碰撞后让游戏对象正确地附加(或“粘贴”)到另一个游戏对象上呢?问题在于,即使它正在改变比例,我也希望游戏对象在碰撞后能够附加。

“碰撞附加”代码:

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;
}

这段代码可以让GameObject在碰撞后完美附着。但是,当该GameObject更改比例时(在其附着时),它在视觉上不再看起来与其碰撞的物体附着在一起。基本上,这段代码使GameObject仅与碰撞时的原始比例粘在一起。如何使GameObject始终附着于其所碰撞的物体?并在整个过程中保持任何比例?我想避免使用父对象:"虽然有点不安全,将碰撞器作为父对象可能会导致奇怪的结果,例如随机传送或物体开始疯狂移动和旋转等。" - Samed Tarık ÇETİN:评论
缩放脚本:
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);
    }
}

你尝试过挂载一个父级对象吗,而不是这种方式?虽然我从未尝试过类似的东西,但我在互联网上搜索到了一些与您情况类似的资源http://answers.unity3d.com/questions/55068/change-parent-of-gameobject-c.html。 - Burak Karasoy
是的,我已经尝试过了,但总是得到意外的结果。我想避免使用父子关系。"虽然这样有点不安全,将碰撞器作为父级可能会导致奇怪的结果,比如随机传送或对象开始疯狂移动和旋转等等。" - Samed Tarık ÇETİN:评论 - user5509962
你尝试过在检测到子物体的碰撞器后只缩放父物体吗?比如,如果(碰撞条件){transform.parent.localScale ++}? - Hamza Hasan
4个回答

1

您的基本想法是正确的,您的代码可以稍微修改一下以支持此功能。

这里是诀窍:不要将您的对象粘附到与其碰撞的对象上,而是在碰撞点创建一个虚拟游戏对象,让我们称之为“胶水”,并将您的对象粘附到胶水上。然后,将胶水对象作为父对象与我们碰撞的对象相关联。

由于胶水只是一个具有转换和一些脚本组件的虚拟对象,因此不存在父子关系的问题。

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

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

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 P.S. 我看到您混淆了transform和rigidbody语义,由于它是在Kinematic刚体上完成的,所以这并不是什么大问题,但只是一个建议:请考虑是否真的需要在已经“粘”在其他物体上的对象上拥有刚体,如果不需要-也许只需删除或禁用刚体而不是使其变为Kinematic。

你好!感谢您的回答。在我的个人项目中实现您的代码时似乎存在问题。当游戏对象碰撞时,“胶水”确实被创建,但并不在其所碰撞物体的“边缘”。它是在中心创建的,因此不能被看到,因为它穿透了内部。 - user5509962
很奇怪。基本上,胶水是在你指定的位置创建的。在上面的例子中,我只是将其位置设置为第一个碰撞点,即col.contacts [0] .point。你不会忘记在创建后将胶水位置设置为碰撞点吧(就像在示例中的CreateGlue()中所做的那样)?如果不是这种情况,那么我能想到的唯一一件事就是碰撞点不在表面上,这非常不可能,基本上物理引擎应该处理它... - Yuri Nudelman
没事了,我找到问题的源头了,稍后会告诉你。更重要的是,你的代码使游戏对象在碰撞时附着,但当它不断改变比例时却无法保持附着状态... - user5509962
如果您提供有关您尝试做什么的更多信息,我将很乐意帮助。就我所检查的情况而言,它没有任何问题,请在此处查看工作演示:https://dl.dropboxusercontent.com/u/16950335/glue/WebBuild.html,因此我目前不知道为什么这对您无效。也许如果您发布您尝试的代码,我将能够提供帮助。 - Yuri Nudelman
请问您能否上传演示的Unity项目? - user5509962
1
当然,这里是链接:https://www.dropbox.com/s/rumkxhwkz4nvig8/GlueObjects1.zip?dl=0 实际上它几乎与我之前上传的相同,只是我给立方体添加了一个动画。 - Yuri Nudelman

1

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

Scaling About Pivot Point

在上面的图片中,左上角的箱子具有中心旋转点,因此当您沿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


请您能否创建另一个简单的项目,以便我更好地理解您的代码是如何工作的?当前的项目并没有清晰地展示您的代码... - user5509962
1
抱歉,我不确定发生了什么事情,但看起来我的场景更改没有保存。我已重新上传该项目,现在应该可以工作了,在其中2个立方体碰撞时,一个贴在另一个上并开始上下缩放,而另一个立方体沿着x位置移动。我将尽快添加更详细的说明和一些图表,谢谢! - Benzino
1
啊,没错,子对象的定位不正确。我已经在我的答案中进行了更正,现在它应该可以适用于任何大小的立方体了。我还更新了示例项目。 - Benzino
我该如何调整缩放的速度? - user5509962
还有一个更大的问题。你的代码干扰了我的旋转脚本。当游戏对象发生碰撞时,它就会消失。而且在发生碰撞之前旋转时,它的旋转不正常。 - user5509962
显示剩余5条评论

0
确保你正在缩放 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的建议呢?只要你知道自己在做什么,把碰撞器、刚体甚至其他任何物件都设置为父物件都是非常安全的。只需要将粘性变换对象设为目标对象的子物件,一切就搞定了!如果出了问题,只需删除刚体组件或禁用碰撞器组件即可。


谢谢您的回答!您说:“确保您正在缩放 stuckTo 变换。”您具体是什么意思? - user5509962
考虑这样一种情况:stuckTo 有一个父变换。如果你缩放父变换,视觉效果可能与缩放 stuckTo 变换相同。但是 stuckTo 的实际缩放比例不会改变。因此,请确保你缩放的是 stuckTo,而不是父变换。 - Nika Kasradze

0

改变全局

protected Collider stuckTo = null;    

使用Collider而不是Transform对象。这可能会得到更好的解决方案。如果它有效或出现任何错误,请告诉我,因为我还没有尝试过。我想知道它是否有效。

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;

            }
}

1
“error CS0103: The name col' does not exist in the current context” 在这一行代码上出现了错误:offset = col.collider.bounds.center - transform.position;` - user5509962
2
当然会出错。我犯了一个简单的错误,偏移量应该是offset = stuckTo.bounds.center - transform.position; - Burak Karasoy
1
我必须提醒你,在 GameObject 碰撞之后,它会一直改变比例。 - user5509962
1
你能否在问题中添加你的游戏图片?至少再走一步 :) - Burak Karasoy
1
没什么特别的。立方体1与立方体2发生碰撞。当发生碰撞时,立方体1会不断改变比例。使用两个立方体游戏对象测试您的代码。 - user5509962
显示剩余4条评论

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