Last active
May 19, 2023 21:02
-
-
Save lzralbu/3d05e49ccc7e59c1426bdbcb9816d526 to your computer and use it in GitHub Desktop.
Lua/LÖVE code for Flat Bird, yet another clone of Flappy Bird but with minimalist art.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--------------------------- | |
-- some useful constants -- | |
--------------------------- | |
local CANVAS_WIDTH = 450 | |
local CANVAS_HEIGHT = 600 | |
local colors = { | |
WHITE = { 1, 1, 1 }, | |
SKY_BLUE = { 135 / 255, 206 / 255, 235 / 255 }, | |
GREEN = { 0, 0.75, 0 }, | |
ORANGE = { 1, 165 / 255, 0 }, | |
BROWN = { 0.58, 0.29, 0 } | |
} | |
local GRAVITY = 120 | |
---------- | |
-- bird -- | |
---------- | |
local bird = {} | |
local function birdInit() | |
bird.width = 25 | |
bird.height = 25 | |
bird.x = CANVAS_WIDTH / 2 - 50 | |
bird.y = (CANVAS_HEIGHT - bird.height) / 2 | |
bird.min_speed = 25 | |
bird.max_speed = 250 | |
bird.speed = bird.min_speed | |
bird.bump_height = 25 | |
bird.color = colors.ORANGE | |
bird.alive = true | |
end | |
local function birdReset() | |
bird.y = (CANVAS_HEIGHT - bird.height) / 2 | |
bird.speed = bird.min_speed | |
bird.alive = true | |
end | |
local function birdUpdate(dt) | |
bird.speed = bird.speed + GRAVITY * dt | |
if bird.speed > bird.max_speed then | |
bird.speed = bird.max_speed | |
end | |
bird.y = bird.y + bird.speed * dt | |
end | |
----------- | |
-- pipes -- | |
----------- | |
local pipes = {} | |
local function pipesInit() | |
pipes.clock = 0 -- how much time(in seconds) has elapsed since the last pipe was generated | |
pipes.gen_rate = 3 -- how much time(in seconds) the game should waiting before generating another pipe | |
end | |
local function pipesReset() | |
pipes.clock = 0 | |
while #pipes > 0 do | |
table.remove(pipes, 1) | |
end | |
end | |
local function pipeCreate() | |
local pipe = {} | |
pipe.height1 = math.random(100, CANVAS_HEIGHT - 250) | |
pipe.empty_space = 100 | |
pipe.width = 50 | |
pipe.height2 = CANVAS_HEIGHT - pipe.height1 - pipe.empty_space | |
pipe.x = CANVAS_WIDTH | |
pipe.y = 0 | |
pipe.speed = -100 | |
pipe.color = colors.GREEN | |
pipe.behind_bird = false | |
return pipe | |
end | |
local function pipesUpdate(dt) | |
pipes.clock = pipes.clock + dt | |
if pipes.clock > pipes.gen_rate then | |
pipes.clock = 0 | |
table.insert(pipes, pipeCreate()) | |
end | |
-- move all the pipes a bit to the left | |
for k, pipe in ipairs(pipes) do | |
pipe.x = pipe.x + pipe.speed * dt | |
end | |
-- count how many pipes are out of screen | |
local dead_pipes_count = 0 | |
for k, pipe in ipairs(pipes) do | |
if pipe.x < -pipe.width then | |
dead_pipes_count = dead_pipes_count + 1 | |
else | |
break | |
end | |
end | |
-- remove each of the first dead_pipes_count pipes | |
for _ = 1, dead_pipes_count do | |
table.remove(pipes, 1) | |
end | |
end | |
---------------- | |
-- scoreboard -- | |
---------------- | |
local scoreboard = {} | |
local function scoreboardInit() | |
scoreboard.current_score = 0 | |
scoreboard.highest_score = 0 | |
scoreboard.color = colors.WHITE | |
scoreboard.font = love.graphics.newFont(36) | |
scoreboard.x = (CANVAS_WIDTH - scoreboard.font:getWidth('0')) / 2 | |
scoreboard.y = 30 | |
end | |
local function scoreboardReset() | |
scoreboard.current_score = 0 | |
scoreboard.x = (CANVAS_WIDTH - scoreboard.font:getWidth('0')) / 2 | |
end | |
local function scoreboardUpdate(dt) | |
local bird_center_x = bird.x + bird.width / 2 | |
for k, pipe in ipairs(pipes) do | |
local pipe_center_x = pipe.x + pipe.width / 2 | |
if pipe_center_x < bird_center_x and not pipe.behind_bird then | |
scoreboard.current_score = scoreboard.current_score + 1 | |
if scoreboard.current_score > scoreboard.highest_score then | |
scoreboard.highest_score = scoreboard.current_score | |
end | |
pipe.behind_bird = true | |
end | |
end | |
scoreboard.x = (CANVAS_WIDTH - | |
scoreboard.font:getWidth( | |
tostring(scoreboard.current_score) | |
)) / 2 | |
end | |
----------- | |
-- floor -- | |
----------- | |
local floor = {} | |
local function floorInit() | |
floor.width = CANVAS_WIDTH | |
floor.height = 50 | |
floor.x = 0 | |
floor.y = CANVAS_HEIGHT - floor.height | |
floor.color = colors.BROWN | |
end | |
--------------------------- | |
-- some helper functions -- | |
--------------------------- | |
local game_over_clock = 0 | |
local game_over_duration = 3 | |
local function gameInit() | |
birdInit() | |
pipesInit() | |
scoreboardInit() | |
floorInit() | |
end | |
local function gameReset() | |
birdReset() | |
pipesReset() | |
scoreboardReset() | |
game_over_clock = 0 | |
end | |
-- detect intersection between two rectangles | |
local function hasIntersection(x1, y1, w1, h1, x2, y2, w2, h2) | |
return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1 | |
end | |
-------------------- | |
-- LÖVE callbacks -- | |
-------------------- | |
function love.load() | |
math.randomseed(os.time()) | |
gameInit() | |
love.graphics.setFont(scoreboard.font) | |
end | |
function love.update(dt) | |
if bird.alive then | |
birdUpdate(dt) | |
pipesUpdate(dt) | |
-- check collision between bird and floor | |
if hasIntersection( | |
bird.x, bird.y, bird.width, bird.height, floor.x, floor.y, | |
floor.width, floor.height | |
) then | |
bird.alive = false | |
end | |
-- check collision between bird and pipes | |
for k, pipe in ipairs(pipes) do | |
if hasIntersection( | |
bird.x, bird.y, bird.width, bird.height, pipe.x, pipe.y, | |
pipe.width, pipe.height1 | |
) or hasIntersection( | |
bird.x, bird.y, bird.width, bird.height, pipe.x, | |
pipe.y + pipe.height1 + pipe.empty_space, pipe.width, | |
pipe.height2 | |
) then | |
bird.alive = false | |
end | |
end | |
if not bird.alive then | |
return | |
end | |
scoreboardUpdate(dt) | |
else | |
game_over_clock = game_over_clock + dt | |
if game_over_clock > game_over_duration then | |
gameReset() | |
end | |
end | |
end | |
function love.draw() | |
-- draw sky | |
love.graphics.clear(colors.SKY_BLUE) | |
-- draw bird | |
love.graphics.setColor(bird.color) | |
love.graphics.rectangle('fill', bird.x, bird.y, bird.width, bird.height) | |
-- draw pipes | |
for k, pipe in ipairs(pipes) do | |
love.graphics.setColor(pipe.color) | |
love.graphics | |
.rectangle('fill', pipe.x, pipe.y, pipe.width, pipe.height1) | |
love.graphics.rectangle( | |
'fill', pipe.x, pipe.y + pipe.height1 + pipe.empty_space, | |
pipe.width, pipe.height2 | |
) | |
end | |
-- draw scoreboard | |
love.graphics.setColor(scoreboard.color) | |
love.graphics.print( | |
tostring(scoreboard.current_score), scoreboard.x, scoreboard.y | |
) | |
-- draw floor | |
love.graphics.setColor(floor.color) | |
love.graphics.rectangle('fill', floor.x, floor.y, floor.width, floor.height) | |
-- draw game over message if bird is dead | |
if not bird.alive then | |
love.graphics.setColor(scoreboard.color) | |
local line1 = "GAME OVER" | |
love.graphics.print( | |
line1, (CANVAS_WIDTH - scoreboard.font:getWidth(line1)) / 2, | |
CANVAS_HEIGHT / 3 | |
) | |
local line2 = "Best: " .. tostring(scoreboard.highest_score) | |
love.graphics.print( | |
line2, (CANVAS_WIDTH - scoreboard.font:getWidth(line2)) / 2, | |
CANVAS_HEIGHT / 3 + scoreboard.font:getHeight() | |
) | |
end | |
end | |
function love.keypressed(key, scancode, isrepeat) | |
if (scancode == 'space' or scancode == 'up' or scancode == 'w') and | |
bird.alive then | |
bird.y = bird.y - bird.bump_height | |
bird.speed = bird.min_speed | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment