-
Notifications
You must be signed in to change notification settings - Fork 17
Improved Ball Rebounds
So far the ball rebounds upon collisions with the other objects have been very simple. In particular, there was little control over a ball direction after a collision with the platform. In this part, I want to improve this situation and introduce some other changes to rebound algorithms.
The ball participates in 3 types of collisions: ball-platform, ball-walls and ball-bricks. For each type, I explicitly define a function to handle the corresponding rebound.
function collisions.ball_platform_collision( ball, platform )
.....
if overlap then
ball.platform_rebound( shift_ball, ..... )
end
end
function collisions.ball_walls_collision( ball, walls )
.....
if overlap then
ball.wall_rebound( shift_ball )
end
.....
end
function collisions.ball_bricks_collision( ball, bricks )
.....
if overlap then
ball.brick_rebound( shift_ball )
bricks.brick_hit_by_ball( i, brick, shift_ball )
end
.....
end
The ball-brick rebound is the simplest of the three.
I leave it as the normal rebound.
However, I also want to increase the ball's speed after some number of collisions.
To implement this, it is necessary to maintain a collision counter inside the ball
table.
function ball.brick_rebound( shift_ball )
ball.normal_rebound( shift_ball )
ball.increase_collision_counter()
ball.increase_speed_after_collision()
end
function ball.increase_collision_counter()
ball.collision_counter = ball.collision_counter + 1
end
function ball.increase_speed_after_collision()
local speed_increase = 20
local each_n_collisions = 10
if ball.collision_counter ~= 0 and
ball.collision_counter % each_n_collisions == 0 then
ball.speed = ball.speed + ball.speed:normalized() * speed_increase
end
end
In the ball-platform collision, the ball direction after the rebound should depend on the point where the ball hit the platform. The first attempt might be to bind the rebound angle to a distance between the platform and ball centers, i.e. something like
ball_rebound_angle = (ball_center.x - platform_center.x) / platform.half_width
* max_rebound_angle
ball.speed = ball.speed:len() * vector( math.sin( ball_rebound_angle ),
-math.cos( ball_rebound_angle ) ) --(*1)
(*1): ball_rebound_angle
is counted from the vertical, 0-angle direction is up.
However, I don't like this approach. While simple, it results in counterintuitive mechanics and makes it hard to precisely control the direction of the ball (IMO).
Instead, a more complicated method is used. An idea is to make the ball rebound from the platform mimic a rebound from a spherical surface. To determine the ball speed, it is possible to use the same rule as for the normal rebound: the tangential component of the speed is conserved, whereas the normal is reversed.
The major difference, however, is the direction of the normal to the surface of the platform.
For a plane horizontal platform the normal is always oriented vertical. To imitate a spherical surface,
the direction of the normal at the point of the collision can be set as n = ( sin( alpha ), -cos( alpha ) ) (see the figure below). For the present purposes, it can be approximated as n ~ ( x / R, -1 ). The radius of the sphere R is a parameter that can be adjusted. The vector class has a method projectOn
which allows to compute the normal component of the ball speed local v_norm = ball.speed:projectOn( normal_direction )
. The tangential component is then calculated by subtracting the normal component from the whole vector local v_tan = ball.speed - v_norm
. The speed after the collision is computed as ball.speed = v_tan + (-1)* v_norm
.
Since the normal direction depends on the separation between the centers of the platform and the ball, it is necessary to pass the coordinates of the platform center into that function. I simply pass the whole platform object.
function ball.platform_rebound( shift_ball, platform )
ball.bounce_from_sphere( shift_ball, platform )
ball.increase_collision_counter()
ball.increase_speed_after_collision()
end
function ball.bounce_from_sphere( shift_ball, platform )
local actual_shift = ball.determine_actual_shift( shift_ball )
ball.position = ball.position + actual_shift
if actual_shift.x ~= 0 then
ball.speed.x = -ball.speed.x
end
if actual_shift.y ~= 0 then
local sphere_radius = 200
local ball_center = ball.position
local platform_center = platform.position +
vector( platform.width / 2, platform.height / 2 )
local separation = ( ball_center - platform_center )
local normal_direction = vector( separation.x / sphere_radius, -1 )
local v_norm = ball.speed:projectOn( normal_direction )
local v_tan = ball.speed - v_norm
local reverse_v_norm = v_norm * (-1)
ball.speed = reverse_v_norm + v_tan
end
end
function ball.determine_actual_shift( shift_ball )
local actual_shift = vector( 0, 0 )
local min_shift = math.min( math.abs( shift_ball.x ),
math.abs( shift_ball.y ) )
if math.abs( shift_ball.x ) == min_shift then
actual_shift.x = shift_ball.x
else
actual_shift.y = shift_ball.y
end
return actual_shift
end
function collisions.ball_platform_collision( ball, platform )
.....
if overlap then
ball.platform_rebound( shift_ball, platform )
end
end
Finally, the ball-wall rebound also remains the normal rebound, but with some modifications. Changes in the platform-ball rebound algorithm made it possible for the ball to fly horizontally or almost horizontally. This is undesirable, because the ball can get stuck bouncing from left to right without reaching the platform. To prevent such a situation, during a ball-wall collision, the ball's speed angle to the horizontal is checked and set no less than some minimal angle.
function ball.wall_rebound( shift_ball )
ball.normal_rebound( shift_ball )
ball.min_angle_rebound()
ball.increase_collision_counter()
ball.increase_speed_after_collision()
end
function ball.min_angle_rebound()
local min_horizontal_rebound_angle = math.rad( 20 )
local vx, vy = ball.speed:unpack()
local new_vx, new_vy = vx, vy
rebound_angle = math.abs( math.atan( vy / vx ) )
if rebound_angle < min_horizontal_rebound_angle then
new_vx = sign( vx ) * ball.speed:len() *
math.cos( min_horizontal_rebound_angle )
new_vy = sign( vy ) * ball.speed:len() *
math.sin( min_horizontal_rebound_angle )
end
ball.speed = vector( new_vx, new_vy )
end
local sign = math.sign or function(x) return x < 0 and -1 or x > 0 and 1 or 0 end
Feedback is crucial to improve the tutorial!
Let me know if you have any questions, critique, suggestions or just any other ideas.
Chapter 1: Prototype
- The Ball, The Brick, The Platform
- Game Objects as Lua Tables
- Bricks and Walls
- Detecting Collisions
- Resolving Collisions
- Levels
Appendix A: Storing Levels as Strings
Appendix B: Optimized Collision Detection (draft)
Chapter 2: General Code Structure
- Splitting Code into Several Files
- Loading Levels from Files
- Straightforward Gamestates
- Advanced Gamestates
- Basic Tiles
- Different Brick Types
- Basic Sound
- Game Over
Appendix C: Stricter Modules (draft)
Appendix D-1: Intro to Classes (draft)
Appendix D-2: Chapter 2 Using Classes.
Chapter 3 (deprecated): Details
- Improved Ball Rebounds
- Ball Launch From Platform (Two Objects Moving Together)
- Mouse Controls
- Spawning Bonuses
- Bonus Effects
- Glue Bonus
- Add New Ball Bonus
- Life and Next Level Bonuses
- Random Bonuses
- Menu Buttons
- Wall Tiles
- Side Panel
- Score
- Fonts
- More Sounds
- Final Screen
- Packaging
Appendix D: GUI Layouts
Appendix E: Love-release and Love.js
Beyond Programming: