Skip to content

Improved Ball Rebounds

noooway edited this page Jun 18, 2017 · 1 revision

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

    Home
    Acknowledgements
    Todo

Chapter 1: Prototype

  1. The Ball, The Brick, The Platform
  2. Game Objects as Lua Tables
  3. Bricks and Walls
  4. Detecting Collisions
  5. Resolving Collisions
  6. Levels

    Appendix A: Storing Levels as Strings
    Appendix B: Optimized Collision Detection (draft)

Chapter 2: General Code Structure

  1. Splitting Code into Several Files
  2. Loading Levels from Files
  3. Straightforward Gamestates
  4. Advanced Gamestates
  5. Basic Tiles
  6. Different Brick Types
  7. Basic Sound
  8. 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

  1. Improved Ball Rebounds
  2. Ball Launch From Platform (Two Objects Moving Together)
  3. Mouse Controls
  4. Spawning Bonuses
  5. Bonus Effects
  6. Glue Bonus
  7. Add New Ball Bonus
  8. Life and Next Level Bonuses
  9. Random Bonuses
  10. Menu Buttons
  11. Wall Tiles
  12. Side Panel
  13. Score
  14. Fonts
  15. More Sounds
  16. Final Screen
  17. Packaging

    Appendix D: GUI Layouts
    Appendix E: Love-release and Love.js

Beyond Programming:

  1. Game Design
  2. Minimal Marketing (draft)
  3. Finding a Team (draft)

Archive

Clone this wiki locally