2D Circle projectile trajectory prediction : taking collider radius into account

Greetings.

I am currently working on a game in which I will use trajectory prediction on 2D gameObjects using 2D Circle Colliders, in order to get an AI to intercept a projectile.

I’ve made a pretty simple trajectory prediction algorithm, based on looping this pseudocode

// FixedUpdate simulation : Apply projectile curve effect
    
// Physics simulation :
// Check for collision
// |- If collision :
// |  |- Set position to collision point (remember travelled distance) 
// |  |- Set new velocity vector according to projectile parameters (custom bounce)
// |  |- Set new curve effect according to projectile parameters
// |  '- Travel along new velocity vector (remaining travel distance for this tick)
// '- Else (no collision) :
//    '- Travel along current velocity vector

Actual code can be found at the bottom of this post as it is quite long.

The problem I encountered is that my prediction algorithm is based on simulating position, velocity and curve of the projectiles, and as such, does not take their collider radius into account as is.

When using it to predict trajectory, even though it allows me to visualize the custom behaviour of my projectiles, the collision point is wrong, as a larger radius projectile will collide “sooner” than the prediction and thus make it inexact.

Example : http://puu.sh/l0LEJ/58280476d4.gif

I’ve been trying a few different ways to take the collider radius into account, be it during the raycast, or during the repositionning relative to collision point, but didn’t manage to achieve satisfactory results.

I would be very grateful if you could assist me in solving this problem, or point me to something I might have missed or overlooked in my implementation.

List<Vector2> PredictTrajectory(ProjectileScript projectileScript, int cycles)
{
    List<Vector2> projectileInterceptPoints = new List<Vector2>();
    Vector2 projectilePosition = projectileScript.GetPosition();
    Vector2 projectileVelocity = projectileScript.GetVelocity();
    float projectileCurve = projectileScript.GetCurve();
    ProjectileInfo projectileInfo = projectileScript.GetProjectileInfo();

    for (int i = 0; i < cycles; i++)
    {
        // FixedUpdate simulation : Apply projectile curve effect
        if (projectileCurve != 0)
            projectileVelocity = AddAngleToVector2(projectileVelocity, projectileCurve);

        // Physics simulation :
        // Check for collision
        // |- If collision :
        // |  |- Set position to collision point (remember travelled distance)
        // |  |- Set new velocity according to projectile parameters (custom bounce)
        // |  |- Set new curve according to projectile parameters
        // |  '- Travel along new velocity vector (remaining travel distance for this tick)
        // '- Else (no collision) :
        //    '- Travel along current velocity vector
        RaycastHit2D hit;
        hit = Physics2D.Raycast(projectilePosition, projectileVelocity.normalized, (projectileVelocity.magnitude * Time.fixedDeltaTime), 1 << LayerMask.NameToLayer("Walls"));

        if (hit.collider != null)
        {
            // Get distance to collision point and remaining distance
            // TODO take into account circle collider radius
            float distanceToCollisionPoint = Vector2.Distance(projectilePosition, hit.point);
            float remainingTravelDistance = (projectileVelocity.magnitude * Time.fixedDeltaTime) - distanceToCollisionPoint;

            // Set position to collision point
            projectilePosition = hit.point;

            // Set new velocity according to projectile parameters
            if (hit.transform.tag == "BottomWall") // UNITY ANSWERS : Other cases omitted
                projectileVelocity = projectileVelocity.magnitude * (GetVector2FromAngle(projectileInfo.customBounceAngles.BottomWall));

            // Set new curve according to projectile parameters
            if (hit.transform.tag == "BottomWall")
                projectileCurve = projectileInfo.customBounceCurves.BottomWall; // UNITY ANSWERS : 0° in this case

            // Travel along new velocity vector (remaining travel distance for this tick since collision)
            projectilePosition += projectileVelocity.normalized * remainingTravelDistance;
        }
        else
        {
            // Travel along current velocity vector
            projectilePosition += projectileVelocity * Time.fixedDeltaTime;
        }

        projectileInterceptPoints.Add(projectilePosition);
    }

    return projectileInterceptPoints;
}

Fixed with some RTFM

Reading material : Unity - Scripting API: Physics2D.OverlapCircleAll
Used this to detect collision instead of raycast like so :

                    Collider2D[] colliders = Physics2D.OverlapCircleAll(projectilePosition, projectileCollider.radius, 1 << LayerMask.NameToLayer("Walls"));

                    if (colliders.Length > 0)
                    {
                        // Proceed with collision stuff
                    }
                    else
                    {
                        // Travel along current velocity vector
                        discPosition += discVelocity * Time.fixedDeltaTime;
                    }

The only downside is that it doesn’t take the current projectile velocity / direction into account like a raycast, and results in the simulation getting “stuck” on the first valid collider hit unless I manually move it away on the same tick collision is processed.

Result : http://puu.sh/l6C66/dbb7d0e7ff.gif