Basically, you want to implement circle-cirle collision detection and response. I consider your character a static object and the ball a dynamic object. I do this because it makes everything a lot easier, and the character doesn't move too much anyways.
First, your ball should have a velocity vector and a position. You need to get it's old and new position (the new position is just old position + velocity) and it's radius. You should also have the position and radius of the player.
The old and new position of the ball forms a line, get the distance of the line and the player's position:
In case you don't know how to calculate the distance between a line and a point, here's a great StackOverflow answer for that.
d
will always be the closest the 2 circles get to each other. If it's bigger than the sum of the 2 radiuses, then they never collide. This is the end of the broad phase.
Now we can get the exact point the 2 circles collide:
d
is the closest the 2 circles get, t
is the sum of the circles' radiuses. s
is the distance of the collision point from the closest point, this is unknown but you can calculate it with the Pythagorean theorem:
s = sqrt(t * t - d * d)
Now calculate the distance between the closest point and the old position (get the distance from the old position and the player, then use the Pythagorean theorem again with the distance and d), divide s from it, then multiply the normalized velocity vector of the ball with the result. Again, in pseudo code:
func get_position(float s, float d, vec2 old_position, vec2 player, vec2 velocity) {
u := mag(player - old_position)
dist := sqrt(u * u - d * d) - s
return player + normalize(velocity) * dist
}
Now the bouncing part. The normal at the intersection of the 2 circles is always equal to the normalized version of the vector between the intersection point and the center of the circle, thus it's also the normalized version of the vector going from the player's position to the ball's new position.
Let's call the normal n
and the velocity vector v
Based on this other nice Mathematics answer, you can get the new velocity vector with the following formula:
v2 := v - 2 * dot(v, n) * n
Set the velocity of the player to v2 and done. Now, here's the complete answer in pseudo code (I assume you have a way to set the player's velocity and position, here I use the set_player(vec2 new_pos, vec2 new_velocity)
function):
func intersect(vec2 ball_pos, float ball_radius, vec2 ball_velocity, vec2 player_pos, float player_radius) {
d := line_point_dist(ball_pos, ball_pos + ball_velocity, player_pos)
t := ball_radius + player_radius
if (d > t) do
# The balls never intersect
return
end
s := sqrt(t * t - d * d)
pos := get_position(s, d, ball_pos, player_pos, ball_velocity)
n := normalize(pos - player_pos)
new_velocity := ball_velocity - 2 * dot(ball_velocity, n) * n
set_player(pos, new_velocity)
}
func line_point_dist(vec2 start, vec2 end, vec2 point) {
l := pow(mag(end - start), 2)
if (l == 0) do
return mag(end - start)
end
t := max(min(1, dot(point - end, end - start) / l))
proj := start + t * (end - start)
return mag(point - proj)
}
func get_position(float s, float d, vec2 old_position, vec2 player, vec2 velocity) {
u := mag(player - old_position)
dist := sqrt(u * u - d * d) - s
return player + normalize(velocity) * dist
}