1

I am working on a game where a 2D projectile can ricochet off of walls. I currently have the object itself ricocheting just fine, however I would like an Angry Birds Style HUD (minus the gravity) that displays where the object is going to hit a wall, and show what angle it will ricochet at: (Pardon my drawing skills)

My end goal

I have this system working fine on the technical side, all except for the fact because the raycast is endless, so the actual result looks like this:

Current result

So to be clear, in the end, I want the 1st raycast to go out infinitely until it hits something, and then when it generates the 2nd second raycast where the first one hit somthing, have the second raycast only go out a pre-specified distance (Maybe 3f or something like that), or until it hits something, in which case it should stop.

My Script:

Transform firePoint;

void Update
{
     DrawPredictionDisplay();
}

private void DrawPredictionDisplay()
    {
        Vector2 origin = firePoint.transform.position; //unity has a built in type converter that converts vector3 to vector2 by dropping the z component
        direction = firePoint.transform.up;
        float radius = 0.4f;
        RaycastHit2D hit = Physics2D.CircleCast(origin, radius, direction);

        // Draw black line from firepoint to hit point 1
        Debug.DrawLine(origin, direction * 10000, UnityEngine.Color.black);

        if (hit)
        {          
            origin = hit.point + (hit.normal * radius);
            Vector2 secondDirection = Vector2.Reflect(direction, hit.normal);            

            // Create second raycast
            RaycastHit2D hit2 = Physics2D.CircleCast(origin, radius, secondDirection);

            if (hit2)
            {
                if(hit.collider.gameObject.tag != "Destroy")
                {
                    // Enable prediction points
                    for (int i = 0; i < numOfPoints; i++)
                    {
                        predictionPoints2[i].SetActive(true);
                    }

                    // Calculate reflect direction
                    Vector2 origin2 = hit2.point + (hit2.normal * radius);
                    // Draw blue line from hit point 1 to predicted reflect direction
                    Debug.DrawLine(origin, secondDirection * 10000, UnityEngine.Color.blue);
                }      
            }
            
        }
    }

    Vector2 predictionPointPosition(float time)
    {
        Vector2 position = (Vector2)firePoint.position + direction.normalized * 10f * time;
        return position;
    }

    Vector2 predictionPointPosition2(float time, Vector2 origin, Vector2 direction)
    {
        Vector2 position = origin + direction.normalized * 10f * time;
        return position;
    }

Notes:

While I would use regular raycasts, I found that normal Raycasts don't cut it because the raycasts are only 1 pixel wide and the object is (about) 512px by 512px, meaning the object would physically touch the wall before the raycast did, causing inaccuracy.

I've already created a system that generates dots along the raycast path in a similar vein to Angry Birds so that the player can see what the raycasts are doing in the game view, but since it's not relevant to the question I removed that part of the code from my script above. Which means all I need to do limit how far the raycasts go, not find a way for the player to see what's happening. (I'm addng this in the notes to avoid bringing up the conversation of whether or not the player can see what's happening.)

firepoint is the barrel/tip of the weapon that the projectile is fired from. (The weapon rotates according to/following to the mouse)

Jhon Piper
  • 513
  • 2
  • 8
  • 21
  • 1
    `Debug.DrawLine` takes 2 points as arguments not a point and a direction, did you mean to use `Debug.DrawRay`? You can wait to draw the path until after you know if you hit something or not and you have the collision points/information to draw it. – Pluto Jul 25 '20 at 03:39
  • @Pluto I've never had to use Debug.DrawRay before, could you show me what it would look like in this context? – Jhon Piper Jul 25 '20 at 17:24
  • `Debug.DrawLine(origin, origin + direction * 10000, UnityEngine.Color.black);` is equivalent to `Debug.DrawRay(origin, direction * 10000, UnityEngine.Color.black);` – Pluto Jul 25 '20 at 17:32
  • @Pluto Alright, but how do I get it to stop once it hits an object? – Jhon Piper Jul 25 '20 at 17:40

1 Answers1

1

You can draw the line/path after you know if it hit an object or not:

Vector2 origin = firePoint.transform.position; //unity has a built in type converter that converts vector3 to vector2 by dropping the z component
direction = firePoint.transform.up;
float radius = 0.4f;
RaycastHit2D hit = Physics2D.CircleCast(origin, radius, direction);

