local VIRTUAL_WIDTH, VIRTUAL_HEIGHT = 16*10*3, 9*10*3 local CANVAS_PADDING = 6 DEBUG = true 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 cameraModule = require("camera") local camera = nil local fonts = require("fonts") local currentState = "game" local currentMapPath = "assets/maps/tilemap.lua" local currentTilesetPath = "assets/maps/tileset.png" local world = nil local states = { game = {}, menu = {}, settings = {} } 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, currentTilesetPath) 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) end function states.game.update(dt) if camera then camera:update(dt) end 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 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) 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 == "space" or key == "up" or key == "w") and not isrepeat then local player = world and world:getPlayer() if player then player:jump() end 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 uvOffsetX = subDx * WORLD_TO_CANVAS / CANVAS_WIDTH local uvOffsetY = subDy * WORLD_TO_CANVAS / CANVAS_HEIGHT if 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 smoothCameraShader then love.graphics.setShader() end end return nil