local VIRTUAL_WIDTH, VIRTUAL_HEIGHT = 16*10*5, 9*10*5 local CANVAS_PADDING = 6 DEBUG = false local CANVAS_WIDTH = VIRTUAL_WIDTH + CANVAS_PADDING local CANVAS_HEIGHT = VIRTUAL_HEIGHT + CANVAS_PADDING local WORLD_TO_CANVAS = 3 local scale = 1 local finalScale = 1 local offsetX, offsetY = 0, 0 local dpiScale = 1 local canvas = nil local smoothCameraShader = nil local shaderEnabled = true local cameraModule = require("camera") local camera = nil local fonts = require("fonts") local HUD = require("hud") local hud = nil local previousState = nil local currentState = "game" local currentMapPath = "assets/maps/tilemap.lua" local currentTilesets = { tileset = { path = "assets/maps/tileset.png", tilewidth = 16, tileheight = 16 }, misc = { path = "assets/maps/simples_pimples.png", tilewidth = 16, tileheight = 16 }, } local world = nil local states = { game = {}, menu = {}, settings = {}, hud = {}, } local function recalcScale(w, h) dpiScale = (love.window.getDPIScale and love.window.getDPIScale()) or 1 scale = math.max(1, math.floor(math.min(w / VIRTUAL_WIDTH, h / VIRTUAL_HEIGHT) / dpiScale)) finalScale = scale * dpiScale offsetX = math.floor((w - VIRTUAL_WIDTH * finalScale) / 2) offsetY = math.floor((h - VIRTUAL_HEIGHT * finalScale) / 2) end function states.game.load() local World = require("world") world = World:new() world:load(currentMapPath, currentTilesets) local target = world:getPlayer() or { x = 0, y = 0, width = 16, height = 16 } camera = cameraModule:new(target, VIRTUAL_WIDTH/2, VIRTUAL_HEIGHT/2, true, WORLD_TO_CANVAS) world:setCamera(camera) hud = HUD:new() end function states.game.update(dt) if world then world:update(dt) end end function states.game.draw() if camera then camera:set() end if world then world:draw() end if camera then camera:unset() end end function states.menu.load() camera = nil world = nil end function states.menu.update(dt) end function states.menu.draw() love.graphics.print("Menu (state machine ready)", 10, 10) end function states.settings.load() camera = nil world = nil end function states.settings.update(dt) end function states.settings.draw() love.graphics.print("Settings", 10, 10) end function states.hud.update(dt) if hud then local w, h = love.graphics.getWidth(), love.graphics.getHeight() hud:update(dt, w, h, finalScale) end end function states.hud.draw() if camera then camera:set() end if world then world:draw() end if camera then camera:unset() end end function love.load() love.graphics.setDefaultFilter("nearest", "nearest") love.window.setTitle("Openformer") fonts.load() love.graphics.setFont(fonts.default) canvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) canvas:setFilter("nearest", "nearest") local ok, shader = pcall(love.graphics.newShader, "shaders/smooth_camera.glsl") smoothCameraShader = ok and shader or nil if not smoothCameraShader then print("Warning: smooth_camera.glsl not loaded, using fallback (no sub-pixel offset)") end local w, h = love.graphics.getWidth(), love.graphics.getHeight() recalcScale(w, h) love.mouse.setVisible(false) local state = states[currentState] if state and state.load then state.load() end end function love.resize(w, h) recalcScale(w, h) if canvas then canvas:release() end canvas = love.graphics.newCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) canvas:setFilter("nearest", "nearest") end function love.update(dt) local state = states[currentState] if state and state.update then state.update(dt) end end function love.keypressed(key, scancode, isrepeat) if key == "f11" then love.window.setFullscreen(not love.window.getFullscreen(), "desktop") end if key == "f1" and DEBUG then shaderEnabled = not shaderEnabled end if key == "tab" and not isrepeat then if currentState == "game" then previousState = currentState currentState = "hud" love.mouse.setVisible(true) elseif currentState == "hud" then currentState = previousState or "game" previousState = nil love.mouse.setVisible(false) end end if (key == "space" or key == "up" or key == "w") and not isrepeat then if currentState == "game" then local player = world and world:getPlayer() if player then player:jump() end end end end function love.mousepressed(x, y, button) if currentState == "hud" and hud then hud:mousepressed(x, y, button) end end function love.draw() love.graphics.setCanvas(canvas) love.graphics.clear() love.graphics.push() local state = states[currentState] if state and state.draw then state.draw() end love.graphics.pop() love.graphics.setCanvas() love.graphics.clear() local drawX = offsetX - (CANVAS_PADDING * finalScale) / 2 local drawY = offsetY - (CANVAS_PADDING * finalScale) / 2 local subDx, subDy = 0, 0 if world and world.camera and world.camera.getSubPixelOffset then subDx, subDy = world.camera:getSubPixelOffset() end local camScale = (world and world.camera and world.camera._lastScale) or 2 local uvOffsetX = subDx * camScale / CANVAS_WIDTH local uvOffsetY = subDy * camScale / CANVAS_HEIGHT if shaderEnabled and smoothCameraShader and (subDx ~= 0 or subDy ~= 0) then smoothCameraShader:send("offset", { uvOffsetX, uvOffsetY }) love.graphics.setShader(smoothCameraShader) end love.graphics.draw(canvas, math.floor(drawX), math.floor(drawY), 0, finalScale, finalScale) if shaderEnabled and smoothCameraShader then love.graphics.setShader() end if currentState == "hud" and hud then hud:draw() end if DEBUG then local fps = love.timer.getFPS() local shaderLoaded = smoothCameraShader ~= nil local shaderApplied = shaderEnabled and shaderLoaded and (subDx ~= 0 or subDy ~= 0) local shaderName = shaderLoaded and "smooth_camera.glsl" or "none (fallback)" local shaderState = not shaderLoaded and "unavailable" or not shaderEnabled and "disabled [F1]" or shaderApplied and "active" or "idle" local shaderLine = "shader: " .. shaderName .. " (" .. shaderState .. ")" love.graphics.setColor(0, 0, 0, 0.5) love.graphics.rectangle("fill", 4, 4, 280, 36) love.graphics.setColor(1, 1, 0, 1) love.graphics.print("FPS: " .. fps, 8, 8) love.graphics.print(shaderLine, 8, 22) love.graphics.setColor(1, 1, 1, 1) end end return nil