Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Ball bouncing the wrong direction

Discussion in '2D' started by czzplnm, Aug 12, 2019 at 4:40 AM.

  1. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    I would imagine my problem is because the engine is having collision with one of the corners instead of the edge of the square, but it seems to be happening a lot.

    The blue line is the bad trajectory, where the yellow line is what I would expect the ball to do.

    How do I solve this problem in Unity?
     

    Attached Files:

  2. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    410
    How unreasonable is it to adjust your tile's width slightly? There appears to be a significant gap.
     
  3. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    The green line is the actual colliders so there is no gap from what I see.
     
  4. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    czzplnm likes this.
  5. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    I looked into that briefly last night. Would each brick still be able to behave independently and be removed easily from the composite or would I have to re calculate the composite each time a block is removed?
     
  6. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    Composite collider component can be configured to update automatically whenever it or any colliders that makes it up are changed. That's the "synchronous" generation type.

    The original gameobjects and colliders are still there. But if you are colliding with the composite collider, and you want to get a reference to the specific child object that was hit, you may need to do another physics check looking for non-composite colliders at the contact point, like a small OverlapCircle check.

    Or do what Unity does in this brickbreaker tech demo, which is use the hit surface normal to offset the check slightly inside the collider in order to get correct tile coordinates:
    https://github.com/Unity-Technologies/2d-techdemos/blob/master/Assets/Tilemap/Brick/Scripts/Ball.cs
     
    czzplnm likes this.
  7. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    So would all of my Block / Triangle objects be a child of a parent 'Composite Collider object' that would synchronously adapt to its children automatically? Or would each Block/Triangle have a composite Collider and infuse with its neighbors?
     
  8. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    Have a read through the documentation I linked for it.

    You just need one composite for many colliders. Each collider on the object or as a child object that has the "Used by Composite" checkbox set will be part of the final shape.
     
    czzplnm likes this.
  9. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    I'm not using a Tilemap. I am generate my game objects on the fly based on the devices width/height.

    I am trying to figure out which individual GameObject was collided with but the x/y of the collision point is not exactly precise.

    Code (CSharp):
    1.  private void OnCollisionEnter2D(Collision2D collision)
    2.     {
    3.         RectTransform[] allObjects = GetComponentsInChildren<RectTransform>(true);
    4.         foreach (RectTransform rt in allObjects)
    5.         {
    6.             GameObject obj = rt.gameObject;
    7.             if (obj.tag != "Triangle" && obj.tag != "Block")
    8.             {
    9.                 continue;
    10.             }
    11.             Vector3[] objCorners = new Vector3[4];
    12.             rt.GetWorldCorners(objCorners);
    13.  
    14.             foreach (ContactPoint2D hit in collision.contacts)
    15.             {
    16.                 double x = Math.Round(hit.point.x, 3);
    17.                 double y = Math.Round(hit.point.y, 3);
    18.  
    19.                 double bottomX = Math.Round(objCorners[0].x, 3);
    20.                 double bottomY = Math.Round(objCorners[0].y, 3);
    21.                 double topX = Math.Round(objCorners[2].x, 3);
    22.                 double topY = Math.Round(objCorners[2].y, 3);
    23.  
    24.                 if (x >= bottomX
    25.                     && y >= bottomY
    26.                     && x <= topX
    27.                     && y <= topY){
    28.                     obj.SendMessage("gameCollision", collision);
    29.                     //Debug.Log("Collided with: " + obj.name);                
    30.                 }
    31.                
    32.             }
    33.         }
    34.     }
    This code will detect certain objects at the top, but the ones that are closer need it to be rounded to 1 or 2 decimal places. Is what I am trying to do possible? The Composite collider solved the collision problem but now I cant detect which individual object was hit X_X.
     
  10. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    That solution is not limited to Tilemaps. You can do a physics check using a position inside the Collider by moving it along the surface normal that you get from the initial collision. In the Brickbreaker demo this is done to change a tile, but you can do it to get the underlying GameObject.

    I haven't tested this, but here is the idea:
    Code (CSharp):
    1. private void OnCollisionEnter2D(Collision2D collision)
    2. {
    3.     // if this object just hit a composite collider
    4.     bool hitCompositeCollider = collision.otherCollider.GetComponent<CompositeCollider2D>() != null;
    5.  
    6.     if (hitCompositeCollider)
    7.     {
    8.         // a small amount of negative offset
    9.         float offsetAlongNormal = -0.01f;
    10.         Vector2 newHitPosition = Vector2.zero;
    11.  
    12.         // check each contact
    13.         foreach (ContactPoint2D hit in collision.contacts)
    14.         {
    15.             // move the hit position inside the surface
    16.             newHitPosition += offsetAlongNormal * hit.normal;
    17.  
    18.             // get all the 2D colliders under that new point
    19.             Collider2D[] colliders = Physics2D.OverlapPointAll(newHitPosition);
    20.  
    21.             // filter in a loop
    22.             foreach (Collider2D collider in colliders)
    23.             {
    24.                 // if the object has the right tag
    25.                 if (collider.tag == "Triangle" || collider.tag == "Block")
    26.                 {
    27.                     // do something to the object
    28.                     Destroy(collider.gameObject);
    29.                 }
    30.             }
    31.         }
    32.     }
    33. }
     
    czzplnm likes this.
  11. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28

    Thanks, I'll give this a shot when I get home.
     
  12. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    Code (CSharp):
    1.         bool hitCompositeCollider = collision.otherCollider.GetComponent<CompositeCollider2D>() != null;
    2.  
    3.         if (hitCompositeCollider)
    4.         {
    5.             // a small amount of negative offset
    6.             float offsetAlongNormal = 0.01f;
    7.             Vector2 hitPosition = Vector2.zero;
    8.  
    9.             // check each contact
    10.             foreach (ContactPoint2D hit in collision.contacts)
    11.             {
    12.                 // move the hit position inside the surface
    13.                 //newHitPosition += hit.point - offsetAlongNormal * hit.normal;
    14.                 hitPosition.x = hit.point.x - offsetAlongNormal * hit.normal.x;
    15.                 hitPosition.y = hit.point.y - offsetAlongNormal * hit.normal.y;
    16.  
    17.                 // get all the 2D colliders under that new point
    18.                 Collider2D[] colliders = Physics2D.OverlapPointAll(hitPosition);
    19.  
    20.                 // filter in a loop
    21.                 foreach (Collider2D collider in colliders)
    22.                 {
    23.                     Debug.Log(collider);
    24.                     // if the object has the right tag
    25.                     if (collider.tag == "Triangle" || collider.tag == "Block")
    26.                     {
    27.                         // do something to the object
    28.                         Destroy(collider.gameObject);
    29.                     }
    30.                 }
    31.             }
    32.         }
    I combined the other code that you shared and tried yours directly but colliders is always empty.


    So far this works but it will very rarely not properly detect a block.

    Code (CSharp):
    1. RectTransform[] allObjects = GetComponentsInChildren<RectTransform>(true);
    2.         foreach (RectTransform rt in allObjects)
    3.         {
    4.             GameObject obj = rt.gameObject;
    5.             if (obj.tag != "Triangle"
    6.                 && obj.tag != "Block"
    7.                 ||
    8.                 (
    9.                 obj.name == "BrickAnimation"
    10.                 || !obj.activeSelf)
    11.                 )
    12.             {
    13.                 continue;
    14.             }
    15.             Vector3[] objCorners = new Vector3[4];
    16.             rt.GetWorldCorners(objCorners);
    17.             ContactPoint2D[] points = new ContactPoint2D[collision.contactCount];
    18.             collision.GetContacts(points);
    19.             foreach (ContactPoint2D hit in points)
    20.             {
    21.                 Vector3 hitPosition = Vector3.zero;
    22.                 //double x = hit.point.x;// Math.Round(hit.point.x, 3);
    23.                 //double y = hit.point.y;// Math.Round(hit.point.y, 3);
    24.                 if(hit.point.x > objCorners[0].x)
    25.                 {
    26.                     hitPosition.x = hit.point.x - 0.01f;// * hit.normal.x;
    27.                 } else
    28.                 {
    29.                     hitPosition.x = hit.point.x + 0.01f;// * hit.normal.x;
    30.                 }
    31.                 if (hit.point.y < objCorners[2].y)
    32.                 {
    33.                     hitPosition.y = hit.point.y + 0.01f;// * hit.normal.y;
    34.                 } else
    35.                 {
    36.                     hitPosition.y = hit.point.y - 0.01f;// * hit.normal.y;
    37.                 }
    38.                 //hitPosition.x = hit.point.x - 0.01f * hit.normal.x;
    39.                 Collider2D[] colliders = Physics2D.OverlapPointAll(hitPosition);
    40.                 double x = hitPosition.x;
    41.                 double y = hitPosition.y;
    42.                
    43.                 double bottomX = objCorners[0].x;//Math.Round(objCorners[0].x, 1);
    44.                 double bottomY = objCorners[0].y;//Math.Round(objCorners[0].y, 1);
    45.                 double topX = objCorners[2].x;//Math.Round(objCorners[2].x, 1);
    46.                 double topY = objCorners[2].y;//Math.Round(objCorners[2].y, 1);
    47.  
    48.                 if (x >= bottomX
    49.                     && y >= bottomY
    50.                     && x <= topX
    51.                     && y <= topY){
    52.                     //Debug.Log(obj.name);
    53.                     obj.SendMessage("gameCollision", collision);
    54.                     //Debug.Log("Collided with: " + obj.name);                
    55.                 }
    56.                
    57.             }
    58.         }
    For some reason I cant get Physics2D.OverlapPointAll(closestPoint); to work with any Vector2 point.
     
    Last edited: Aug 16, 2019 at 5:09 AM
  13. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    That solution for finding a point within the child object is a working one (it works in the brickbreaker demo), so if colliders array is always empty, i would debug that before complicating the solution without really understanding what is going wrong. Why is it empty? You know that there is at least the composite collider there, so it's suspicious that it comes up empty.

    Try adding some debug lines to see where the collision point is, what the normal vector is, and where the offset is trying to check for more colliders:
    Code (CSharp):
    1. foreach (ContactPoint contact in collision.contacts)
    2. {
    3.     // draw the surface normal
    4.     Debug.DrawLine(contact.point, contact.point + contact.normal, Color.green, 5, false);
    5.     // draw the offset location for second check
    6.     Debug.DrawLine(contact.point, contact.point + -offsetAlongNormal * contact.normal, Color.cyan, 5, false);
    7. }
    Make sure that those lines look right, and the cyan line ends inside the collider you're checking for.
     
    czzplnm likes this.
  14. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    The red ball is where it was hit and the green lines seems to be too far above the block.
     

    Attached Files:

  15. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    The red line is the one that was set to cyan
     

    Attached Files:

  16. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    Ive adjusted it so the red line is now in one of the Rect Colliders and I still cant get colliders from Physics2D.OverlapPointAll

    Code (CSharp):
    1.     bool hitCompositeCollider = collision.otherCollider.GetComponent<CompositeCollider2D>() != null;
    2.  
    3.         if (hitCompositeCollider)
    4.         {
    5.             // a small amount of negative offset
    6.             float offsetAlongNormal = 0.05f;
    7.             Vector2 hitPosition = Vector2.zero;
    8.  
    9.             // check each contact
    10.             foreach (ContactPoint2D hit in collision.contacts)
    11.             {
    12.  
    13.                 Collider2D[] colliders = Physics2D.OverlapPointAll(hit.point + offsetAlongNormal * hit.normal);
    14.                 Debug.Log("Drawing debug lines");
    15.                 // draw the surface normal
    16.                 Debug.DrawLine(hit.point, hit.point + hit.normal, Color.green, 60, false);
    17.                 // draw the offset location for second check
    18.                 Debug.DrawLine(hit.point, hit.point + offsetAlongNormal * hit.normal, Color.red, 60, false);
    19. }
    20. }
     

    Attached Files:

  17. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    Thanks for the screenshots, that raised some questions so I did some testing, and it appears that colliders that have the "Used by Composite" enabled aren't being checked in the physics step (which makes sense performance-wise, considering they are being used to build a single larger collider).

    An easy workaround is to duplicate the collider, uncheck "used by composite", and make it a trigger. That way you can collide with the composite surface, but still use raycasting to detect the underlying shapes and objects. Using continuous collision on the ball was also key to getting accurate hit results.

    Here's the project I used to find a working solution:
     

    Attached Files:

    Last edited: Aug 18, 2019 at 6:34 AM
    czzplnm likes this.
  18. czzplnm

    czzplnm

    Joined:
    Jul 16, 2019
    Posts:
    28
    Awesome man! Works like a charm! I would've been stuck with buggy code if not for you figuring out this weird issue. You should add a donate link to your signature for being this persistent in helping people!
     
    jeffreyschoch likes this.
  19. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    No problem, happy to help. Now I know a lot more about CompositeCollider2D so you helped me as well.
     
    czzplnm likes this.