I just started learning programming in the past two or three months. I started with Python, and now I'm learning lua. I started game programming this week and this is a game I made with the Love2D engine for lua. If you don't know how it works, there are these three main functions:
love.load() -- Runs at game startup and doesn't run again unless explicitly called
love.update() -- Updates values in the game and runs every tick. It uses delta time as an argument for consistent speed on every machine
love.draw() -- Draws the updates to the screen, however you choose for them to be displayed.
love.update()
and love.draw()
loop back and forth in-between each other.
love.update()
always runs first and then love.draw()
.
This is my first game, a Pong like game where you destroy blocks with a ball bounced off of a paddle like player. I would like less criticism on the game itself, but more on the structure of the code. Please let me know if you see anywhere I could make a function, or achieve something with less code.
function love.load()
-- WINDOW SETUP
love.window.setTitle("Block Buster")
height = love.graphics.getHeight()
width = love.graphics.getWidth()
-- SOUND SOURCES
hit = love.audio.newSource("hit.mp3")
bounce = love.audio.newSource("bounce.mp3")
loss = love.audio.newSource("loss.mp3")
-- PLAYER SETUP
player = {}
function player.load()
player.width = 70
player.height = 20
player.x = width/2 - player.width/2
player.y = height - player.height
player.speed = 400
player.lives = 5
player.points = 0
end
player.load()
-- BLOCKS
blocks = {}
blocks.draw = {}
-- LOAD BLOCKS
function blocks.load()
column = 0; row = 1
while 5 >= row do
block = {}
block.width = width/10 - 5
block.height = 20
block.x = column * (block.width + 5)
block.y = row * (block.height + 5)
table.insert(blocks.draw, block)
column = column + 1
if column == 10 then column = 0; row = row + 1 end
end
end
blocks.load()
-- BALL
ball = {}
function ball.load()
ball.radius = 5
ball.x = width/2
ball.y = player.y - 200
ball.speed = 200
ball.direction = "d"
ball.cooldown = 200
end
ball.load()
-- CHECK TOP FOR BOUNCE
function topbounce()
if ball.direction == "ull" then ball.direction = "dll"
elseif ball.direction == "ul" then ball.direction = "dl"
elseif ball.direction == "uul" then ball.direction = "ddl"
elseif ball.direction == "u" then ball.direction = "d"
elseif ball.direction == "uur" then ball.direction = "ddr"
elseif ball.direction == "ur" then ball.direction = "dr"
elseif ball.direction == "urr" then ball.direction = "drr"
end
end
end
------ UPDATE ------
function love.update(dt)
if ball.cooldown > 0 then ball.cooldown = ball.cooldown - 1 end
-- Player movement
if love.keyboard.isDown("right") and player.x <= (width - player.width) then
player.x = player.x + (dt * player.speed)
elseif love.keyboard.isDown("left") and player.x >= 0 then
player.x = player.x - (dt * player.speed)
elseif love.keyboard.isDown("r") then
ball.load()
end
-- Hitbox for player
if ball.y >= player.y and ball.y <= height and ball.x >= player.x and
ball.x <= (player.x + player.width) then
if ball.x >= player.x and ball.x < (player.x + 10) then
ball.direction = "ull"
elseif ball.x >= (player.x + 10) and ball.x < (player.x + 20) then
ball.direction = "ul"
elseif ball.x >= (player.x + 20) and ball.x < (player.x + 30) then
ball.direction = "uul"
elseif ball.x >= (player.x + 30) and ball.x < (player.x + 40) then
ball.direction = "u"
elseif ball.x >= (player.x + 40) and ball.x < (player.x + 50) then
ball.direction = "uur"
elseif ball.x >= (player.x + 50) and ball.x < (player.x + 60) then
ball.direction = "ur"
elseif ball.x >= (player.x + 60) and ball.x < (player.x + 70) then
ball.direction = "urr"
end
love.audio.play(bounce)
end
-- Hitbox for blocks
for i,v in ipairs(blocks.draw) do
if ball.y <= (v.y + v.height) and ball.y >= v.y then
if ball.x <= (v.x + v.width) and ball.x >= v.x then
topbounce()
love.audio.play(hit)
table.remove(blocks.draw, i)
player.points = player.points + 1
end
end
end
-- Bounces ball off walls
if (ball.x <= 0) or (ball.x >= width) then
if ball.direction == "uur" then ball.direction = "uul"
elseif ball.direction == "ur" then ball.direction = "ul"
elseif ball.direction == "urr" then ball.direction = "ull"
elseif ball.direction == "drr" then ball.direction = "dll"
elseif ball.direction == "dr" then ball.direction = "dl"
elseif ball.direction == "ddr" then ball.direction = "ddl"
elseif ball.direction == "ddl" then ball.direction = "ddr"
elseif ball.direction == "dl" then ball.direction = "dr"
elseif ball.direction == "dll" then ball.direction = "drr"
elseif ball.direction == "ull" then ball.direction = "urr"
elseif ball.direction == "ul" then ball.direction = "ur"
elseif ball.direction == "uul" then ball.direction = "uur"
end
love.audio.play(bounce)
end
-- Bounce ball off ceiling
if ball.y <= 0 then topbounce() end
-- Move ball
if ball.cooldown == 0 then
if ball.direction == "u" then
ball.y = ball.y - 2 * (dt * ball.speed)
elseif ball.direction == "uur" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x + 1 * (dt * ball.speed)
elseif ball.direction == "ur" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "urr" then
ball.y = ball.y - 1 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "drr" then
ball.y = ball.y + 1 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "dr" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "ddr" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x + 1 * (dt * ball.speed)
elseif ball.direction == "d" then
ball.y = ball.y + 2 * (dt * ball.speed)
elseif ball.direction == "ddl" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x - 1 * (dt * ball.speed)
elseif ball.direction == "dl" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "dll" then
ball.y = ball.y + 1 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "ull" then
ball.y = ball.y - 1 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "ul" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "uul" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x - 1 * (dt * ball.speed)
end
end
if ball.y >= height then
love.audio.play(loss)
player.lives = player.lives - 1; ball.load()
end
if player.lives < 0 then
love.graphics.print("GAME OVER", width/2, height/2)
love.load()
end
end
------ DRAW ------
function love.draw()
-- Cooldown
if ball.cooldown > 0 then
love.graphics.print("Get ready!", width/2, height/2)
end
-- Points/Lives
love.graphics.print("Lives: " .. player.lives, 10, height/3)
love.graphics.print("Points: " .. player.points, 10, height/3 + 20)
-- Draw player
love.graphics.setColor(255, 255, 255)
love.graphics.rectangle("fill", player.x, player.y, player.width, player.height - 10)
-- Draw blocks
love.graphics.setColor(255, 0, 0)
iter = 0
for _,v in pairs(blocks.draw) do
love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
end
-- Draw ball
love.graphics.setColor(255, 255, 255)
love.graphics.circle("fill", ball.x, ball.y, ball.radius)
end
-
\$\begingroup\$ This game is known as breakout \$\endgroup\$ggorlen– ggorlen2023年09月17日 14:45:46 +00:00Commented Sep 17, 2023 at 14:45
1 Answer 1
1) Instead of this:
if ball.direction == "uur" then ball.direction = "uul"
elseif ball.direction == "ur" then ball.direction = "ul"
elseif ball.direction == "urr" then ball.direction = "ull"
elseif ball.direction == "drr" then ball.direction = "dll"
elseif ball.direction == "dr" then ball.direction = "dl"
elseif ball.direction == "ddr" then ball.direction = "ddl"
elseif ball.direction == "ddl" then ball.direction = "ddr"
elseif ball.direction == "dl" then ball.direction = "dr"
elseif ball.direction == "dll" then ball.direction = "drr"
elseif ball.direction == "ull" then ball.direction = "urr"
elseif ball.direction == "ul" then ball.direction = "ur"
elseif ball.direction == "uul" then ball.direction = "uur"
You may want to use a table with all the values.
local WALL_BOUNCE = {
uur = "uul",
ur = "ul"
-- etc...
}
And in your function just use:
ball.direction = WALL_BOUNCE[ball.direction]
2) Another point:
if ball.direction == "u" then
ball.y = ball.y - 2 * (dt * ball.speed)
elseif ball.direction == "uur" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x + 1 * (dt * ball.speed)
elseif ball.direction == "ur" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "urr" then
ball.y = ball.y - 1 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "drr" then
ball.y = ball.y + 1 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "dr" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x + 2 * (dt * ball.speed)
elseif ball.direction == "ddr" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x + 1 * (dt * ball.speed)
elseif ball.direction == "d" then
ball.y = ball.y + 2 * (dt * ball.speed)
elseif ball.direction == "ddl" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x - 1 * (dt * ball.speed)
elseif ball.direction == "dl" then
ball.y = ball.y + 2 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "dll" then
ball.y = ball.y + 1 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "ull" then
ball.y = ball.y - 1 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "ul" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x - 2 * (dt * ball.speed)
elseif ball.direction == "uul" then
ball.y = ball.y - 2 * (dt * ball.speed)
ball.x = ball.x - 1 * (dt * ball.speed)
end
You can use a table here too, it will have a format
local VELOCITY = {
u = {0, -2},
uur = {1, -2}
-- etc...
-- direction = {velocity_x, velocity_y}
}
local velocity = VELOCITY[ball.direction]
ball.x = ball.x + velocity[1] * dt * ball.speed
ball.y = ball.y + velocity[2] * dt * ball.speed
3) Use local
variables wherever possible. However for a simple project like this one this is not obligatory
-
\$\begingroup\$ What does the local keyword do? I'm guessing it extends the variable scope outside of the function, but as I am new to Lua, I don't really know. And thanks for the tables idea! do you think I could just make one table that stores the directions as keys and the value at index 1 will be the opposite direction and the value at index 2 will be the x and y values \$\endgroup\$coder guy– coder guy2015年08月06日 15:27:45 +00:00Commented Aug 6, 2015 at 15:27
-
\$\begingroup\$ 1) On the contrary, it limits the variable scope to that of the lexical block. It helps you avoid namespace pollution and variable names clashing. \$\endgroup\$Danil Gaponov– Danil Gaponov2015年08月06日 15:29:22 +00:00Commented Aug 6, 2015 at 15:29
-
\$\begingroup\$ 2) You can even write a function that will count direction characters in strings like "uur" and return the resulting velocity. But the table lookup will be faster, though it won't be obvious in this pong example. I think, however, that the explicit table mapping string directions and velocities is the better, than two tables with directions and opposites. It is just more readable. \$\endgroup\$Danil Gaponov– Danil Gaponov2015年08月06日 15:34:50 +00:00Commented Aug 6, 2015 at 15:34
-
\$\begingroup\$ if it keeps variable names from clashing does that mean theres no variable scope in Lua? if there was then why would you have to use local to keep variable names from crashing. Also I don't exactly know what a lexical block is or a namespace. This terminology is confusing a beginner like me haha. \$\endgroup\$coder guy– coder guy2015年08月06日 15:38:43 +00:00Commented Aug 6, 2015 at 15:38
-
\$\begingroup\$ So could i do something like for "u" in ball.direction do y = y-1 end or for "d" in ball.direction y=y+1 \$\endgroup\$coder guy– coder guy2015年08月06日 15:41:10 +00:00Commented Aug 6, 2015 at 15:41