class: center, middle # Make Games With LÖVE! ![](logo-love.png) --- # Seppi ### josefnpat@gmail.com Game Developer, Cat Herder, Hat Wearer, etc. ![](avatar.png) --- ## What is LÖVE? ### LÖVE = Game Framework * Backend: SDL, OpenAL, OpenGL, and much more. * Frontend: Lua 5.1 (LuaJIT) ![](Roxbury-Brothers-Jim-Carrey-Head-Dance.gif) _Baby, don't hurt me..._ --- ## Lua 5.1 (LuaJIT) * Small syntax set (Easy to learn!) * Easily embeddable (Easy to hook into advanced libraries!) * Fast (Zoom!) ![](lua.png) --- ## Installed? If you don't have love installed, go ahead and do so now. * Visit http://love2d.org * This lesson uses `LÖVE 0.10.2` "Super Toast" ![](website.png) --- ## Confirm Install? Run your love binary to make sure it works. You should get the no-game screen with super toast: ![](no-game.png) --- ## Running your code * Windows/Mac: drag and drop your folder with `main.lua` onto `love.exe` * Linux: use the command line and run `love
` where `
` is the location of your code. ![](run-windows.resize.png) --- # OK, let's make a game! We're going to make ... ## Missile Command by Atari, Inc (1980) ![](Missile_Command.jpg) ![](Missile_Command_A5200.png) --- ![](final_new_game-resized.png) ??? Note the different elements: * Background * Targets (bases and cities) * UI Text * Cursor * Score * Key to start New Game --- ![](final-resized.png) ??? * Enemy Missiles * Counter Missiles * Explosions * Sound Effects * Music --- ## Elements Here are the elements that you saw in the previous two slides. We will do them in this order: * Background * Cursor * Key to start New Game * Targets (bases and cities) * Counter Missiles * Explosions * Enemy Missiles And if we have time: * Title and Score (Basic UI) * Fonts (Advanced UI) * Music * Sound Effects --- ## diff utility All changes that you are to make to your code will be in something akin to diff format. * Lines that start with a space (nothing) should be left alone. * Lines that start with a `+` should be added. * Lines that start with a `-` should be removed. * Lines that contain `--<
>` mean that extra code has been removed to help fit on a slide. ```patch Hello there! +I want you to add this line! -but you should remove this line. --<
> Not game over, but "The End". ``` Would result in: ```patch Hello there! I want you to add this line! --<
> Not game over, but "The End". ``` --- ## Lua - Comments Sometimes it's nice to leave notes for other developers. ```lua -- This is a single line comment ``` --- ## Lua - Variables A variable is a named storage location that we can store things in. ```lua -- Boolean (true or false) cats_are_cute = true the_earth_is_flat = false -- Strings (series of characters) my_cats_name = "Dinah, Warmonger of Doom" worst_fear = 'Being mauled by my cat' -- Numbers (integers or doubles) fingers_on_my_hand = 5 centimeters_in_an_inch = 2.54 times_attacked_by_a_bear = 0 bank_account_balance = -9000 -- Tables (collection of other variables) basketball_baskets = {2,2,1,3} player = {} player.name = "Mario" player.lives = 3 player.has_mushroom = false player.princess = {name="Peach",has_mushroom=true} ``` --- ## Lua - Functions A function is a series of instructions that are packaged as a unit. ```lua player = { name = "Mario", lives = 3, has_mushroom = false, } function print_name(player) print(player.name) return player.lives,player.has_mushroom end local lives,has_mushroom = print_name(player) -- Mario print(lives) -- 3 print(has_mushroom) -- false ``` --- ## Lua - Function Scope The scope of a function is what variables a function can use. ```lua -- A global variable can be accessed anywhere after it is stored lives = 6 -- global variable friend = "Toad" function print_name() -- A local variable can only be accessed for the rest of the function block local friend = "Luigi" -- local variable print(friend .. "[" .. lives .. "]") end print_name() -- Luigi[6] print(friend) -- Toad ``` --- ## LÖVE - Callbacks When specific functions are defined, love will treat them as "callbacks" or functions that the framework chooses when to call. ```lua function love.load() -- When LÖVE starts end function love.draw() -- When LÖVE wants to know what to draw to the screen end function love.update(dt) -- When LÖVE wants to know what to update end function love.mousepressed(x,y,b) -- When LÖVE wants to know what to do when you push a mouse button end function love.keypressed(key) -- When LÖVE wants to know what to do when you push a key on the keyboard end ``` --- ## LÖVE - love.graphics.newImage `image = love.graphics.newImage( filename )` Arguments: * string `filename` - The filepath to the image file. Returns: * Image `image` - An Image object which can be drawn on screen. http://love2d.org/wiki/love.graphics.newImage --- ## LÖVE - love.graphics.draw `love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy )` Arguments: * Drawable `drawable` - A drawable object. * number `x` (0) - The position to draw the object (x-axis). * number `y` (0) - The position to draw the object (y-axis). * number `r` (0) - Orientation (radians). * number `sx` (1) - Scale factor (x-axis). * number `sy` (`sx`) - Scale factor (y-axis). * number `ox` (0) - Origin offset (x-axis). * number `oy` (0) - Origin offset (y-axis). Returns: * None http://love2d.org/wiki/love.graphics.draw --- ## Get On With It! ![](get_on_with_it.jpg) ### Let's make a game already! --- ## Code Editor Open up `main.lua` with your code editor of choice. Here's me using gEdit! ![](gedit.png) --- ## Background Set up the load and draw callbacks and use `love.graphics.newImage` and `love.graphics.draw` to draw the background. ```patch function love.load() g_images = {} + g_images.background = love.graphics.newImage("images/background.png") --<
> end function love.draw() + love.graphics.draw(g_images.background) --<
> end ``` --- ## LÖVE - love.mouse.getPosition() `x,y = love.mouse.getPosition()` Arguments: * None Returns: * number `x` - The position of the mouse along the x-axis. * number `y` - The position of the mouse along the y-axis. http://love2d.org/wiki/love.mouse.getPosition --- ## Cursor Let's add a `draw_ui()` function and use `love.mouse.getPosition()` to draw the cursor. ```patch function love.load() g_images = {} g_images.background = love.graphics.newImage("images/background.png") g_images.cursor = love.graphics.newImage("images/cursor.png") --<
> end function love.draw() --<
> draw_ui() end function draw_ui() --<
> + local mousex,mousey = love.mouse.getPosition() + love.graphics.draw(g_images.cursor,mousex,mousey,0,1,1,32,32) end ``` --- ## Lua - Operators Operators are symbols that tell the interpreter to perform mathematical or logical operations. We will be using the following: * Equality: `==` (e.g. `A == B`) * Less Than `<` (e.g. `A < B`) * Logical Not: `not` (e.g. `A == not B`) * Logical And: `and` (e.g. `A and B`) * Addition: `+` (e.g. `A + B`) * String Concatination: `..` (e.g. `"A".."B"`) Lua has much more: * Arithmetic Operators: `+`, `-`, `*`, `/`, `%`, `^`, `-` * Relational Operators: `==`, `~=`, `>`, `<`, `>=`, `<=` * Logical operators: `and`, `or`, `not` * Misc: `..`, `#` --- ## Lua - Conditionals Lua also has syntax to perform conditionals. ```lua if attacked_by_bear then run_in_fear = true end ``` And here is a more complex if-then-elseif example: ```lua if cat_name == "dinah" then print("Oh no! Please spare my life!") elseif cat_name == "garfield" then print("Where'd my lasagne go?") else print("I fear no cat! Get out of here, " .. cat_name .. "!") end ``` --- ## New Game (1/2) Let's load the missile command library that will have all of the game logic. ```patch missilecommandlib = require "missilecommand" function love.load() g_images = {} g_images.background = love.graphics.newImage("images/background.png") g_images.cursor = love.graphics.newImage("images/cursor.png") --<
> new_game() end function new_game() g_mc = missilecommandlib.new() end ``` --- ## New Game (2/2) We will use the `love.keypressed(key)` callback to detect the spacebar for game states. ```patch function love.keypressed(key) if key == "space" then if g_mc:is_new_game() then g_mc:start_new_game() end if g_mc:is_gameover() then new_game() end end end ``` --- ## Lua - pairs Lua has an iterator system designed for tables with content in them. When you need to do something for every entry in a table, we use can `pairs`. ```lua basketball_baskets = {2,2,3,1,2} for i,v in pairs(basketball_baskets) do print("basket number: "..i..", points scored: "..v) end -- basket number: 1 points scored: 2 -- basket number: 2 points scored: 2 -- basket number: 3 points scored: 3 -- basket number: 4 points scored: 1 -- basket number: 5 points scored: 2 ``` --- ## Targets - Cities and Bases (1/3) Let's load the target assets and set up a `draw_targets()` function. ```patch function love.load() g_images = {} g_images.background = love.graphics.newImage("images/background.png") g_images.cursor = love.graphics.newImage("images/cursor.png") g_images.base = love.graphics.newImage("images/base.png") g_images.city = love.graphics.newImage("images/city.png") --<
> new_game() end ``` ```patch function love.draw() love.graphics.draw(g_images.background) + draw_targets(g_mc:get_targets()) draw_ui() end ``` --- ## Targets - Cities and Bases (2/3) Let's implement the `draw_targets()` function. This will draw the individual bases and targets. The bases will be drawn at an angle so that you get an idea where you're aiming. We will use `:find_angle_to_position` for this. ```patch function draw_targets(targets) + for _,target in pairs(targets) do if target.alive then if target.type == "base" then local mousex,mousey = love.mouse.getPosition() local angle = g_mc:find_angle_to_position(target,mousex,mousey) love.graphics.draw(g_images.base,target.x,target.y,angle,1,1,32,32) else love.graphics.draw(g_images.city,target.x,target.y,0,1,1,32,32) end end + end end ``` --- ## Targets - Cities and Bases (3/3) Let's hook up the base reloading so that we don't run out of ammo. ```patch function love.update(dt) if not g_mc:is_gameover() and not g_mc:is_new_game() then g_mc:update_ammo(dt) --<
> end end ``` --- ## LÖVE - love.graphics.setColor() `love.graphics.setColor( red, green, blue, alpha )` Arguments: * number `red` - The amount of red. * number `green` - The amount of green. * number `blue` - The amount of blue. * number `alpha` (255) - The amount of alpha. The alpha value will be applied to all subsequent draw operations, even the drawing of an image. Return: * None http://love2d.org/wiki/love.graphics.setColor --- ## Counter Missiles (1/2) Let's add a `draw_missiles()` function we can use to draw missiles. Since we will re-use this function, we will set the color in the `love.draw()` callback using `love.graphics.setColor()`. ```patch function love.draw() love.graphics.draw(g_images.background) + love.graphics.setColor(0,0,255) draw_missiles(g_mc:get_counter_missiles()) love.graphics.setColor(255,255,255) draw_targets(g_mc:get_targets()) draw_ui() end function draw_missiles(missiles) for _,missile in pairs(missiles) do love.graphics.line(missile.source.x,missile.source.y, missile.current.x,missile.current.y) end end ``` --- ## Counter Missiles (2/2) Let's add the logic so that the counter missiles move. We will use `:update_counter_missiles()`. ```patch function love.update(dt) if not g_mc:is_gameover() and not g_mc:is_new_game() then g_mc:update_ammo(dt) + g_mc:update_counter_missiles(dt) --<
> end end ``` ```patch function love.mousepressed(x,y,button) if not g_mc:is_new_game() then g_mc:fire_counter_missile(x,y,button) end end ``` --- ## Lua - math.random() When you need a random number, Lua's `math` module has one for you to use. When a single argument is provided, it will return a random number from 1 to the number provided. ```lua print(math.random(10)) -- 1 print(math.random(10)) -- 4 print(math.random(10)) -- 2 print(math.random(10)) -- 8 print(math.random(10)) -- 9 print(math.random(10)) -- 5 print(math.random(10)) -- 10 ``` --- ## LÖVE - love.graphics.circle() Synopsis love.graphics.circle( mode, x, y, radius ) Arguments: * DrawMode `mode` - How to draw the circle. * number `x` - The position of the center along x-axis. * number `y` - The position of the center along y-axis. * number `radius` - The radius of the circle. Returns: * None http://love2d.org/wiki/love.graphics.circle --- ## Explosions Let's set up `draw_explosions()` function to draw our explosions. We use `math.random()` to make the colors change every frame. ```patch function love.draw() --<
> draw_explosions(g_mc:get_counter_missiles()) draw_targets(g_mc:get_targets()) draw_ui() end ``` ```patch function draw_explosions(counter_missiles) for _,missile in pairs(counter_missiles) do if missile.explode then + love.graphics.setColor(math.random(255),math.random(255),math.random(255)) + love.graphics.circle("fill", + missile.current.x,missile.current.y,missile.explode*64) end end love.graphics.setColor(255,255,255) end ``` --- ## Enemy Missiles Let's reuse our `draw_missiles()` function and wrap it with a new color; red. We will also update the missile and wave logic. ```patch function love.draw() --<
> + love.graphics.setColor(255,0,0) draw_missiles(g_mc:get_enemy_missiles()) love.graphics.setColor(255,255,255) draw_targets(g_mc:get_targets()) draw_ui() end ``` ```patch function love.update(dt) if not g_mc:is_gameover() and not g_mc:is_new_game() then g_mc:update_ammo(dt) g_mc:update_counter_missiles(dt) g_mc:update_enemy_missiles(dt) g_mc:update_next_wave(dt) end end ``` --- ## LÖVE - love.graphics.printf `love.graphics.printf( text, x, y, limit, align )` Arguments: * string `text` - A text string. * number `x` - The position on the x-axis. * number `y` - The position on the y-axis. * number `limit` - Wrap the line after this many horizontal pixels. * AlignMode `align` ("left") - The alignment. Returns: * None http://love2d.org/wiki/love.graphics.printf --- ## Title and Score (Basic UI) Let's add some basic UI elements using `love.graphics.printf()`. We will check `:is_new_game()` and `:is_gameover` to determine what to show. ```patch function draw_ui() love.graphics.print("SCORE: "..g_mc:get_score(),32,32) if g_mc:is_new_game() then love.graphics.setFont(g_fonts.title) + love.graphics.printf("MISSILE COMMAND",0,150,800,"center") + love.graphics.printf("DEFEND CITIES",0,300,800,"center") elseif g_mc:is_gameover() then love.graphics.setFont(g_fonts.title) + love.graphics.printf("THE END",0,300,800,"center") end love.graphics.setFont(g_fonts.default) local mousex,mousey = love.mouse.getPosition() love.graphics.draw(g_images.cursor,mousex,mousey,0,1,1,32,32) end ``` --- ## LÖVE - love.graphics.newFont `font = love.graphics.newFont( filename )` Arguments: * string `filename` - The filepath to the BMFont or TrueType font file. Returns: * Font `font` - A Font object which can be used to draw text on screen. http://love2d.org/wiki/love.graphics.newFont --- ## LÖVE - love.graphics.setFont `love.graphics.setFont( font )` Arguments: * Font `font` - The Font object to use. Returns: * None http://love2d.org/wiki/love.graphics.setFont --- ## Fonts - Advanced UI (1/2) Let's make the UI look better by adding some fonts with `love.graphics.newFont()`. ```patch function love.load() --<
> g_fonts = {} g_fonts.default = love.graphics.newFont("fonts/Audiowide-Regular.ttf",18) g_fonts.title = love.graphics.newFont("fonts/Audiowide-Regular.ttf",64) + g_fonts.subtitle = love.graphics.newFont("fonts/Audiowide-Regular.ttf",24) love.graphics.setFont(g_fonts.default) new_game() end ``` --- ## Fonts - Advanced UI (2/2) Let us set them in the UI with `love.graphics.setFont()`. ```patch function draw_ui() + love.graphics.setFont(g_fonts.subtitle) love.graphics.print("SCORE: "..g_mc:get_score(),32,32) if g_mc:is_new_game() then love.graphics.setFont(g_fonts.title) love.graphics.printf("MISSILE COMMAND",0,150,800,"center") + love.graphics.setFont(g_fonts.subtitle) love.graphics.printf("DEFEND CITIES",0,300,800,"center") elseif g_mc:is_gameover() then love.graphics.setFont(g_fonts.title) love.graphics.printf("THE END",0,300,800,"center") end love.graphics.setFont(g_fonts.default) local mousex,mousey = love.mouse.getPosition() love.graphics.draw(g_images.cursor,mousex,mousey,0,1,1,32,32) end ``` --- ## LÖVE - love.audio.newSource Arguments: * string `filename` - The filepath to the audio file. * SourceType `type` ("stream") - Streaming or static source. Returns: * None --- ## LÖVE - Source:play() ## LÖVE - Source:setLooping() ## LÖVE - Source:stop() ```lua meow = love.audio.newSource("meow.ogg") meow:stop() meow:setLooping(false) meow:play() ``` --- ## Music Let's use `love.audio.newSource` `source:setLooping()` and `source:play()` to start some music when the game loads. ```patch function love.load() g_images = {} g_images.background = love.graphics.newImage("images/background.png") g_images.cursor = love.graphics.newImage("images/cursor.png") g_images.base = love.graphics.newImage("images/base.png") g_images.city = love.graphics.newImage("images/city.png") g_fonts = {} g_fonts.default = love.graphics.newFont("fonts/Audiowide-Regular.ttf",18) g_fonts.title = love.graphics.newFont("fonts/Audiowide-Regular.ttf",64) g_fonts.subtitle = love.graphics.newFont("fonts/Audiowide-Regular.ttf",24) love.graphics.setFont(g_fonts.default) g_music = love.audio.newSource("music/mmc_3.ogg") g_music:setLooping(true) + g_music:play() new_game() end ``` --- ## Sound Effects (1/3) Let's add a few sound effects using `love.audio.newSource()`, `source:stop()` and `source:play()`. We add `source:stop()` so that the sound effects give an immediate response. ```patch function love.load() --<
> g_sounds = {} g_sounds.missile_explode = love.audio.newSource("sounds/missile_explode.ogg","static") + g_sounds.shoot = + love.audio.newSource("sounds/shoot.ogg","static") g_sounds.game_status = love.audio.newSource("sounds/game_status.ogg","static") g_sounds.target_explode = love.audio.newSource("sounds/target_explode.ogg","static") new_game() end ``` --- ## Sound Effects (2/3) Here we hook the sound effects into the `love.update` callback by using `:is_target_exploding()` and `:is_missile_exploding()` ```patch function love.update(dt) if not g_mc:is_gameover() and not g_mc:is_new_game() then g_mc:update_ammo(dt) g_mc:update_counter_missiles(dt) g_mc:update_enemy_missiles(dt) g_mc:update_next_wave(dt) end if g_mc:is_target_exploding() then g_sounds.target_explode:stop() g_sounds.target_explode:play() end if g_mc:is_missile_exploding() then g_sounds.missile_explode:stop() g_sounds.missile_explode:play() end end ``` --- ## Sound Effects (3/3) Let's also add sound effects to shooting, using `:fire_counter_missile`'s return value and a sound effect for when the space bar is pressed. ```patch function love.mousepressed(x,y,button) if not g_mc:is_new_game() then - g_mc:fire_counter_missile(x,y,button) + if g_mc:fire_counter_missile(x,y,button) then + g_sounds.shoot:stop() + g_sounds.shoot:play() + end end end ``` ```patch function love.keypressed(key) if key == "space" then g_sounds.game_status:stop() g_sounds.game_status:play() --<
> end ``` --- ## Targets - Continued Let's show when bases are out of ammo and how much ammo they have left. ```patch function draw_targets(targets) for _,target in pairs(targets) do if target.alive then if target.type == "base" then local mousex,mousey = love.mouse.getPosition() local angle = g_mc:find_angle_to_position(target,mousex,mousey) + if target.ammo < 1 then + love.graphics.setColor(127,127,127) + end love.graphics.draw(g_images.base,target.x,target.y,angle,1,1,32,32) + love.graphics.printf(string.rep("I",target.ammo), + target.x-32,target.y+32,64,"center") + love.graphics.setColor(255,255,255) else --if target.type == "city" then love.graphics.draw(g_images.city,target.x,target.y,0,1,1,32,32) end end end end ``` --- ## Creative Learning Nice job! You've gotten the tutorial done! Go ahead and play around! Some ideas: * Change the explosion radius * Add rapid fire * Make enemy waves happen faster * Change the art! *Check out missilecommand.lua for more logic!* --- class: center, middle # You did it! ## Thanks for using LÖVE Contact me at @josefnpat or josefnpat@gmail.com if you have questions!