Properly attach to a GameObject after collision?

2019-06-17 06:52发布

How can I properly make a GameObject attach (or "stick") to another GameObject after collision? The problem: I want the GameObject to attach after collision even if it is changing scale.

"Attach on collision" code:

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

This code makes a GameObject attach perfectly after collision. But when that GameObject changes scale (while it's attached), it visually no longer looks attached to whatever it collided with. Basically, this code makes the GameObject stick with only the original scale at the moment of the collision. How can I make the GameObject always stick to whatever it collided with? And with whatever scale it has during the process? I would like to avoid parenting: "It's a bit unsafe though, parenting colliders can cause weird results, like random teleporting or object starting to move and rotate insanely, etc." - Samed Tarık ÇETİN : comment.

Scaling script:

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

4条回答
看我几分像从前
2楼-- · 2019-06-17 07:11

change global

protected Collider stuckTo = null;    

///// use Collider instead of transform object. You might get better solution.Inform me if it works or gives any error since i haven't tried if it works i would like to know.

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;

            }
}
查看更多
乱世女痞
3楼-- · 2019-06-17 07:13

What you want to do is scale about the collision point. You can achieve this by setting the pivot point to the collision point, that way when you scale the object it will be scaled based on the pivot. The best way to simulate this is using sprites in Unity.

Scaling About Pivot Point

In the above image, the top-left crate has a pivot point in the center, so when you scale it along x, it scales about that point, increasing it's width both sides of the pivot point. But when the pivot point is set to the side, like in the bottom left image, when you scale it, it can only extend it out to the left (unless you scale negatively of course).

Now the problem is you can't easily change the pivot point of mesh, so there are a number of different work-arounds for this. One such approach is to attach the mesh to an empty new gameObject and adjust the mesh's local transform in relation to the parent, simulating moving the meshes pivot point.

What the below code does is determine the collision point. Since there can be multiple collision points, I get the average collision point of them all. I then move the mesh's parent to the collision point and adjust the meshes local position so that the side of the cube is positioned at that point, this acts as setting the mesh's pivot point to the collision point.

Now when you scale, it will scale about the collision point, just like in the above image.

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

Here is an example project with the above code: https://www.dropbox.com/s/i6pdlw8mjs2sxcf/CubesAttached.zip?dl=0

查看更多
倾城 Initia
4楼-- · 2019-06-17 07:13

Make sure that you are scaling the stuckTo transform (the one that has the collider attached to) and not any of it's parents or this will not work.

if the stuckTo's scale is uniform:

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

but if the stuckTo's scale is non-uniform:

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

But still though - why are you following ÇETİN's advice man? It's totally safe to parent colliders and rigidbodies and literally anything as long as you know what you are doing. Just parent your sticky transform under the target and bam! if something goes wrong just remove your rigidbody component or disable your collider component.

查看更多
乱世女痞
5楼-- · 2019-06-17 07:22

Your basic idea is right, your code can be modified slightly to support this.

Here is the trick: instead instead of sticking your object to the object it collided with, you create a dummy game object, lets call it "glue", at the collision point, and stick your object to the glue. The glue object is then parented to the object we collided with.

Since glue is just a dummy object with only components transform and some script, there is no problem with parenting.

Also, pay attention that it does not really matter at which contact point we create the glue, in case we have multiple contact points, and it is also easy to extend this to support rotations, see below.

So on collision, the only thing we do now is creating a glue. Here is the code:

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

And here is how Glue.cs script looks, it will handle LateUpdate and modify transform.

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

Also, pay attention that simply parenting the objects as it was suggested here will get you in some additional trouble, because scaling parent also scales children, so you will have to re-scale the child back to its original size. The problem is that these scaling operations are relative to different anchor points, so you will also have to make additional adjustments in objects position. Can be done though.

I also created a small sample project, see here (Unity v5.2.f3): https://www.dropbox.com/s/whr85cmdp1tv7tv/GlueObjects.zip?dl=0

P.S. I see that you mix transform and rigidbody semantics, since it is done on Kinematic rigidbodies it is not a big deal, but just a suggestion: think whether you really need to have rigidbodies on objects that are already "stuck" to others, if not - maybe just remove or disable the rigidbody instead of making it Kinematic.

查看更多
登录 后发表回答