local Game
local Player
local Enemy
local Bullet
local Collision
local Particles
local Audio
local Input
local game
local player
local enemies
local bullets
local particles
local score
local lives
local gameState
local dt_accumulator
local images = {}
local sounds = {}
local fonts = {}
local Config = {
window = {
width = 800,
height = 600,
title = "Space Shooter",
},
player = {
speed = 200,
fireRate = 0.2,
},
enemy = {
speed = 50,
spawnRate = 2.0,
},
bullet = {
speed = 400,
},
}
function love.load()
love.window.setMode(Config.window.width, Config.window.height)
love.window.setTitle(Config.window.title)
love.graphics.setDefaultFilter("nearest", "nearest")
love.graphics.setBackgroundColor(0.1, 0.1, 0.2)
game = Game:new()
sounds.shoot = love.audio.newSource("assets/sounds/shoot.wav", "static")
sounds.explosion = love.audio.newSource("assets/sounds/explosion.wav", "static")
sounds.background = love.audio.newSource("assets/sounds/music.mp3", "stream")
sounds.background:setLooping(true)
sounds.background:play()
love.math.setRandomSeed(os.time())
game:reset()
end
function love.update(dt)
if gameState ~= "playing" then
return
end
game:update(dt)
game:spawnEnemies(dt)
game:checkCollisions()
game:cleanupEntities()
if love.keyboard.isDown("escape") then
love.audio.stop()
love.event.quit()
end
end
function love.draw()
game:draw()
love.graphics.setColor(1, 1, 1)
love.graphics.print("Score: " .. score, 10, 10)
love.graphics.print("Lives: " .. lives, 10, 30)
love.graphics.print("FPS: " .. love.timer.getFPS(), Config.window.width - 80, 10)
if gameState == "gameover" then
love.graphics.setColor(1, 0, 0)
love.graphics.printf("GAME OVER", 0, Config.window.height / 2 - 20, Config.window.width, "center")
love.graphics.printf("Press R to restart", 0, Config.window.height / 2 + 10, Config.window.width, "center")
end
if gameState == "paused" then
love.graphics.setColor(1, 1, 0)
love.graphics.printf("PAUSED", 0, Config.window.height / 2, Config.window.width, "center")
end
end
function love.keypressed(key)
if key == "space" then
if player then
player:shoot()
end
end
if key == "r" then
if gameState == "gameover" then
game:reset()
gameState = "playing"
end
end
if key == "p" then
if gameState == "playing" then
gameState = "paused"
elseif gameState == "paused" then
gameState = "playing"
end
end
end
function love.keyreleased(key)
if key == "space" then
if player then
player.shooting = false
end
end
end
function love.mousepressed(x, y, button)
if button == 1 then
if player then
player:shoot()
end
end
end
Game = {}
Game.__index = Game
function Game:new()
local instance = {
state = "playing",
spawnTimer = 0,
enemySpawnRate = Config.enemy.spawnRate,
}
setmetatable(instance, Game)
return instance
end
function Game:reset()
gameState = "playing"
score = 0
lives = 3
player = Player:new(Config.window.width / 2, Config.window.height - 50)
enemies = {}
bullets = {}
particles = {}
self.spawnTimer = 0
end
function Game:update(dt)
if player then
player:update(dt)
end
for i, enemy in ipairs(enemies) do
enemy:update(dt)
if love.math.random() < 0.01 then
enemy:shoot()
end
end
for i, bullet in ipairs(bullets) do
bullet:update(dt)
end
for i, particle in ipairs(particles) do
particle:update(dt)
end
end
function Game:draw()
if player then
player:draw()
end
for i, enemy in ipairs(enemies) do
enemy:draw()
end
for i, bullet in ipairs(bullets) do
bullet:draw()
end
for i, particle in ipairs(particles) do
love.graphics.setColor(particle.color)
love.graphics.circle("fill", particle.x, particle.y, particle.size)
end
end
function Game:gameOver()
gameState = "gameover"
if sounds.explosion then
sounds.explosion:play()
end
local highScore = love.filesystem.read("highscore.txt")
if not highScore or score > tonumber(highScore) then
love.filesystem.write("highscore.txt", tostring(score))
end
end
Player = {}
Player.__index = Player
function Player:new(x, y)
local instance = {
x = x,
y = y,
width = 32,
height = 32,
speed = Config.player.speed,
health = 100,
shooting = false,
fireTimer = 0,
fireRate = Config.player.fireRate,
}
setmetatable(instance, Player)
return instance
end
function Player:update(dt)
if love.keyboard.isDown("left") or love.keyboard.isDown("a") then
self.x = self.x - self.speed * dt
end
if love.keyboard.isDown("right") or love.keyboard.isDown("d") then
self.x = self.x + self.speed * dt
end
if love.keyboard.isDown("up") or love.keyboard.isDown("w") then
self.y = self.y - self.speed * dt
end
if love.keyboard.isDown("down") or love.keyboard.isDown("s") then
self.y = self.y + self.speed * dt
end
self.x = math.max(0, math.min(self.x, Config.window.width - self.width))
self.y = math.max(0, math.min(self.y, Config.window.height - self.height))
self.fireTimer = self.fireTimer - dt
end
function Player:draw()
love.graphics.setColor(0, 1, 0)
love.graphics.rectangle("fill", self.x, self.y, self.width, self.height)
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", self.x, self.y - 10, self.width, 5)
love.graphics.setColor(0, 1, 0)
love.graphics.rectangle("fill", self.x, self.y - 10, self.width * (self.health / 100), 5)
end
function Player:shoot()
if self.fireTimer > 0 then
return
end
local bullet = Bullet:new(self.x + self.width / 2, self.y, 0, -1, "player")
table.insert(bullets, bullet)
self.fireTimer = self.fireRate
if sounds.shoot then
sounds.shoot:play()
end
end
function Player:takeDamage(amount)
self.health = self.health - amount
if self.health <= 0 then
lives = lives - 1
if lives <= 0 then
game:gameOver()
else
self.health = 100
self.x = Config.window.width / 2
self.y = Config.window.height - 50
end
end
end
Enemy = {}
Enemy.__index = Enemy
function Enemy:new(x, y)
local instance = {
x = x,
y = y,
width = 32,
height = 32,
speed = Config.enemy.speed,
health = 50,
direction = 1,
}
setmetatable(instance, Enemy)
return instance
end
function Enemy:update(dt)
self.y = self.y + self.speed * dt
self.x = self.x + math.sin(self.y * 0.01) * self.speed * 0.5 * dt
if self.x < 0 or self.x > Config.window.width - self.width then
self.direction = -self.direction
end
if self.y > Config.window.height then
self.dead = true
end
end
function Enemy:draw()
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", self.x, self.y, self.width, self.height)
love.graphics.setColor(0, 1, 0)
love.graphics.rectangle("fill", self.x, self.y - 10, self.width * (self.health / 50), 5)
end
function Enemy:shoot()
local bullet = Bullet:new(self.x + self.width / 2, self.y + self.height, 0, 1, "enemy")
table.insert(bullets, bullet)
if sounds.shoot then
sounds.shoot:play()
end
end
Bullet = {}
Bullet.__index = Bullet
function Bullet:new(x, y, dx, dy, owner)
local instance = {
x = x,
y = y,
dx = dx,
dy = dy,
speed = Config.bullet.speed,
owner = owner,
radius = 5,
dead = false,
}
setmetatable(instance, Bullet)
return instance
end
function Bullet:update(dt)
self.x = self.x + self.dx * self.speed * dt
self.y = self.y + self.dy * self.speed * dt
if self.y < 0 or self.y > Config.window.height then
self.dead = true
end
end
function Bullet:draw()
if self.owner == "player" then
love.graphics.setColor(1, 1, 0)
else
love.graphics.setColor(1, 0.5, 0)
end
love.graphics.circle("fill", self.x, self.y, self.radius)
end
Collision = {}
function Collision.checkAABB(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
function Collision.checkCircle(x1, y1, r1, x2, y2, r2)
local dx = x1 - x2
local dy = y1 - y2
local distance = math.sqrt(dx * dx + dy * dy)
return distance < r1 + r2
end
function Game:checkCollisions()
for i, bullet in ipairs(bullets) do
if bullet.owner == "player" then
for j, enemy in ipairs(enemies) do
if Collision.checkCircle(bullet.x, bullet.y, bullet.radius, enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, enemy.width / 2) then
bullet.dead = true
enemy.health = enemy.health - 25
if enemy.health <= 0 then
enemy.dead = true
score = score + 100
self:createExplosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2)
if sounds.explosion then
sounds.explosion:play()
end
end
end
end
end
end
for i, bullet in ipairs(bullets) do
if bullet.owner == "enemy" and player then
if Collision.checkCircle(bullet.x, bullet.y, bullet.radius, player.x + player.width / 2, player.y + player.height / 2, player.width / 2) then
bullet.dead = true
player:takeDamage(10)
self:createExplosion(player.x + player.width / 2, player.y + player.height / 2)
end
end
end
for i, enemy in ipairs(enemies) do
if player and Collision.checkAABB(enemy.x, enemy.y, enemy.width, enemy.height, player.x, player.y, player.width, player.height) then
enemy.dead = true
player:takeDamage(25)
self:createExplosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2)
end
end
end
function Game:cleanupEntities()
for i = #bullets, 1, -1 do
if bullets[i].dead then
table.remove(bullets, i)
end
end
for i = #enemies, 1, -1 do
if enemies[i].dead then
table.remove(enemies, i)
end
end
for i = #particles, 1, -1 do
if particles[i].dead then
table.remove(particles, i)
end
end
end
function Game:spawnEnemies(dt)
self.spawnTimer = self.spawnTimer + dt
if self.spawnTimer >= self.enemySpawnRate then
self.spawnTimer = 0
local x = love.math.random(0, Config.window.width - 32)
local enemy = Enemy:new(x, -32)
table.insert(enemies, enemy)
end
end
function Game:createExplosion(x, y)
for i = 1, 20 do
local angle = love.math.random() * math.pi * 2
local speed = love.math.random(50, 150)
local particle = {
x = x,
y = y,
vx = math.cos(angle) * speed,
vy = math.sin(angle) * speed,
size = love.math.random(2, 5),
color = { love.math.random(), love.math.random(), love.math.random(), 1 },
life = 1.0,
dead = false,
update = function(self, dt)
self.x = self.x + self.vx * dt
self.y = self.y + self.vy * dt
self.life = self.life - dt
if self.life <= 0 then
self.dead = true
end
end,
}
table.insert(particles, particle)
end
end
local function clamp(value, min, max)
return math.max(min, math.min(value, max))
end
local function lerp(a, b, t)
return a + (b - a) * t
end
local function distance(x1, y1, x2, y2)
local dx = x2 - x1
local dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
end
local function angle(x1, y1, x2, y2)
return math.atan2(y2 - y1, x2 - x1)
end
local function drawDebugInfo()
love.graphics.setColor(1, 1, 1)
love.graphics.print("Enemies: " .. #enemies, 10, 50)
love.graphics.print("Bullets: " .. #bullets, 10, 70)
love.graphics.print("Particles: " .. #particles, 10, 90)
end
local function logGameState()
print(string.format("Score: %d, Lives: %d, State: %s", score, lives, gameState))
end