443 lines
12 KiB
Lua
443 lines
12 KiB
Lua
|
-- ===================================================== --
|
||
|
-- ____ _ _ ____ _
|
||
|
-- | _ \(_)_ __ ___ __ _ _ __ _ __ | | ___ | _ \(_)__________ _
|
||
|
-- | |_) | | '_ \ / _ \/ _` | '_ \| '_ \| |/ _ \ | |_) | |_ /_ / _` |
|
||
|
-- | __/| | | | | __/ (_| | |_) | |_) | | __/ | __/| |/ / / / (_| |
|
||
|
-- |_| |_|_| |_|\___|\__,_| .__/| .__/|_|\___| |_| |_/___/___\__,_|
|
||
|
-- |_| |_|
|
||
|
-- Made for the the Linux Game Jam March 2017
|
||
|
-- Requires love2d 0.10 (Super Toast)
|
||
|
--
|
||
|
-- A simple game about protecting pizza from yourself
|
||
|
--
|
||
|
-- This is a propaganga piece made to show pineapples
|
||
|
-- ruin pizza. Don't deny it, you know it to be true
|
||
|
--
|
||
|
-- Also, it's in space
|
||
|
--
|
||
|
-- The game quits when the pineapple touches a pizza
|
||
|
-- as what's the point anymore if pizza has been ruined
|
||
|
--
|
||
|
-- Controls : WASD and Space, P to pause and ESC to exit
|
||
|
--
|
||
|
-- ===================================================== --
|
||
|
--
|
||
|
-- Images cliparts.co
|
||
|
-- BG spacetelescope.org/images/potw1006a
|
||
|
-- Music toneden.io/mi77ermusic/post/stardust
|
||
|
-- Sound freesound.org/people/greenvwbeetle/sounds/244654
|
||
|
--
|
||
|
-- ===================================================== --
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Settings
|
||
|
|
||
|
-- Whole scene properties
|
||
|
kppm = 64 -- Pixels per metre
|
||
|
kwidth = 0
|
||
|
kheight = 0
|
||
|
|
||
|
-- player properties
|
||
|
kplayerThrust = 30000
|
||
|
kplayerMaxV = 350
|
||
|
kplayerFriction = 5
|
||
|
|
||
|
-- Bullet info
|
||
|
knumBullets = 3
|
||
|
kbullRadius = 10
|
||
|
kbulletReturnForce = 300
|
||
|
kbulletMaxV = 400
|
||
|
|
||
|
-- Enemy Info
|
||
|
knumEnemies = 50
|
||
|
kenemySpawnStartRate = 2
|
||
|
|
||
|
-- Music volume
|
||
|
kmusicVolume = 0.05
|
||
|
|
||
|
-- Settings
|
||
|
kdifficultyFactor = 1.02
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Objects
|
||
|
|
||
|
-- The physics world
|
||
|
world = {}
|
||
|
|
||
|
-- Arena Stuff
|
||
|
bounds = {}
|
||
|
bounds.ground = {}
|
||
|
bounds.lwall = {}
|
||
|
bounds.rwall = {}
|
||
|
bounds.ceil = {}
|
||
|
|
||
|
-- The player
|
||
|
ktypePlayer = {}
|
||
|
player = {}
|
||
|
player.type = ktypePlayer
|
||
|
|
||
|
-- Bullets
|
||
|
ktypeBullet = {}
|
||
|
bullets = {}
|
||
|
for i=1,knumBullets do
|
||
|
table.insert(bullets, {})
|
||
|
end
|
||
|
for key,bull in pairs(bullets) do
|
||
|
bull.projectile = false
|
||
|
bull.returning = false
|
||
|
bull.type = ktypeBullet
|
||
|
end
|
||
|
|
||
|
-- Enemies
|
||
|
ktypeEnemy = {}
|
||
|
enemySpawnRate = kenemySpawnStartRate
|
||
|
enemies = {}
|
||
|
for i=1,knumEnemies do
|
||
|
table.insert(enemies, {})
|
||
|
end
|
||
|
for key,enemy in pairs(enemies) do
|
||
|
enemy.alive =false
|
||
|
enemy.type = ktypeEnemy
|
||
|
end
|
||
|
|
||
|
-- Score
|
||
|
score = 0
|
||
|
paused = true
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Load
|
||
|
function love.load()
|
||
|
|
||
|
-- Store some variables
|
||
|
kwidth, kheight = love.graphics.getDimensions()
|
||
|
|
||
|
-- Create images
|
||
|
imageBG = love.graphics.newImage("space.png")
|
||
|
imagePlayer = love.graphics.newImage("pineapple.png")
|
||
|
imageBullet = love.graphics.newImage("fireball.png")
|
||
|
imageEnemy = love.graphics.newImage("pizza.png")
|
||
|
|
||
|
-- Load sound
|
||
|
music = love.audio.newSource("Stardust.ogg")
|
||
|
music:setLooping(true)
|
||
|
music:setVolume(kmusicVolume)
|
||
|
music:play()
|
||
|
|
||
|
soundPop = love.audio.newSource("pop.ogg","static")
|
||
|
|
||
|
-- Create a physics world
|
||
|
love.physics.setMeter(kppm)
|
||
|
world = love.physics.newWorld( 0 , 0, true )
|
||
|
world:setCallbacks(beginContact, endConact, preSolve, postSolve)
|
||
|
|
||
|
-- Create the ground (and walls)
|
||
|
bounds.ground.body = love.physics.newBody(world, kwidth/2, kheight)
|
||
|
bounds.ground.shape = love.physics.newRectangleShape( kwidth, 1 )
|
||
|
bounds.lwall.body = love.physics.newBody(world, 0, kheight/2)
|
||
|
bounds.lwall.shape = love.physics.newRectangleShape( 1, kheight )
|
||
|
bounds.rwall.body = love.physics.newBody(world, kwidth, kheight/2)
|
||
|
bounds.rwall.shape = love.physics.newRectangleShape( 1, kheight )
|
||
|
bounds.ceil.body = love.physics.newBody(world, kwidth/2, 0)
|
||
|
bounds.ceil.shape = love.physics.newRectangleShape( kwidth, 1 )
|
||
|
|
||
|
for key,bound in pairs(bounds) do
|
||
|
bound.fixture = love.physics.newFixture( bound.body, bound.shape )
|
||
|
bound.fixture:setCategory(1)
|
||
|
end
|
||
|
|
||
|
-- Create the player
|
||
|
player.body = love.physics.newBody(world, kwidth/2, kheight-40, "dynamic")
|
||
|
player.shape = love.physics.newRectangleShape(0, 0, 20, 40)
|
||
|
player.fixture = love.physics.newFixture(player.body, player.shape )
|
||
|
player.body:setFixedRotation(true)
|
||
|
player.body:setLinearDamping(kplayerFriction)
|
||
|
player.fixture:setCategory(2)
|
||
|
player.fixture:setUserData(player)
|
||
|
|
||
|
-- Create the bullets
|
||
|
for key,bull in pairs(bullets) do
|
||
|
x, y = player.body:getPosition()
|
||
|
x, y = x + math.random(-30,30), y + math.random(-30,30)
|
||
|
bull.body = love.physics.newBody(world, x, y, "dynamic")
|
||
|
bull.shape = love.physics.newCircleShape(kbullRadius)
|
||
|
bull.fixture = love.physics.newFixture(bull.body, bull.shape)
|
||
|
bull.fixture:setCategory(3)
|
||
|
bull.fixture:setMask(1,2,3) -- bullets don't hit wall, the player, or eachother
|
||
|
bull.fixture:setUserData(bull)
|
||
|
bull.image = imageBullet
|
||
|
end
|
||
|
|
||
|
-- Create the enemies
|
||
|
enemyTimer = love.timer.getTime()
|
||
|
enemyScale = 1
|
||
|
for key,enemy in pairs(enemies) do
|
||
|
enemy.body = love.physics.newBody(world, kwidth/2, -100, "dynamic")
|
||
|
enemy.shape = love.physics.newCircleShape(10 * enemyScale)
|
||
|
enemy.scale = enemyScale
|
||
|
enemy.fixture = love.physics.newFixture(enemy.body, enemy.shape)
|
||
|
enemy.fixture:setCategory(4)
|
||
|
enemy.fixture:setMask(1,4) -- enemies don't hit wall or eachother
|
||
|
enemy.fixture:setUserData(enemy)
|
||
|
enemyScale = enemyScale + 0.1
|
||
|
end
|
||
|
|
||
|
-- Load in save file
|
||
|
local savedatachunk = love.filesystem.load( "savedata" )
|
||
|
if savedatachunk then
|
||
|
savedata = savedatachunk()
|
||
|
else
|
||
|
savedata = {}
|
||
|
savedata.highscore = 0
|
||
|
savedata.fails = 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Update
|
||
|
function love.update(dt)
|
||
|
if paused then
|
||
|
music:pause()
|
||
|
return
|
||
|
else
|
||
|
music:resume()
|
||
|
end
|
||
|
|
||
|
-- Keyboard controls for player
|
||
|
local dx, dy = 0, 0
|
||
|
if love.keyboard.isDown("left") then
|
||
|
dx = dx - kplayerThrust
|
||
|
end
|
||
|
if love.keyboard.isDown("right") then
|
||
|
dx = dx + kplayerThrust
|
||
|
end
|
||
|
if love.keyboard.isDown("up") then
|
||
|
dy = dy - kplayerThrust
|
||
|
end
|
||
|
if love.keyboard.isDown("down") then
|
||
|
dy = dy + kplayerThrust
|
||
|
end
|
||
|
player.body:applyForce( dx*dt, dy*dt )
|
||
|
|
||
|
-- Limit player max velocity
|
||
|
local vx, vy = player.body:getLinearVelocity()
|
||
|
local vel = math.sqrt(vx*vx + vy*vy)
|
||
|
if vel > kplayerMaxV then
|
||
|
vx = vx/vel * kplayerMaxV
|
||
|
vy = vy/vel * kplayerMaxV
|
||
|
player.body:setLinearVelocity(vx,vy)
|
||
|
end
|
||
|
|
||
|
-- handle the enemies
|
||
|
local spawnEnemy = love.timer.getTime() - enemyTimer > enemySpawnRate
|
||
|
for key,enemy in pairs(enemies) do
|
||
|
-- alive enemies should slowly drop
|
||
|
if enemy.alive then
|
||
|
local x, y = enemy.body:getPosition()
|
||
|
if y > kheight then
|
||
|
enemy.alive = false
|
||
|
end
|
||
|
else
|
||
|
enemy.body:setPosition(-100,-100) -- innactive enemies off screen
|
||
|
if spawnEnemy then
|
||
|
spawnEnemy = false
|
||
|
enemyTimer = love.timer.getTime() -- reset the timer
|
||
|
enemy.body:setPosition( math.random(0,kwidth), 0 )
|
||
|
enemy.body:setLinearVelocity(math.random(-20,20),math.random(50,200))
|
||
|
enemy.alive = true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Sort out the bullets
|
||
|
local px, py = player.body:getPosition()
|
||
|
for key,bull in pairs(bullets) do
|
||
|
local bx, by = bull.body:getPosition()
|
||
|
-- non projectiles orbit the player
|
||
|
if not bull.projectile then
|
||
|
-- Get the delta to the player
|
||
|
local dx, dy = px-bx, py-by
|
||
|
bull.body:applyForce( dx * kbulletReturnForce * dt, dy * kbulletReturnForce * dt )
|
||
|
|
||
|
-- Account for returned bullets
|
||
|
local dmag = math.sqrt(dx*dx + dy*dy)
|
||
|
if dmag < bull.shape:getRadius()*3 then
|
||
|
bull.returning = false
|
||
|
end
|
||
|
|
||
|
-- limit vel
|
||
|
local vx, vy = bull.body:getLinearVelocity()
|
||
|
local vel = math.sqrt(vx*vx + vy*vy)
|
||
|
if vel > kbulletMaxV then
|
||
|
vx = vx/vel * kbulletMaxV
|
||
|
vy = vy/vel * kbulletMaxV
|
||
|
bull.body:setLinearVelocity(vx,vy)
|
||
|
end
|
||
|
-- otherwise they fly and come back
|
||
|
else
|
||
|
if by < 0 then
|
||
|
bull.projectile = false
|
||
|
bull.returning = true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Update the physics world last
|
||
|
world:update(dt)
|
||
|
end
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Key presses
|
||
|
function love.keypressed( key, sc, isrepeat )
|
||
|
|
||
|
-- Fire
|
||
|
if key == "space" then
|
||
|
-- find a bullet
|
||
|
for key,bull in pairs(bullets) do
|
||
|
if not bull.projectile and not bull.returning then
|
||
|
bull.projectile = true
|
||
|
bull.body:setPosition(player.body:getPosition())
|
||
|
bull.body:setLinearVelocity( 0, -1000 )
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Pause
|
||
|
elseif key == "p" then
|
||
|
paused = not paused
|
||
|
|
||
|
-- Quit
|
||
|
elseif key == "escape" then
|
||
|
love.event.quit()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Draw
|
||
|
function love.draw()
|
||
|
|
||
|
-- Draw the BG
|
||
|
love.graphics.setColor( 128,128,128,256 )
|
||
|
love.graphics.draw( imageBG, kwidth/2, kheight/2, love.timer.getTime()/10, 0.8, 0.8, 600, 600)
|
||
|
|
||
|
-- If we're paused
|
||
|
if paused then
|
||
|
love.graphics.setColor( 256,256,256,256 )
|
||
|
love.graphics.print( " Paused!\n[P] to unpause", kwidth/2, kheight/2, 0, 1, 1, 40 )
|
||
|
end
|
||
|
|
||
|
-- Draw the player
|
||
|
local x,y = player.body:getPosition()
|
||
|
love.graphics.setColor( 256,256,256,256 )
|
||
|
love.graphics.draw( imagePlayer, x, y+5, 0, 0.2, 0.2, 72,170 )
|
||
|
|
||
|
-- Draw the enemies
|
||
|
love.graphics.setColor( 256,256,256,256 )
|
||
|
for key,enemy in pairs(enemies) do
|
||
|
if enemy.alive then
|
||
|
love.graphics.draw( imageEnemy,
|
||
|
enemy.body:getX(),
|
||
|
enemy.body:getY(),
|
||
|
enemy.scale-1,
|
||
|
0.2*enemy.scale, 0.2*enemy.scale,
|
||
|
60, 60 )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Draw the bullets
|
||
|
for key,bull in pairs(bullets) do
|
||
|
if bull.projectile then
|
||
|
love.graphics.setColor( 256,256,256,256 )
|
||
|
elseif bull.returning then
|
||
|
love.graphics.setColor( 128,128,128,64 )
|
||
|
else
|
||
|
love.graphics.setColor( 200,200,200,180 )
|
||
|
end
|
||
|
love.graphics.draw( bull.image,
|
||
|
bull.body:getX(), bull.body:getY(),
|
||
|
love.timer.getTime()*4,
|
||
|
0.2, 0.2,
|
||
|
60, 60 )
|
||
|
end
|
||
|
|
||
|
-- Draw the UI
|
||
|
love.graphics.setColor( 256,256,256,256 )
|
||
|
love.graphics.print( string.format("Pizzas Saved: %i\nHigh Score: %i\nRuined Pizzas: %i",
|
||
|
score,
|
||
|
savedata.highscore,
|
||
|
savedata.fails), 1, 1 )
|
||
|
end
|
||
|
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Quit
|
||
|
function love.quit()
|
||
|
-- Save out the data
|
||
|
local highest = score
|
||
|
if highest < savedata.highscore then highest = savedata.highscore end
|
||
|
love.filesystem.write( "savedata", string.format("return {\nhighscore = %i,\nfails = %i\n}",
|
||
|
highest,
|
||
|
savedata.fails) )
|
||
|
end
|
||
|
|
||
|
-- ===================================================== --
|
||
|
-- Physics callbacks
|
||
|
|
||
|
-- ie if a and be are type x and y
|
||
|
-- if so, returns them in correct order
|
||
|
function abxy( a, b, x, y )
|
||
|
if not a or not b then return end
|
||
|
local at, bt = a.type, b.type
|
||
|
if not at or not bt then return
|
||
|
elseif ( at == x and bt == y) then
|
||
|
return a, b
|
||
|
elseif ( bt == x and at == y) then
|
||
|
return b, a
|
||
|
else
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- called at start of overlap (always)
|
||
|
function beginContact(first, second, collision)
|
||
|
local fd, sd = first:getUserData(), second:getUserData()
|
||
|
|
||
|
-- Enemies and bullets
|
||
|
local bullet, enemy = abxy( fd, sd, ktypeBullet, ktypeEnemy )
|
||
|
if bullet and enemy then
|
||
|
if bullet.projectile and enemy.alive then
|
||
|
enemy.alive = false
|
||
|
bullet.projectile = false
|
||
|
bullet.returning = true
|
||
|
score = score + 1
|
||
|
soundPop:play()
|
||
|
enemySpawnRate = enemySpawnRate/kdifficultyFactor -- speed up spawns
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local enemy, player = abxy( fd, sd, ktypeEnemy, ktypePlayer )
|
||
|
if enemy and player then
|
||
|
savedata.fails = savedata.fails + 1
|
||
|
love.event.quit()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- called after overlap (only if overlap occured)
|
||
|
function endContact(first, second, collision)
|
||
|
end
|
||
|
|
||
|
-- called before the collision gets resolved
|
||
|
function preSolve(first, second, collision)
|
||
|
local fd, sd = first:getUserData(), second:getUserData()
|
||
|
|
||
|
-- Don't do any physics collisions for bullets and enemies
|
||
|
local bullet, enemy = abxy( fd, sd, ktypeBullet, ktypeEnemy )
|
||
|
if bullet and enemy then
|
||
|
collision:setEnabled(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- called after collision resolved, with details
|
||
|
function postSolve(first, second, collision, normalimpulse, tangentimpulse)
|
||
|
-- Not needed yet
|
||
|
end
|