0
\$\begingroup\$

I'm working on a little pong clone and trying to implement collisions using triggers.

I have a ball which OnTriggerEnter sends out a raycast from the ball's position towards its velocity. I then take the .normal of the raycast hit and use that to figure out bounce angle.

most of the time this is working, but in a few cases (I can't quite figure out the common element between them) It doesn't work and gives a completely wrong number.

Video example, https://webmshare.com/xAe7X

Full code,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallScript : MonoBehaviour {
    public Vector2 velocity;
    public float angularVelocity;
    private Vector2 collisionNormal;

    void FixedUpdate(){
        transform.Translate (velocity.x, velocity.y, 0);
        Debug.DrawRay ((Vector2)transform.position - velocity * 2, velocity * 5,Color.red);
    }

    void OnTriggerEnter2D(Collider2D other) {
        FindCollisionNormal ();
        ReflectVelocity ();
    }

    public void FindCollisionNormal(){
        RaycastHit2D[] hits = Physics2D.RaycastAll ( (Vector2)transform.position - velocity * 2, velocity * 5);
        foreach (RaycastHit2D hit in hits) {
            if (hit.collider.GetType () == typeof(BoxCollider2D)) {
                collisionNormal = hit.normal;
                collisionNormal = new Vector2 (collisionNormal.y, collisionNormal.x);
            }
        }
    }

    public void ReflectVelocity(){
        print (collisionNormal);
        velocity = -velocity + 2 * (Vector2.Dot (velocity, collisionNormal) * collisionNormal);
    }
}
\$\endgroup\$
1
\$\begingroup\$

The problem here is the use of RaycastAll instead of Raycast.

As its name implies, RaycastAll gives you all hits along a given ray - not just the first one.

Taking a screengrab from the video, just before the ball does its first "weird" bounce:

Sample showing extended raycast getting two hits

Here RaycastAll says don't stop after the first hit, keep checking for more - so it also finds a second hit where the ray intersects the floor, even though that's much further away.

"But wait, isn't the raycast only covering the red line?" Nope. Check the docs on RaycastAll:

public static RaycastHit2D[] RaycastAll(
                             Vector3 origin, 
                             Vector3 direction,
                             float distance = Mathf.Infinity, // Aha!
                             int layermask = DefaultRaycastLayers, 
                             ...

When we provide just the two vector arguments to this raycast method, they're taken as origin and direction (ignoring length), and the third parameter, distance, is taken to be infinite.

This wouldn't be so bad - just needlessly inefficient - if we only grabbed the first hit from the returned array. Since RaycastAll returns the hits in order by distance, that would still get the correct first hit.

But by processing the hits in a loop like this:

foreach (RaycastHit2D hit in hits) {
   if (hit.collider.GetType () == typeof(BoxCollider2D)) {
       collisionNormal = hit.normal;
       collisionNormal = new Vector2 (collisionNormal.y, collisionNormal.x);
   }
}

the collisionNormal variable is left in whatever state it was set to by the last entry in the array - the exact opposite of what we want!

You can fix this by using a plain Raycast to get just the closest hit. Or you can use a CircleCast which will behave a bit better in tight corners (since a ray is infinitely thin, it can get deeper into a crevice than the ball itself can, potentially finding the wrong bounce in some cases).

As jgallant says in the earlier answer, you should also avoid mixing physics movement/collision checks with direct position changes via transforms. This can lead to teleportation glitches and unnecessary juddering, although it's not the root cause of this particular error. Rigidbody2D.velocity and Rigidbody2D.MovePosition let you keep the motion in the domain of the physics system, avoiding conflicts.

\$\endgroup\$
0
\$\begingroup\$

When relying on Rigidbodies for collision response, you cannot use direct position translations like you are doing in your FixedUpdate()

 transform.Translate (velocity.x, velocity.y, 0);

This will cause your physics engine to fail at some point as you are literally forcing a teleport on each frame. What you need to do instead, is apply the movement to your Rigidbody instead.

You can try something like:

transform.GetComponent<RidigBody2D>().position += velocity;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.