Properly attach to a GameObject after collision?
Asked Answered
S

4

7

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);
    }
}
Sobranje answered 21/11, 2015 at 16:49 Comment(3)
have you tried attaching a parent instead of this way. I have never made something like this but i searched on Internet and found something similar to your situation answers.unity3d.com/questions/55068/….Road
Yes, I already tried it and I always get unexpected results. 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 : commentSobranje
Did you try only scaling parent after detecting child's collider. Like if (collision condition) {transform.parent.localScale ++} ?Twinflower
E
1

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.

Ettore answered 27/11, 2015 at 11:59 Comment(6)
Hello! Thank you for your answer. There appears to be a problem when implementing your code in my own personal project. When the GameObject collides, "glue" is indeed created but not on the "edge" of what it collided with. It is created in the center and thus it cannot be seen because it penetrated inside.Sobranje
That's strange. Basically glue is created where you say it to be created. In the example above, I simply set its position to the first collision point which is col.contacts[0].point. You didn't forget by any chance to set glue position to the collision point after creating it (same as it is done in CreateGlue() in the example)? If this is not the case, then the only thing I could think about is that the collision point is not on the surface, which is very unlikely, basically physics engine should handle it...Ettore
Nevermind, I found the source of the problem, I will tell you about it later. More important: your code makes the GameObject attach on collision but does not stay attached when it's continuously changing scale...Sobranje
I will be glad to help if you provide some more info about what you tried to do. As far as I checked, it has no problems, see working demo here: dl.dropboxusercontent.com/u/16950335/glue/WebBuild.html so I currently have no clue why this does not work for you. Maybe if you post code you tried I will be able to helpEttore
Could you please upload the demo's Unity project?Sobranje
Sure, here: dropbox.com/s/rumkxhwkz4nvig8/GlueObjects1.zip?dl=0 It is actually almost the same as the one I uploaded before, I just added an animation to cubeEttore
K
1

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

Kerstin answered 27/11, 2015 at 12:32 Comment(10)
Could you please create another simple project so I can understand better how your code works? The current one does not clearly demonstrate your code...Sobranje
Apologies, I'm not sure what happened but looks like my changes to the scene were not saved. I've re-uploaded the project and it should be working now, where 2 cubes collide, one sticks to the other and starts scaling up and down while the other cube moves along the x position. I'll try to add a more detailed explanation with some diagrams in a bit as soon as I get a chance.Kerstin
Your code does not fit very well with my scaling script, mentioned in my edited question. Can you update your answer and Unity project with my own scaling script?Sobranje
I'm not sure I understand what you are doing with your scaling but I've added it to my project and it seems to work, although it does scale oddly. I've also updated my answer to better explain what's going on.Kerstin
Thank you for the update. But in my case, the "CubeThatGetsSuck" has a scale of (3,3,3) and "CubeMesh" has a scale of (1.1.1), and when it collides, it moves away immediately from what it collided with. What's the problem?Sobranje
Ah, that's right, the positioning of the child object was incorrect. I've corrected it now in my answer and it should work regardless of the cubes size now. I've also updated the sample project.Kerstin
How can I adjust the speed of the scaling?Sobranje
Well the scaling is your code, so I'm not 100% sure what's happening. From looking at it, you adjust the scale based on the distance from the other cube, so the faster the cube is moving, the faster it will scale. You could modify your code so that it determines what the target scale value should be based on the distance, and then adjust the scale every update to meet the target scale. That way, you could set how much you change the scale every update without affecting the speed of the object.Kerstin
There's another larger issue. Your code is interfering with my rotation script. When the GameObject collides, it just disappears. And when it's rotating before it collides, it's not rotating normally.Sobranje
Without knowing how or when you are rotating, it's hard to tell what's the problem. Once the objects are stuck, you could trying rotating the child mesh about the pivot point (parent's position).Kerstin
R
0

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;

            }
}
Road answered 21/11, 2015 at 18:54 Comment(9)
ı forgot to update offset.edited my answer.If it works ı will try to make the code better.Road
error CS0103: The name `col' does not exist in the current context ERROR on this line: offset = col.collider.bounds.center - transform.position;Sobranje
ofc it gives error.Easy mistake i made offset = stuckTo.bounds.center - transform.position;Road
I have to remind you that after the GameObject collides, it keeps changing scale all the time.Sobranje
hmm.Edited something if works need to optimize.cause it will be always on the same position.Road
At least one step:) can you add a picture of your game to your question.Road
Nothing special. Cube 1 collides with Cube 2. When there's a collision, Cube 1 changes scale continuously. Test your code with two Cube GameObjects.Sobranje
Did you figure it out?Sobranje
Couldn't open Unity5 since working for exams. I will look it as soon as possible but if you figure it out before inform me i am curious about solution as well.Road
T
0

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.

Tallyho answered 27/11, 2015 at 11:56 Comment(2)
Thank you for your answer! You said "Make sure that you are scaling the stuckTo transform" What did you mean exactly?Sobranje
consider a case: stuckTo has a parent transform. if you scale the parent the visual may be the same as scaling the stuckTo transform. But actual scale of stuckTo will not change. so make sure you scale stuckTo, not the parent.Tallyho

© 2022 - 2024 — McMap. All rights reserved.