1. Create New Project
A love2D project can be several styles: 1. A single Lua script file, 2. A folder with a Lua script file named main.lua
, 3. A package file with .love
extension, this format is mostly used when you want to distribute your games to others.
In this tutorial, we will use the second style. So we create a folder in the Documents folder.
1.1 Create project folder
Select ‘Docs’ Tab, then select ‘Documents’ row in Sandbox section
Documents
Click ‘Select’ button on right of the navigation bar
Enter ‘Selection mode’ of the File Explorer
We can create, select, copy, delete, rename file/folder items.
Here we click the ‘NewFolder’ button in the toolbar
Enter the folder name
Here I use DemoPong as the new project name
1.2 Copy from other project
You can also copy other project as a template, then modify it as you like.
There are many demo projects in the Bundle of the App. You can copy them to Documents, so you can modify them then try to run.
Select Code Editor tab,
Click the topleft button in the navigation bar, a menu will pop up
click the ‘Open Files in Bundle’ menu
Here is the File Selection Panel
There are 3 folders, Games is the folder where all the love2d games live in.
Click on it.
Here is the contents of Games folder
We can see there are 6 items in it. Some are folder type and one is the special ‘.love’ package type.
Enter selection mode
Then we can select the folders we want
Then click the ‘…’ button in the bottom toolbar
Then click the ‘Copy’ menu
now we have copied all the selected items
Let’s quit the File Selection Panel now
Here is the Documents folder in the ‘Docs’ tab
You should know how to navigate here
If you forget, you can go back to 1.1 section
Enter Selection mode
Click ‘…’ button in bottom toolbar
Click ‘Paste’ menu
Then all the items will be pasted here
Go to ‘Love’ tab
pull down to refresh
Then you can see all the games you copied last step will show in the Sandbox section.
1.3 Download some open source love2d game projects from the Internet
You can also download love2d projects source code to your iPhone/iPad, save them to Files app. Then copy the projects to folder Love2D Game Maker in Files app.
Love2DGameMaker folder in Files app
You can add/remove files in it.
It’s very convenient for you to import games from the Internet or your computer.
2 config the game options
2.1 create conf.lua file
Go to DemoPong folder
Enter Selection mode
Click ‘NewFile’ button in bottom toolbar
Enter ‘conf.lua’, the config file name can only be ‘conf.lua’
Click Done
2.2 config options
Here is the Love2D documentation of Config Files – LOVE (love2d.org)
We can copy the demo file contents into out conf.lua
, then do some modifications according to our requirements.
Here is a demo config file that I used in the pong-master game in Bundle
-- Configuration
function love.conf(t)
t.title = "Pong"
t.version = "11.1"
t.gameversion = "0.1"
t.accelerometerjoystick = true
t.window.x = 110
t.window.y = 100
t.window.width = 200 -- The window width (number)
t.window.height = 200 -- The window height (number)
t.window.resizable = true
t.window.fullscreen = true -- whether fullscreen
-- t.window.fullscreentype = exclusive
t.window.borderless = true
t.window.vsync = true -- Enable vertical sync (boolean)
t.window.fsaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.display = 1 -- Index of the monitor to show the window in (number)
t.window.highdpi = true -- Enable high-dpi mode for the window on a Retina display (boolean). Added in 0.9.1
t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean). Added in 0.9.1
t.modules.audio = true -- Enable the audio module (boolean)
t.modules.event = true -- Enable the event module (boolean)
t.modules.graphics = true -- Enable the graphics module (boolean)
t.modules.image = true -- Enable the image module (boolean)
t.modules.joystick = true -- Enable the joystick module (boolean)
t.modules.keyboard = true -- Enable the keyboard module (boolean)
t.modules.math = true -- Enable the math module (boolean)
t.modules.mouse = false -- Enable the mouse module (boolean)
t.modules.physics = true -- we don't need phys on our game.
t.modules.sound = true -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.timer = true -- Enable the timer module (boolean)
t.modules.window = true -- Enable the window module (boolean)
end
This file tells the love2d framework to create a fullscreen window, then the game content will show in that window. If you don’t want a fullscreen window, you can set the `t.window.fullscreen = true` to false. Then the love2d framework will create a window with size 200×200, and origin position (topleft) at point (110,100).
If you want to learn more about the config options, you can read the documents then try to modify the options then run the game to see the effects.
This is why I develop the app, it can help you learn to coding. I have also developed other apps, LuaLu REPL, an app that can run pure lua codes, Solar2D Studio an app that similar to Love2D Game Maker, but based on Solar2D game engine (also known as CoronaSDK)
3 implement the game logic
3.1 Create main.lua file
Create ‘main.lua’ file in DemoPong folder
Below is content of main.lua
debug = false
screenWidth = love.graphics.getWidth()
screenHeight = love.graphics.getHeight()
drawParticles = true
local safeX, safeY, safeW, safeH = love.window.getSafeArea()
updatetime = 0
paddleSize = {w = 50, h = 100}
puckSize = 50
wallThickness = 10
centerLineThickness = 10
goalSize = 300
paddleOffset = 100
gameSettings = {time = 3 * 60, started = false, timeLeft = 3 * 60}
world = nil
objects = {}
score = {left = 0, right = 0}
function love.load(arg)
setScale()
--love.window.setPosition(130, 80, 1)
world = love.physics.newWorld(0, 0)
createPaddles()
createPuck()
createWalls()
nuor = ""
end
-- Function to remove a specific object from the table
function removeFromTable(tbl, obj)
for i, value in ipairs(tbl) do
if value == obj then
table.remove(tbl, i) -- Remove the object from the table
break -- Exit the loop once the object is removed
end
end
end
function removeAllPhysicsObjects(world)
-- Destroy all bodies
local bodies = world:getBodies()
for _, body in ipairs(bodies) do
body:destroy()
end
-- Destroy all joints
local joints = world:getJoints()
for _, joint in ipairs(joints) do
joint:destroy()
end
end
function love.resize(w, h)
print(("Window resized to width: %d and height: %d."):format(w, h))
local oldW = screenWidth
local oldH = screenHeight
screenWidth = love.graphics.getWidth()
screenHeight = love.graphics.getHeight()
if (oldW ~= screenWidth or oldH ~= screenHeight) then
love.window.setMode(screenWidth, screenHeight)
end
removeFromTable(objects, leftPaddle)
removeFromTable(objects, rightPaddle)
removeFromTable(objects, puck)
removeFromTable(objects, wallT)
removeFromTable(objects, wallB)
removeAllPhysicsObjects(world)
createPaddles()
createPuck()
createWalls()
end
function love.displayrotated(i, o)
nuor = love.window.getDisplayOrientation(i)
print("rotate")
if (nuor == "landscape") then
--love.event.quit()
end
end
function love.update(dt)
updatetime = dt
world:update(dt)
puck.pSystem:setPosition(puck.body:getX(), puck.body:getY())
puck.pSystem:update(dt)
if (puck.body:getX() < 0) then
resetPuck()
score.right = score.right + 1
elseif (puck.body:getX() > screenWidth) then
resetPuck()
score.left = score.left + 1
end
if gameSettings.started then
gameSettings.timeLeft = gameSettings.timeLeft - dt
if (gameSettings.timeLeft < 0) then
if score.left > score.right then
gameSettings.winner = "Blue Wins!"
elseif score.right > score.left then
gameSettings.winner = "Red Wins!"
elseif score.right == score.left then
gameSettings.winner = "Tie Game"
end
gameSettings.started = false
gameSettings.timeLeft = gameSettings.time
resetPuck()
score.left = 0
score.right = 0
end
end
end
function love.keypressed(key, scancode, isrepeat)
if key == 'd' and not isrepeat then
debug = not debug
end
if love.keyboard.isDown('escape') then
love.event.push('quit')
elseif love.keyboard.isDown('r') then
resetPuck()
score.left = 0
score.right = 0
elseif love.keyboard.isDown('p') then
drawParticles = not drawParticles
end
end
function love.draw()
love.graphics.print(nuor, 100, 100)
-- center line
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", love.graphics.getWidth() / 2 -
(centerLineThickness / 2), 0,
centerLineThickness, love.graphics.getHeight())
love.graphics.setColor(1, 1, 1)
-- puck and paddles
for i, o in ipairs(objects) do
love.graphics.draw(o.img, o.body:getX(), o.body:getY(), 0, 1, 1,
o.img:getWidth() / 2, o.img:getHeight() / 2)
end
-- draw particle systems
if (drawParticles) then
love.graphics.draw(puck.pSystem, 0, 0)
end
-- scores and timer
love.graphics.setColor(0, 0, 1)
love.graphics.printf(tostring(score.left), 10, 50 * sy,
(screenWidth - 20) / (10 * sx), "left", 0, 10 * sx,
10 * sy)
love.graphics.setColor(1, 0, 0)
love.graphics.printf(tostring(score.right), 10, 50 * sy,
(screenWidth - 20) / (10 * sx), "right", 0, 10 * sx,
10 * sy)
love.graphics.setColor(1, 1, 0)
love.graphics.printf(SecondsToClock(gameSettings.timeLeft), 10, 50 * sy,
(screenWidth - 20) / (10 * sx), "center", 0, 10 * sx,
10 * sy)
-- winner message
if (not gameSettings.started and
not (gameSettings.winner == nil or gameSettings.winner == '')) then
love.graphics.setColor(0, 1, 0)
love.graphics.printf(gameSettings.winner, 10, screenHeight / 2,
(screenWidth - 20) / (10 * sx), "center", 0,
10 * sx, 10 * sy)
love.graphics.setColor(1, 1, 1)
end
if debug then
drawDebug()
end
end
function love.touchpressed(id, x, y, dx, dy, pressure)
if not gameSettings.started then
gameSettings.started = true
gameSettings.winner = nil
end
-- prevent puck from being stuck in x direction, and start motion after resetting puck
if math.abs(puck.body:getLinearVelocity()) < 10 then
local v = 500
if math.random(0, 1) < 0.5 then
v = v * -1
end
puck.body:setLinearVelocity(v, 0)
end
local isLeft = true
if (x > (screenWidth / 2)) then
isLeft = false
end
if (isLeft and leftPaddle.touchid == nil) then
leftPaddle.touchid = id
leftPaddle.joint:setTarget(paddleOffset, y)
elseif (not isLeft and rightPaddle.touchid == nil) then
rightPaddle.touchid = id
rightPaddle.joint:setTarget(screenWidth - paddleOffset, y)
end
end
function love.touchmoved(id, x, y, dx, dy, pressure)
if (leftPaddle.touchid == id and x <= screenWidth / 2) then
leftPaddle.joint:setTarget(paddleOffset, y)
elseif (rightPaddle.touchid == id and x > screenWidth / 2) then
rightPaddle.joint:setTarget(screenWidth - paddleOffset, y)
end
end
function love.touchreleased(id, x, y, dx, dy, pressure)
if (leftPaddle.touchid == id) then
leftPaddle.touchid = nil
elseif (rightPaddle.touchid == id) then
rightPaddle.touchid = nil
end
end
function getCirclePaddle(size, color)
local paddle = love.graphics.newCanvas(size, size)
love.graphics.setCanvas(paddle)
love.graphics.setColor(color.r, color.g, color.b, 1)
love.graphics.circle("fill", size / 2, size / 2, size / 2)
love.graphics.setCanvas()
return paddle
end
function getPaddle(size, color)
local paddle = love.graphics.newCanvas(size.w, size.h)
love.graphics.setCanvas(paddle)
love.graphics.setColor(color.r, color.g, color.b, 1)
love.graphics.rectangle("fill", 0, 0, size.w, size.h, 0, 1, 1, size.w / 2,
size.h / 2)
love.graphics.setCanvas()
return paddle
end
function getRectangle(width, height, color)
local rect = love.graphics.newCanvas(width, height)
love.graphics.setCanvas(rect)
love.graphics.setColor(color.r, color.g, color.b)
love.graphics.rectangle("fill", 0, 0, width, height)
love.graphics.setCanvas()
return rect
end
function createWalls()
wallT = {
img = getRectangle(screenWidth, wallThickness, {r = 1, g = 1, b = 0})
}
wallT.body = love.physics.newBody(world, screenWidth / 2, wallThickness / 2,
"static")
wallT.shape = love.physics.newRectangleShape(screenWidth, wallThickness)
wallT.fixture = love.physics.newFixture(wallT.body, wallT.shape)
wallT.fixture:setFriction(0)
table.insert(objects, wallT)
wallB = {
img = getRectangle(screenWidth, wallThickness, {r = 1, g = 1, b = 0})
}
wallB.body = love.physics.newBody(world, screenWidth / 2,
screenHeight - (wallThickness / 2),
"static")
wallB.shape = love.physics.newRectangleShape(screenWidth, wallThickness)
wallB.fixture = love.physics.newFixture(wallB.body, wallB.shape)
table.insert(objects, wallB)
end
function setScale()
local width, height = love.graphics.getDimensions()
sx = width / 1920
sy = height / 1080
paddleSize.w = paddleSize.w * sx
paddleSize.h = paddleSize.h * sy
wallThickness = wallThickness * sx
puckSize = puckSize * sx
centerLineThickness = centerLineThickness * sx
goalSize = goalSize * sy
paddleOffset = paddleOffset * sx
end
function createPaddles()
leftPaddle = {
img = getPaddle(paddleSize, {r = 0, g = 0, b = 1}),
touchid = nil
}
leftPaddle.body = love.physics.newBody(world, paddleOffset,
screenHeight / 2, "dynamic")
leftPaddle.body:setFixedRotation(true)
leftPaddle.shape = love.physics
.newRectangleShape(paddleSize.w, paddleSize.h)
leftPaddle.fixture = love.physics.newFixture(leftPaddle.body,
leftPaddle.shape)
leftPaddle.joint = love.physics.newMouseJoint(leftPaddle.body, paddleOffset,
screenHeight / 2)
rightPaddle = {
img = getPaddle(paddleSize, {r = 1, g = 0, b = 0}),
touchid = nil
}
rightPaddle.body = love.physics.newBody(world, screenWidth - paddleOffset,
screenHeight / 2, "dynamic")
rightPaddle.body:setFixedRotation(true)
rightPaddle.shape = love.physics.newRectangleShape(paddleSize.w,
paddleSize.h)
rightPaddle.fixture = love.physics.newFixture(rightPaddle.body,
rightPaddle.shape)
rightPaddle.joint = love.physics.newMouseJoint(rightPaddle.body,
screenWidth - paddleOffset,
screenHeight / 2)
table.insert(objects, leftPaddle)
table.insert(objects, rightPaddle)
end
function createPuck()
puck = {img = getCirclePaddle(puckSize, {r = 1, g = 1, b = 0})}
puck.body = love.physics.newBody(world, screenWidth / 2, screenHeight / 2,
"dynamic")
puck.body:setLinearDamping(-.20)
puck.body:setBullet(true)
puck.shape = love.physics.newCircleShape(puckSize / 2)
puck.fixture = love.physics.newFixture(puck.body, puck.shape)
puck.fixture:setRestitution(.9)
local pSystem = love.graphics.newParticleSystem(puck.img, puckSize)
pSystem:setParticleLifetime(0.2, 0.5)
pSystem:setLinearAcceleration(-100, -100, 100, 100)
pSystem:setColors(1, 1, 0, 255, 1, 1, 1, 255)
pSystem:setSizes(1.0, 0.01)
pSystem:setEmissionRate(60)
puck.pSystem = pSystem
table.insert(objects, puck)
end
function resetPuck()
puck.body:setLinearVelocity(0, 0)
puck.body:setX(screenWidth / 2)
puck.body:setY(screenHeight / 2)
end
function SecondsToClock(seconds)
local seconds = tonumber(seconds)
if seconds <= 0 then
return "00:00"
else
mins = string.format("%02.f", math.floor(seconds / 60))
secs = string.format("%02.f", math.floor(seconds - mins * 60))
return mins .. ":" .. secs
end
end
function drawDebug()
love.graphics.setColor(1, 1, 1)
love.graphics.print("DT: " .. tostring(updatetime), 10, 10)
love.graphics.print("FPS: " .. tostring(1.0 / updatetime), 10, 20)
love.graphics.print(
"Screen " .. tostring(love.graphics.getWidth()) .. "x" ..
tostring(love.graphics.getHeight()) .. " scale " .. tostring(sx) ..
"x" .. tostring(sy), 10, 30)
love.graphics.print("Puck " .. tostring(puck.body:getX()) .. ", " ..
tostring(puck.body:getY()) .. " v: " ..
puck.body:getLinearVelocity(), 10, 40)
love.graphics.print("Score " .. tostring(score.left) .. ":" ..
tostring(score.right), 10, 50)
love.graphics.print("Game " .. tostring(gameSettings.timeLeft), 10, 60)
love.graphics.print("SafeArea " .. tostring(safeX) .. "," .. tostring(safeY) .."," .. tostring(safeW) .."," .. tostring(safeH), 10, 70)
for _, body in pairs(world:getBodies()) do
for _, fixture in pairs(body:getFixtures()) do
local shape = fixture:getShape()
if shape:typeOf("CircleShape") then
local cx, cy = body:getWorldPoints(shape:getPoint())
love.graphics.circle("fill", cx, cy, shape:getRadius())
elseif shape:typeOf("PolygonShape") then
love.graphics.polygon("fill",
body:getWorldPoints(shape:getPoints()))
else
love.graphics.line(body:getWorldPoints(shape:getPoints()))
end
end
end
end