Arx 14 Game Over - noooway/love2d_arkanoid_tutorial GitHub Wiki

So far, one important part has been missing - the game over condition.

The mechanics is simple. We need to add a lives counter and remove one life if the ball escapes through the bottom part of the screen. If the amount of lives becomes less than zero, we show the game over screen.

The first thing is a how to store the lives. I define a separate class for this. The lives are stored in the lives field; on start there are 5 of them.

function LivesDisplay:new( o )
   .....
   o.name = o.name or "lives_display"
   o.position = o.position or vector( 680, 500 )
   o.lives = o.lives or 5
   return o
end

The update method is empty, the draw method displays remaining lives in the bottom-right corner of the screen.

function LivesDisplay:update( dt )
end

function LivesDisplay:draw()
   love.graphics.print( "Lives: " .. tostring( self.lives ),
                        self.position.x,
                        self.position.y )
end

The new LivesDisplay instance is created in the game:enter. (don't forget to require the new class).

function game:enter( previous_state, ... )
   args = ...
   .....
   level_filename = "levels/" .. level_sequence.sequence[ level_counter ]
   level = args.level or require( level_filename )
   lives_display = args.lives_display or LivesDisplay:new()
   collider = args.collider or HC.new()
   .....
end

It is also necessary to make changes in the game:draw() callback to display the lives counter:

function game:draw()
   ball:draw()
   platform:draw()
   bricks_container:draw()
   walls_container:draw()
   lives_display:draw()
end

Now to the lives decreasing. It is possible to implement it by monitoring the ball collision with the existing bottom wall. Alternatively we can just monitor ball y-coordinate: if it becomes greater than screen height, we remove the ball. I'll use the second method and remove the bottom wall.

function Ball:update( dt, platform )
   self.position = self.position + self.speed * dt
   self.collider_shape:moveTo( self.position:unpack() )
   self:check_escape_from_screen()
end

function Ball:check_escape_from_screen()
   local x, y = self.position:unpack()
   local ball_top = y - self.radius
   if ball_top > love.graphics.getHeight() then
      self.to_destroy = true
   end
end

function WallsContainer:new( o )
   .....
   o.walls.left = left_wall
   o.walls.right = right_wall
   o.walls.top = top_wall
   return o
end

If the ball leaves the screen, we decrease the lives counter. If the amount of remaining lives drops below zero, we switch to gameover screen.

function game:update( dt )
   ball:update( dt )
   .....
   check_no_more_balls()
   switch_to_next_level()
end

function check_no_more_balls()
   if ball.to_destroy then
      ball:destroy()
      lives_display:lose_life()
      if lives < 0 then
         platform:destroy()
         bricks_container:destroy()
         walls_container:destroy()
         Gamestate.switch( gameover,
                           { level_sequence = level_sequence,
                             level_counter = level_counter,
                             level = level,
                             collider = collider,
                             lives_display = lives_display,
                             ball = ball,
                             platform = platform,
                             walls_container = walls_container,
                             bricks_container = bricks_container } )
      else
         ball = Ball:new( { collider = collider } )      
      end
   end
end

function LivesDisplay:lose_life()
   self.lives = self.lives - 1
end

The GameOver gamestate is similar to the "Game Paused" state, except the 'Game Over!' message and reaction on 'Enter' key: the game is restarted from the first level and there is no need to pass back the game_objects table.

function gameover:enter( previous_state, ... )
   game_objects = ...
end

function gameover:update( dt )
end

function gameover:draw()
   for _,obj in pairs( game_objects ) do
      if type(obj) == "table" and obj.draw then
         obj:draw()
      end
   end
   self:cast_shadow()
   love.graphics.print(
      "Game over. Press Enter to continue or Esc to quit",
      50, 50)
end

function gameover:keyreleased( key, code )
   if key == 'return' then      
      Gamestate.switch( game, { level_counter = 1 } )
   elseif  key == 'escape' then
      love.event.quit()
   end    
end