5
\$\begingroup\$

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
\$\endgroup\$
3
\$\begingroup\$

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

\$\endgroup\$
  • \$\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 Aug 6 '15 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 Aug 6 '15 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 Aug 6 '15 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 Aug 6 '15 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 Aug 6 '15 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.