summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcursed22bc <admin@pixeldawn.org>2026-02-27 13:37:36 +0200
committercursed22bc <admin@pixeldawn.org>2026-02-27 13:37:36 +0200
commitf8da1a788c68ff58d53a173ad197ef3623dc6cff (patch)
tree63d417cac1782968a556675acbc8a506504d9069
parentc528c8fa70a1399efc746be6468bd7d36abe1ca2 (diff)
base setup
-rw-r--r--.vscode/settings.json5
-rw-r--r--camera.lua118
-rw-r--r--fonts.lua27
-rw-r--r--main.lua90
-rw-r--r--shaders/smooth_camera.glsl5
5 files changed, 245 insertions, 0 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..a59b386
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "Lua.diagnostics.globals": [
+ "love"
+ ]
+} \ No newline at end of file
diff --git a/camera.lua b/camera.lua
new file mode 100644
index 0000000..752b32e
--- /dev/null
+++ b/camera.lua
@@ -0,0 +1,118 @@
+local Camera = {}
+Camera.__index = Camera
+
+function Camera:new(target, width, height, mouseFollow, screenToWorldScale)
+ local self = setmetatable({}, Camera)
+ self.target = target
+ self.width = width
+ self.height = height
+ self.mouseFollow = mouseFollow or false
+ self.mouseRadius = math.min(self.width, self.height) * 0.35
+ self.mouseWeight = 0.1
+ self.screenToWorldScale = screenToWorldScale
+
+ self.x = target.x + (target.width) / 2 - self.width / 2
+ self.y = target.y + (target.height) / 2 - self.height / 2
+
+ self.vx = 0
+ self.vy = 0
+
+ self.stiffness = 50
+ self.damping = 2 * math.sqrt(self.stiffness)
+
+ self.limits = {
+ maxX = nil,
+ minX = nil,
+ maxY = nil,
+ minY = nil,
+ }
+
+ return self
+end
+
+function Camera:setTarget(target)
+ self.target = target
+end
+
+function Camera:setLimits(minX, maxX, minY, maxY)
+ self.limits.minX = minX
+ self.limits.maxX = maxX
+ self.limits.minY = minY
+ self.limits.maxY = maxY
+end
+
+function Camera:update(dt)
+ if not self.target then return end
+ local cx = self.target.x + (self.target.width or 0) / 2
+ local cy = self.target.y + (self.target.height or 0) / 2
+ local targetX = cx - self.width / 2
+ local targetY = cy - self.height / 2
+
+ if self.mouseFollow then
+ local sw = love.graphics.getWidth()
+ local sh = love.graphics.getHeight()
+ local mx, my = love.mouse.getPosition()
+
+ local offsetX = (mx - sw / 2) * self.screenToWorldScale
+ local offsetY = (my - sh / 2) * self.screenToWorldScale
+
+ local dist = offsetX * offsetX + offsetY * offsetY
+ if dist > self.mouseRadius * self.mouseRadius then
+ local inv = self.mouseRadius / math.sqrt(dist)
+ offsetX = offsetX * inv
+ offsetY = offsetY * inv
+ end
+
+ targetX = targetX + offsetX * self.mouseWeight
+ targetY = targetY + offsetY * self.mouseWeight
+ end
+
+ local accelerationX = (targetX - self.x) * self.stiffness - self.vx * self.damping
+ local accelerationY = (targetY - self.y) * self.stiffness - self.vy * self.damping
+
+ self.vx = self.vx + accelerationX * dt
+ self.vy = self.vy + accelerationY * dt
+
+ self.x = self.x + self.vx * dt
+ self.y = self.y + self.vy * dt
+
+ local lim = self.limits
+ if lim.minX ~= nil then self.x = math.max(lim.minX, self.x) end
+ if lim.maxX ~= nil then self.x = math.min(self.x, lim.maxX - self.width) end
+ if lim.minY ~= nil then self.y = math.max(lim.minY, self.y) end
+ if lim.maxY ~= nil then self.y = math.min(self.y, lim.maxY - self.height) end
+end
+
+function Camera:set(padding)
+ padding = padding or 0
+ love.graphics.push()
+
+ local canvasW = love.graphics.getCanvas():getWidth()
+ local canvasH = love.graphics.getCanvas():getHeight()
+
+ local viewW = self.width + padding * 2
+ local viewH = self.height + padding * 2
+
+ local scaleX = canvasW / viewW
+ local scaleY = canvasH / viewH
+
+ local scale = math.floor(math.min(scaleX, scaleY))
+
+ love.graphics.scale(scale)
+
+ local snapX = math.floor(self.x)
+ local snapY = math.floor(self.y)
+ love.graphics.translate(-(snapX - padding), -(snapY - padding))
+end
+
+function Camera:unset()
+ love.graphics.pop()
+end
+
+function Camera:getSubPixelOffset()
+ local dx = self.x - math.floor(self.x)
+ local dy = self.y - math.floor(self.y)
+ return dx, dy
+end
+
+return Camera \ No newline at end of file
diff --git a/fonts.lua b/fonts.lua
new file mode 100644
index 0000000..905b2a3
--- /dev/null
+++ b/fonts.lua
@@ -0,0 +1,27 @@
+---@diagnostic disable: undefined-global
+-- Font loader for pixel-perfect 64x64 virtual screen
+local M = {}
+
+local FONT_PATH = "assets/font/font.otf"
+local DEFAULT_SIZE = 16
+
+local function setPixelFilter(font)
+ font:setFilter("nearest", "nearest")
+ return font
+end
+
+function M.load()
+ M.default = setPixelFilter(love.graphics.newFont(FONT_PATH, DEFAULT_SIZE))
+ return M.default
+end
+
+function M.get(size)
+ if not size then return M.default end
+ M.sizes = M.sizes or {}
+ if not M.sizes[size] then
+ M.sizes[size] = setPixelFilter(love.graphics.newFont(FONT_PATH, size))
+ end
+ return M.sizes[size]
+end
+
+return M
diff --git a/main.lua b/main.lua
new file mode 100644
index 0000000..66ef333
--- /dev/null
+++ b/main.lua
@@ -0,0 +1,90 @@
+local VIRTUAL_WIDTH, VIRTUAL_HEIGHT = 16*10*3, 9*10*3
+local CANVAS_PADDING = 6
+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
+
+--TODO: separate to components
+local cameraModule = require("camera")
+local camera = nil
+-- end
+
+local fonts = require("fonts")
+
+
+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 love.load()
+ camera = cameraModule:new({x = 0, y = 0, width = VIRTUAL_WIDTH, height = VIRTUAL_HEIGHT}, VIRTUAL_WIDTH, VIRTUAL_HEIGHT, true, WORLD_TO_CANVAS)
+ 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)
+
+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)
+ if not camera then return end
+ camera:update(dt)
+end
+
+function love.keypressed(key, scancode, isrepeat)
+ if key == "f11" then
+ love.window.setFullscreen(not love.window.getFullscreen(), "desktop")
+ end
+end
+
+function love.draw()
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear()
+ love.graphics.push()
+ if camera then
+ camera:set()
+ love.graphics.print("FPS: " .. love.timer.getFPS(), 10, 10)
+ love.graphics.pop()
+
+ camera:unset()
+ end
+
+ love.graphics.setCanvas()
+
+ love.graphics.clear()
+ local drawX = offsetX - (CANVAS_PADDING * finalScale) / 2
+ local drawY = offsetY - (CANVAS_PADDING * finalScale) / 2
+ love.graphics.draw(canvas, math.floor(drawX), math.floor(drawY), 0, finalScale, finalScale)
+end
+
+return nil \ No newline at end of file
diff --git a/shaders/smooth_camera.glsl b/shaders/smooth_camera.glsl
new file mode 100644
index 0000000..7c8e822
--- /dev/null
+++ b/shaders/smooth_camera.glsl
@@ -0,0 +1,5 @@
+extern vec2 offset;
+
+vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ return Texel(tex, texture_coords + offset);
+}