Skip to content

Final Screen

noooway edited this page Jun 18, 2017 · 46 revisions

In this part I want to add credits screen at the end of the game.

On the credits screen it is customary to mention people who have been involved in the project and resources that have been used. I'm going to use simple text buttons and attach a URL to each one. When cursor hovers over a button, the text changes color; the URL is opened on mouse click.

Most of the required functionality has already been implemented for the buttons class. It is possible to reuse certain parts of the code.

There are different approaches to reusing the code. Lua provides metatables; it is also possible to implement some kind of an OOP system. However, I'll deliberately ignore them and stick to a simpler approach.

To construct a button with URL, it is possible to use a constructor from the old buttons class and insert a URL manually. To achieve a better control over the text appearance, it is also convenient to specify font and text alignment on button construction.

local buttons = require "buttons"

local buttons_with_url = {}

function buttons_with_url.new_button( o )
   btn = buttons.new_button( o )
   btn.url = o.url or nil
   btn.font = o.font or love.graphics.getFont()
   btn.text_align = o.text_align or "center"
   .....
   return( btn )
end

The functionality of the update, and inside callbacks of the buttons_with_url is identical to the similar callbacks of the buttons. In fact, it is possible to simply use an assignment such as buttons_with_url.update_button = buttons.update_button. However, I redirect them explicitly.

function buttons_with_url.update_button( single_button, dt )
   buttons.update_button( single_button, dt )
end

function buttons_with_url.inside( single_button, pos )
   buttons.inside( single_button, pos )
end

The draw callback is redefined to use fonts and text alignment specified on construction.

function buttons_with_url.draw_button( single_button )
   local oldfont = love.graphics.getFont()
   love.graphics.setFont( single_button.font )
   if single_button.selected then
      local r, g, b, a = love.graphics.getColor()
      love.graphics.setColor( 255, 0, 0, 100 )
      love.graphics.printf( single_button.text,
                            single_button.position.x,
                            single_button.position.y,
                            single_button.width,
                            single_button.text_align )
      love.graphics.setColor( r, g, b, a )
   else
      love.graphics.printf( single_button.text,
                            single_button.position.x,
                            single_button.position.y,
                            single_button.width,
                            single_button.text_align )   
   end
   love.graphics.setFont( oldfont )
end

To open a URL on a mouse click, love.system.openURL can be used. The mousereleased callback is redefined instead of being redirected to the buttons.mousereleased.

function buttons_with_url.mousereleased( single_button, x, y, button )
   if single_button.selected then
      local status = love.system.openURL( single_button.url )
   end
   return single_button.selected 
end