var firstPathStart = origin;
var firstPathEnd = origin + direction * 10000;    

if (hit)
{          
    origin = hit.point + (hit.normal * radius);
    firstPathEnd = origin;
    
    Vector2 secondDirection = Vector2.Reflect(direction, hit.normal); 
    var secondPathStart = firstPathEnd;
    var secondPathEnd = origin + secondDirection * 3f;           

    // Create second raycast
    RaycastHit2D hit2 = Physics2D.CircleCast(origin, radius, secondDirection);

    if (hit2)
    {
        if(hit.collider.gameObject.tag != "Destroy")
        {
            // Enable prediction points
            for (int i = 0; i < numOfPoints; i++)
            {
                predictionPoints2[i].SetActive(true);
            }

            // Calculate reflect direction
            Vector2 origin2 = hit2.point + (hit2.normal * radius);
            secondPathEnd = origin2;
        }      
    }
    // Draw blue line from hit point 1 to predicted reflect direction
    Debug.DrawLine(secondPathStart, secondPathEnd, UnityEngine.Color.blue);                  
} 

// Draw black line from firepoint to hit point 1
Debug.DrawLine(firstPathStart, firstPathEnd, UnityEngine.Color.black);  

You can make a class to store the path information (how many segments does the path have, where each segment starts and ends), populate when you check for collisions and draw it at the end.

Pluto
  • 3,911
  • 13
  • 21
  • Took me a couple of minutes to implement but I now have 2 raycasts firing that stop once they hit a wall (Like this: https://imgur.com/a/1E7EhVZ). The only other thing I'll ask before I mark your answer as correct is: Can we make the second raycast stop when it hits a wall **or** after a certain (predetermined) distance? (Like this: https://imgur.com/a/1CqjbYQ) – Jhon Piper Jul 25 '20 at 18:28
  • Hmm... when I do `if(hit2) { var secondPathEnd = origin + secondDirection * 3f; Debug.DrawLine(firstPathEnd, secondPathEnd, UnityEngine.Color.blue); }` I get this: https://imgur.com/a/l3H1T6s (Ignore the dots for the moment) It limits the distance, but then ignores if it hits a wall. – Jhon Piper Jul 25 '20 at 19:02
  • What your saying makes sense, however in practice, if I declare `secondPathEnd` in `if(hit){}` as your suggesting there is no change (as expected). But when I add `secondPathEnd = hit2.point + (hit2.normal * radius);` to `if(hit2){}` it then goes out infinitely and ignores the original 3f distance limit, as if it's hitting something all the time when it's not. – Jhon Piper Jul 25 '20 at 19:38
  • @JhonPiper Oh I think I see what you mean. You need to check `if ( Vector3.Distance(secondPathStart, origin2) < predeterminedDistance) { secondPathEnd = origin2; }`. You can also add the distance parameter to [Physics2D.CircleCast](https://docs.unity3d.com/ScriptReference/Physics2D.CircleCast.html) , You leave it as the default wich is Mathf.Infinity. – Pluto Jul 25 '20 at 20:00
  • By setting distance on the 2nd Circlecast it will now appear and work perfectly if the wall is within 3f of the raycast's origin, but it disappears entirely if the wall is not within 3f. And by putting `if ( Vector3.Distance(secondPathStart, origin2) < predeterminedDistance) { secondPathEnd = origin2; }` in `if(hit2)` it appears to have no effect. – Jhon Piper Jul 25 '20 at 20:09
  • However, by removing the distance limit on circlecast and changing `if ( Vector3.Distance(secondPathStart, origin2) < predeterminedDistance) { secondPathEnd = origin2; } in if(hit2)` to `if (Vector3.Distance(secondPathStart, origin2) > 3f) { secondPathEnd = origin + secondDirection * 3f; }` everything works perfectly! I know you didn't help me with this last bit here, but I still wouldn't have gotten this far without you. Thank you very much for your help! – Jhon Piper Jul 25 '20 at 20:59
  • Possibly, I do have quite a few other components that interact with each other in a similar regard. I'd be hard to say which one is causing the difference. Either way I'm glad we got it sorted out, thank you again for your help! – Jhon Piper Jul 25 '20 at 21:29