summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcursed22bc <admin@pixeldawn.org>2026-03-11 12:50:56 +0200
committercursed22bc <admin@pixeldawn.org>2026-03-11 12:50:56 +0200
commit8c5a91acb224312ea511c7ff502c44bb9fe7452b (patch)
tree194a2aee7f92fabbab79cc0ab64f0d2989b7d55e
parentcd432860828a84aca93852a09cf05fc0e6294bab (diff)
sfx and other polish
-rw-r--r--.vscode/settings.json5
-rw-r--r--enemy.lua15
-rw-r--r--main.lua19
-rw-r--r--player.lua27
-rw-r--r--sfx.lua105
-rw-r--r--world.lua8
6 files changed, 167 insertions, 12 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index a59b386..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "Lua.diagnostics.globals": [
- "love"
- ]
-} \ No newline at end of file
diff --git a/enemy.lua b/enemy.lua
index 152cadf..5c84a49 100644
--- a/enemy.lua
+++ b/enemy.lua
@@ -6,9 +6,12 @@ setmetatable(Enemy, { __index = Entity })
function Enemy.new(spawnX, spawnY, width, height, physicsWidth, physicsHeight)
local self = setmetatable(Entity:new(spawnX or 0, spawnY or 0, width or 16, height or 16, physicsWidth or width or 8, physicsHeight or height or 8), Enemy)
self.isEnemy = true
+ self.texture = love.graphics.newImage("assets/misc/enemy.png")
+ self.texture:setFilter("nearest", "nearest")
self.direction = math.random(1, 2) == 1 and 1 or -1
+ self.facing = self.direction
self.speed = 20
- self.turnDelay = 0.35
+ self.turnDelay = 1
self.turning = false
self.turnTimer = 0
return self
@@ -59,6 +62,8 @@ function Enemy:patrolPlatform(dt)
self.turnTimer = self.turnDelay
end
+ self.facing = self.direction
+
if self.body then
local _, vy = self.body:getLinearVelocity()
self.body:setLinearVelocity(self.speed * self.direction, vy)
@@ -70,8 +75,12 @@ function Enemy:update(dt)
end
function Enemy:draw()
- --bug: texture is drawn shifted by half width to the left
- love.graphics.draw(self.texture, self.x + self.physicsWidth / 2, self.y)
+ local tw = self.texture:getWidth()
+ local th = self.texture:getHeight()
+ local sx = (self.facing == -1) and -1 or 1
+ local cx = self.x + self.physicsWidth
+ local cy = self.y + self.physicsHeight /2
+ love.graphics.draw(self.texture, cx, cy, 0, sx, 1, tw / 2, th / 2)
end
return Enemy \ No newline at end of file
diff --git a/main.lua b/main.lua
index 19c350d..6f2ee53 100644
--- a/main.lua
+++ b/main.lua
@@ -23,6 +23,7 @@ local cameraModule = require("camera")
local camera = nil
local fonts = require("fonts")
local HUD = require("hud")
+local sfx = require("sfx")
local hud = nil
local previousState = nil
@@ -88,6 +89,8 @@ function states.game.load()
gameFinishPhase = nil
gameFinishTimer = 0
irisRadius = 0
+ sfx.stop("UI_loop_song")
+ sfx.loop("game_ambient_loop")
end
function states.game.update(dt)
@@ -167,12 +170,17 @@ function states.menu.load()
menuTime = 0
love.mouse.setVisible(true)
menuHover = nil
+ sfx.stop("game_ambient_loop")
+ sfx.stop("running")
+ sfx.setLiquidEffect(false)
+ sfx.loop("UI_loop_song")
end
function states.menu.update(dt)
menuTime = menuTime + dt
local mx, my = love.mouse.getPosition()
local cx, cy = screenToCanvas(mx, my)
+ local prevHover = menuHover
menuHover = nil
for _, btn in ipairs(menuItems) do
if cx >= btn.x and cx <= btn.x + btn.w and cy >= btn.y and cy <= btn.y + btn.h then
@@ -180,6 +188,9 @@ function states.menu.update(dt)
break
end
end
+ if menuHover and menuHover ~= prevHover then
+ sfx.play("UI")
+ end
end
function states.menu.draw()
@@ -267,6 +278,8 @@ function love.load()
canvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
canvas:setFilter("nearest", "nearest")
+ sfx.load()
+
local ok, shader = pcall(love.graphics.newShader, "shaders/smooth_camera.glsl")
smoothCameraShader = ok and shader or nil
if not smoothCameraShader then
@@ -290,6 +303,10 @@ function love.load()
local state = states[currentState]
if state and state.load then state.load() end
+
+ if currentState == "menu" then
+ sfx.loop("UI_loop_song")
+ end
end
function love.resize(w, h)
@@ -325,10 +342,12 @@ function love.keypressed(key, scancode, isrepeat)
previousState = currentState
currentState = "hud"
love.mouse.setVisible(true)
+ sfx.play("HUD")
elseif currentState == "hud" then
currentState = previousState or "game"
previousState = nil
love.mouse.setVisible(false)
+ sfx.play("HUD")
end
end
if (key == "space" or key == "up" or key == "w") and not isrepeat then
diff --git a/player.lua b/player.lua
index 6e73691..42e56ad 100644
--- a/player.lua
+++ b/player.lua
@@ -1,5 +1,6 @@
local Entity = require("entity")
local Animation = require("animation")
+local sfx = require("sfx")
local Player = {}
Player.__index = Player
@@ -27,7 +28,7 @@ function Player.new(world, spawnX, spawnY)
self:enablePhysics(world, "dynamic")
self.fixture:setFriction(0)
- self.doubleJump = true
+ self.doubleJump = false
self.jumpsUsed = 0
self.animations = {
idle = Animation.new("assets/player/idle.png", 16, 16, 0.6),
@@ -162,7 +163,9 @@ function Player:update(dt)
self.body:applyForce(move * SWIM_SPEED, moveY * SWIM_SPEED)
elseif onFloor or onWaterSurface then
self.body:setLinearDamping(0)
- self.jumpsUsed = 0
+ if not prevOnFloor then
+ self.jumpsUsed = 0
+ end
if move ~= 0 then
self.lastFacing = move
self.body:setLinearVelocity(move * MOVE_SPEED, vy)
@@ -203,6 +206,19 @@ function Player:update(dt)
end
end
+ if self.state ~= prevState then
+ if self.state == "running" then
+ sfx.loop("running")
+ elseif prevState == "running" then
+ sfx.stop("running")
+ end
+ if self.state == "ground_hit" then
+ sfx.play("land")
+ end
+ end
+
+ sfx.setLiquidEffect(self.isInLiquid)
+
self.wasOnFloor = onFloor
self.wasInLiquid = self.isInLiquid
@@ -225,6 +241,8 @@ function Player:die(nx, ny)
self.isDead = true
self.state = "dead"
self.respawnTimer = 4
+ sfx.stop("running")
+ sfx.play("die")
local len = math.sqrt(nx * nx + ny * ny)
if len < 0.01 then
@@ -260,7 +278,8 @@ function Player:jump()
return false
end
- local maxJumps = self.doubleJump and 1 or 0
+ self.doubleJump = (self.pickups["dbljump"] or 0) > 0
+ local maxJumps = self.doubleJump and 2 or 1
if self.jumpsUsed >= maxJumps then
return false
@@ -272,6 +291,8 @@ function Player:jump()
self.body:setLinearVelocity(self.body:getLinearVelocity(), JUMP_FORCE)
+ sfx.play("jump")
+
self.state = "jumping"
self.animations.going_up:reset()
diff --git a/sfx.lua b/sfx.lua
new file mode 100644
index 0000000..c7e7377
--- /dev/null
+++ b/sfx.lua
@@ -0,0 +1,105 @@
+local sfx = {}
+
+sfx.sources = {}
+sfx.volumes = {}
+sfx.liquidActive = false
+
+local LIQUID_FILTER = { type = "lowpass", volume = 0.8, highgain = 0.15 }
+
+local MUSIC_SOURCES = {
+ game_ambient_loop = true,
+ UI_loop_song = true,
+}
+
+local DEFAULT_VOLUMES = {
+ game_ambient_loop = 0.3,
+ UI_loop_song = 0.5,
+ running = 0.35,
+ die = 0.7,
+ dive = 0.6,
+ jump = 0.5,
+ land = 0.5,
+ HUD = 0.5,
+ pickup = 0.5,
+ UI = 0.5,
+}
+
+function sfx.load()
+ local dir = "assets/sfx"
+ local ok, items = pcall(love.filesystem.getDirectoryItems, dir)
+ if not ok or not items then return end
+
+ for _, filename in ipairs(items) do
+ local ext = filename:match("%.(%w+)$")
+ if ext == "ogg" or ext == "mp3" or ext == "wav" then
+ local name = filename:gsub("%.[^.]+$", "")
+ local sourceType = MUSIC_SOURCES[name] and "stream" or "static"
+ local success, src = pcall(love.audio.newSource, dir .. "/" .. filename, sourceType)
+ if success and src then
+ local vol = DEFAULT_VOLUMES[name] or 0.5
+ src:setVolume(vol)
+ sfx.sources[name] = src
+ sfx.volumes[name] = vol
+ end
+ end
+ end
+end
+
+function sfx.play(name)
+ local src = sfx.sources[name]
+ if not src then return end
+ src:stop()
+ src:setLooping(false)
+ if sfx.liquidActive and not MUSIC_SOURCES[name] then
+ pcall(src.setFilter, src, LIQUID_FILTER)
+ end
+ src:play()
+end
+
+function sfx.loop(name)
+ local src = sfx.sources[name]
+ if not src then return end
+ if src:isPlaying() then return end
+ src:setLooping(true)
+ src:play()
+end
+
+function sfx.stop(name)
+ local src = sfx.sources[name]
+ if not src then return end
+ src:stop()
+end
+
+function sfx.isPlaying(name)
+ local src = sfx.sources[name]
+ return src and src:isPlaying()
+end
+
+function sfx.setVolume(name, vol)
+ local src = sfx.sources[name]
+ if not src then return end
+ sfx.volumes[name] = vol
+ src:setVolume(vol)
+end
+
+function sfx.setLiquidEffect(active)
+ if sfx.liquidActive == active then return end
+ sfx.liquidActive = active
+ for name, src in pairs(sfx.sources) do
+ if not MUSIC_SOURCES[name] then
+ if active then
+ pcall(src.setFilter, src, LIQUID_FILTER)
+ else
+ pcall(src.setFilter, src)
+ end
+ end
+ end
+end
+
+function sfx.stopAll()
+ for _, src in pairs(sfx.sources) do
+ src:stop()
+ end
+end
+
+return sfx
diff --git a/world.lua b/world.lua
index ff7a1ad..58dae39 100644
--- a/world.lua
+++ b/world.lua
@@ -6,6 +6,7 @@ local Textbox = require("textbox")
local Enemy = require("enemy")
local BloodParticle = require("bloodParticle")
local DustSystem = require("dustParticle")
+local sfx = require("sfx")
local World = {}
World.__index = World
@@ -236,6 +237,7 @@ end
function World:handlePickup(pickup, player)
if pickup and player and pickup.isPickup and self:_isPlayerLike(player) then
+ if not pickup.collected then sfx.play("pickup") end
pickup:pickup(self, player)
end
end
@@ -250,7 +252,7 @@ function World:handleDoor(door, player)
if keyCount >= needed then
self.doorOpened = true
self.playerTextbox:show(
- "The door opens! You escaped!",
+ "Bye Bye!",
{ player.x + player.width / 2, player.y - 20, "center" },
"write",
{ life = 5, fontSize = 9, centeredText = true, wrapToFit = true }
@@ -321,11 +323,15 @@ function World:_onBeginContact(udA, udB, nx, ny, contact)
end
if type(udA) == "table" and udA.type == "water" and self:_isTrackedEntity(udB) then
+ local wasInWater = (self.waterContacts[udB] or 0) > 0
self.waterContacts[udB] = (self.waterContacts[udB] or 0) + 1
doWaterSplash(udA, udB)
+ if not wasInWater and self:_isPlayerLike(udB) then sfx.play("dive") end
elseif type(udB) == "table" and udB.type == "water" and self:_isTrackedEntity(udA) then
+ local wasInWater = (self.waterContacts[udA] or 0) > 0
self.waterContacts[udA] = (self.waterContacts[udA] or 0) + 1
doWaterSplash(udB, udA)
+ if not wasInWater and self:_isPlayerLike(udA) then sfx.play("dive") end
end
local playerEntity, hazardNx, hazardNy