These definitions are sufficient to create the necessary buttons on the final screen (at least, that is how I've implemented it initially). The problem is, there are 13 of them; together with non-clickable text labels there are 20 elements, and each one has to be positioned manually. This is cumbersome to maintain: for example, to shift the 'Sound' column 5 pixels right it is necessary to update positions of the 9 elements.

This problem can be alleviated to some extent by grouping buttons into layouts. A layout has a position, a default width and a height for it's elements and an offset between them. This information allows to calculate a position of each element in the layout automatically.

function buttons_with_url.new_layout( o )
   return( { position = o.position or vector( 300, 300 ),
             default_width = o.default_width or 100,
             default_height = o.default_height or 50,
             default_offset = o.default_offset or vector( 10, 10 ),
             orientation = o.orientation or "vertical",
             children = o.children or {} } )
end

A widget agrees that it's position and size should be determined by the layout if it has fields positioning and sizing set to "auto". Still, widgets are allowed to shift from automatically calculated position by displacement_from_auto vector.

function buttons_with_url.new_button( o )
   btn = buttons.new_button( o )
   .....
   btn.sizing = o.sizing or nil
   btn.positioning = o.positioning or nil
   btn.displacement_from_auto = o.displacement_from_auto or vector(0, 0)
   return( btn )
end

When an element with positioning = "auto" and sizing = "auto" is added into a layout, it's position and size are determined by the layout.

function buttons_with_url.add_to_layout( layout, element )
   if element.positioning and element.positioning == 'auto' then
      local position = layout.position
      for i, el in ipairs( layout.children ) do
         if layout.orientation == "vertical" then                             --(*1)
            position = position + vector( 0, el.height ) + layout.default_offset 
         else
            print( "unknown layout orientation" )
         end
      end
      element.position = position + element.displacement_from_auto
   end
   if element.sizing and element.sizing == 'auto' then
      element.width = layout.default_width
      element.height = layout.default_height
   end
   table.insert( layout.children, element )
end

(*1): In GUI toolkits it is typical to have both vertical and horizontal layouts. For the final screen I use only vertical layouts, so I do not define horizontal.

In the draw, update and mousereleased callbacks, a layout iterates over it's elements and calls an appropriate callback for each one.

function buttons_with_url.update_layout( layout, dt )
   for _, btn in pairs( layout.children ) do
      buttons_with_url.update_button( btn, dt )
   end
end

function buttons_with_url.draw_layout( layout )
   for _, btn in pairs( layout.children ) do
      buttons_with_url.draw_button( btn )
   end
end

function buttons_with_url.mousereleased_layout( layout, x, y, button )
   for _, btn in pairs( layout.children ) do
      buttons_with_url.mousereleased_button( btn, x, y, button )
   end
end

Such layouts are partial solution, because the layout accepts only button_with_url as it's elements. An attempt to add an element of another type, e.g. a simple button, would have resulted
in problems with choosing which update and draw methods to call. To handle it, each element should either have a reference to each of it's callbacks or it should store a type and dispatch on this type has to be performed. For the former approach, Lua provides and elegant way - metatables. I plan to address it in one of the appendices.

  1. Final screen: define and draw buttons.
local buttons_with_url = require "buttons_with_url"
local vector = require "vector"

local gamefinished = {}

.....

local section_start_y = 280
local section_width = 250
local section_line_height = 25

function gamefinished.load( prev_state, ... )
   code_section = buttons_with_url.new_layout{
      position = vector( 5, section_start_y ),
      default_width = section_width,
      default_height = section_line_height,
      default_offset = vector( 0, 8 )    
   }
   buttons_with_url.add_to_layout(
      code_section,
      buttons_with_url.new_button{
         text = "Game code by noway",
         url = "https://github.com/noooway/love2d_arkanoid_tutorial",
         font = bungee_font_links,
         positioning = "auto",
         sizing = "auto"
   })
   buttons_with_url.add_to_layout(
      code_section,
      buttons_with_url.new_button{
         text = "Love2d framework by Love Team",
         url = "http://love2d.org/",
         positioning = "auto",
         width = code_section.default_width,
         height = 2 * code_section.default_height,
         font = bungee_font_links
   })
   .....
end

Along with buttons, some additional text is also printed.

function gamefinished.draw()
   local oldfont = love.graphics.getFont()
   love.graphics.setFont( bungee_font )
   love.graphics.printf( "Congratulations!",
                         0, 55, love.graphics.getWidth(), "center" )
   love.graphics.printf( "You have finished the game!",
                         0, 95, love.graphics.getWidth(), "center" )
   love.graphics.printf( "---Credits---",
                         5, 188, love.graphics.getWidth(), "center" )

   local section_names_y = 235
   local section_width = 260
   love.graphics.printf( "Code",
                         5, section_names_y, section_width, "center" )
   love.graphics.printf( "Graphics",
                         275, section_names_y, section_width, "center" )
   love.graphics.printf( "Sound",
                         530, section_names_y, section_width, "center" )

   love.graphics.setFont( bungee_font_links )
   love.graphics.printf( "Samples derived from works by",
                         570, section_start_y, 200, "center" )
   love.graphics.setFont( oldfont )
   
   buttons_with_url.draw_layout( code_section )
   buttons_with_url.draw_layout( graphics_section )
   buttons_with_url.draw_layout( sound_effects_section )
   buttons_with_url.draw_button( music_button )
   buttons_with_url.draw_button( thanks_button )
end
  1. Make buttons react on mouse
function gamefinished.update( dt )
   buttons_with_url.update_button( gamecode_button, dt )
   buttons_with_url.update_button( love_button, dt )
   buttons_with_url.update_button( vector_button, dt )
   buttons_with_url.update_button( tileset_button, dt )
   buttons_with_url.update_button( bungeefont_button, dt )
   buttons_with_url.update_button( cmusounddesign_button, dt )
   buttons_with_url.update_button( ngruber_button, dt )
   buttons_with_url.update_button( bart_button, dt )
   buttons_with_url.update_button( qubodup_button, dt )
   buttons_with_url.update_button( tinyworlds_button, dt )
   buttons_with_url.update_button( edgardedition_button, dt )
   buttons_with_url.update_button( music_button, dt )
end

function gamefinished.mousereleased( x, y, button, istouch )
   if button == 'r' or button == 2 then
      love.event.quit()
   end
   buttons_with_url.mousereleased( gamecode_button, x, y, button )
   buttons_with_url.mousereleased( love_button, x, y, button )
   buttons_with_url.mousereleased( vector_button, x, y, button )
   buttons_with_url.mousereleased( tileset_button, x, y, button )
   buttons_with_url.mousereleased( bungeefont_button, x, y, button )
   buttons_with_url.mousereleased( cmusounddesign_button, x, y, button )
   buttons_with_url.mousereleased( ngruber_button, x, y, button )
   buttons_with_url.mousereleased( bart_button, x, y, button )
   buttons_with_url.mousereleased( qubodup_button, x, y, button )
   buttons_with_url.mousereleased( tinyworlds_button, x, y, button )
   buttons_with_url.mousereleased( edgardedition_button, x, y, button )
   buttons_with_url.mousereleased( music_button, x, y, button )
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