-
Notifications
You must be signed in to change notification settings - Fork 17
Final Screen
In this part I want to add a 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. In order to do this, it is possible to use some advanced Lua facilities or to implement some kind of an OOP system. However, I deliberately ignore such approaches and stick to a simpler method.
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 15 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
An element agrees that it's position and size should be determined by the layout if
it has fields positioning
and sizing
set to "auto"
. Still,
it is 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 computed by the layout taking into account other layout elements.
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 a partial solution: they accepts only button_with_url
as their 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 for this element (buttons_with_url.draw_button
would not fit). 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 this issue in one of the appendices.
With layouts defined, it is possible to create several of them for the final screen. Still, a lot of manual positioning is necessary.
local buttons_with_url = require "buttons_with_url"
local vector = require "vector"
local gamefinished = {}
bungee_font = love.graphics.newFont(
"/fonts/Bungee_Inline/BungeeInline-Regular.ttf", 30 )
bungee_font_links = love.graphics.newFont(
"/fonts/Bungee_Inline/BungeeInline-Regular.ttf", 18 )
.....
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
The update
and mousereleased
callbacks are handled as expected.
function gamefinished.update( dt )
buttons_with_url.update_layout( code_section, dt )
buttons_with_url.update_layout( graphics_section, dt )
buttons_with_url.update_layout( sound_effects_section, dt )
buttons_with_url.update_button( music_button, dt )
buttons_with_url.update_button( thanks_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_layout( code_section )
buttons_with_url.mousereleased_layout( graphics_section )
buttons_with_url.mousereleased_layout( sound_effects_section )
buttons_with_url.mousereleased_button( music_button )
buttons_with_url.mousereleased_button( thanks_button )
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: