From b744faa2e42ed37459fe3edb69c1149146233e5b Mon Sep 17 00:00:00 2001 From: ne_mene Date: Sun, 8 Mar 2026 22:21:42 +0100 Subject: let there be light --- src/ecs.lua | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/events.lua | 19 ++++++ src/init.lua | 47 +++++++++++++++ src/input.lua | 75 +++++++++++++++++++++++ src/objs/player.lua | 32 ++++++++++ src/textures.lua | 26 ++++++++ src/utils.lua | 39 ++++++++++++ 7 files changed, 407 insertions(+) create mode 100644 src/ecs.lua create mode 100644 src/events.lua create mode 100644 src/init.lua create mode 100644 src/input.lua create mode 100644 src/objs/player.lua create mode 100644 src/textures.lua create mode 100644 src/utils.lua (limited to 'src') diff --git a/src/ecs.lua b/src/ecs.lua new file mode 100644 index 0000000..1df5421 --- /dev/null +++ b/src/ecs.lua @@ -0,0 +1,169 @@ + +local current_scene = nil +local compfuncs = {} + +function TAGCOMP(_) end + +function new_scene() + local newscene = { + compmask = {}, + entities = {}, + entities_size = 0, + + free_entids = {}, + free_entids_size = 0, + + killset = {}, + killq = {}, + killq_size = 0, + + -- Events + on_draw = new_event(), + on_update = new_event(), + + comp_removeq = {}, + comp_entq = {}, + comp_removeq_size = 0, + } + for comp, _ in pairs(compfuncs) do + newscene.compmask[comp] = { + sparse = {}, + dense = {}, + size = 0, + } + end + return newscene +end + +function set_scene(newscene) + current_scene = newscene +end + +function get_current_scene() + return current_scene +end + +function register_comp(name, func) + assert(compfuncs[name] == nil, "Component '"..name.."' is already registered.") + + compfuncs[name] = func +end + +function add_comp(ent, comp, ...) + assert(current_scene, "No scene up.") + assert(compfuncs[comp], "Unknown component '"..tostring(comp).."'") + assert(current_scene.entities[ent.id], "Entity "..tostring(ent.id).." doesn't exist.") + + local mask = current_scene.compmask[comp] + assert(not mask.sparse[ent.id], "Entity "..tostring(ent.id).." already has component of type '"..comp.."'.") + + mask.size = mask.size + 1 + mask.sparse[ent.id] = mask.size + mask.dense[mask.size] = ent.id + + compfuncs[comp](current_scene.entities[ent.id], ...) +end + +local function remove_comp(ent, comp) + assert(current_scene, "No scene up.") + assert(compfuncs[comp], "Unknown component '"..tostring(comp).."'") + assert(current_scene.entities[ent.id], "Entity "..tostring(ent.id).." doesn't exist.") + + local mask = current_scene.compmask[comp] + assert(mask.sparse[ent.id], "Entity "..tostring(ent.id).." does not have component of type '"..comp.."'.") + + local index = mask.sparse[ent.id] + local lastid = mask.dense[mask.size] + + mask.sparse[ent.id] = nil + mask.sparse[lastid] = index + + mask.dense[index] = mask.dense[mask.size] + mask.dense[mask.size] = nil + mask.size = mask.size - 1 +end + +function run_system(comp, func, ...) + assert(current_scene, "No scene up.") + assert(compfuncs[comp], "Unknown component '"..tostring(comp).."'") + + for _, entid in ipairs(current_scene.compmask[comp].dense) do + func(current_scene.entities[entid], ...) + end +end + +function queue_entity_kill(ent) + assert(current_scene, "No scene up.") + assert(current_scene.entities[ent.id], "Entity "..tostring(ent.id).." doesn't exist.") + + if current_scene.killset[ent.id] then + return + end + current_scene.killset[ent.id] = true + current_scene.killq_size = current_scene.killq_size + 1 + current_scene.killq[current_scene.killq_size] = ent.id +end + +function has_comp(ent, comp) + assert(current_scene, "No scene up.") + assert(compfuncs[comp], "Unknown component '"..tostring(comp).."'") + assert(current_scene.entities[ent.id], "Entity "..tostring(ent.id).." doesn't exist.") + + return current_scene.compmask[comp].sparse[ent.id] ~= nil +end + +function queue_remove_comp(ent, comp) + assert(current_scene, "No scene up.") + assert(compfuncs[comp], "Unknown component '"..tostring(comp).."'") + + if not has_comp(ent, comp) then return end + + current_scene.comp_removeq_size = current_scene.comp_removeq_size + 1 + current_scene.comp_removeq[current_scene.comp_removeq_size] = comp + current_scene.comp_entq[current_scene.comp_removeq_size] = ent +end + +function flush_scene() + assert(current_scene, "No scene up.") + + for i=1, current_scene.comp_removeq_size do + remove_comp(current_scene.comp_entq[i], current_scene.comp_removeq[i]) + end + current_scene.comp_removeq_size = 0 + + for i=current_scene.killq_size, 1, -1 do + local ent = current_scene.entities[current_scene.killq[i]] + for comp, _ in pairs(current_scene.compmask) do + if has_comp(ent, comp) then + remove_comp(ent, comp) + end + end + + current_scene.killset[ent.id] = nil + + current_scene.free_entids_size = current_scene.free_entids_size + 1 + current_scene.free_entids[current_scene.free_entids_size] = ent.id + end + current_scene.killq_size = 0 +end + +function new_entity() + assert(current_scene, "No scene up.") + + if current_scene.free_entids_size > 0 then + local entid = current_scene.free_entids[current_scene.free_entids_size] + current_scene.free_entids_size = current_scene.free_entids_size - 1 + + local ent = current_scene.entities[entid] + for key in pairs(ent) do + ent[key] = nil + end + ent.id = entid + return ent + end + local newid = current_scene.entities_size + 1 + current_scene.entities[newid] = {id = newid} + current_scene.entities_size = newid + + return current_scene.entities[newid] +end diff --git a/src/events.lua b/src/events.lua new file mode 100644 index 0000000..2171ade --- /dev/null +++ b/src/events.lua @@ -0,0 +1,19 @@ +function new_event() + return { + systems = {}, + compnames = {}, + size = 0, + } +end + +function event_bind(event, compname, system) + event.size = event.size + 1 + event.systems[event.size] = system + event.compnames[event.size] = compname +end + +function fire_event(event, ...) + for i=1, event.size do + run_system(event.compnames[i], event.systems[i], ...) + end +end diff --git a/src/init.lua b/src/init.lua new file mode 100644 index 0000000..c18f175 --- /dev/null +++ b/src/init.lua @@ -0,0 +1,47 @@ +require "src.events" +require "src.ecs" +require "src.utils" +require "src.input" +require "src.textures" + +SCR_WIDTH = 320 +SCR_HEIGHT = 180 +WindowScale = 1 + +register_input("Left", {{"key", "left"}, {"key", "a"}}) +register_input("Right", {{"key", "right"}, {"key", "d"}}) +register_input("Down", {{"key", "down"}, {"key", "s"}}) +register_input("Up", {{"key", "up"}, {"key", "w"}}) + +register_input("Right_Click", {{"mouse", 1}}) +register_input("Left_Click", {{"mouse", 2}}) + +local lg = love.graphics +local lf = love.filesystem + +local function load_dir(path) + local files = lf.getDirectoryItems(path) + + for _, file in ipairs(files) do + local filepath = path.."/"..file + + if lf.getInfo(filepath).type == "directory" then + load_dir(filepath) + else + lf.load(filepath)(); + end + end +end + +function main_init() + lg.setDefaultFilter("nearest", "nearest") + Viewport = lg.newCanvas(SCR_WIDTH, SCR_HEIGHT) + + load_textures_from() + + love.window.setMode(SCR_WIDTH, SCR_HEIGHT, {fullscreen = false}) + + load_dir("src/objs") + load_dir("src/scenes") +end + diff --git a/src/input.lua b/src/input.lua new file mode 100644 index 0000000..8029be2 --- /dev/null +++ b/src/input.lua @@ -0,0 +1,75 @@ +local lk = love.keyboard +local lm = love.mouse + +local inputs = {} +local keyEvents = {} +local mouseEvents = {} + +---@param triggers table pair like this {type, keycode/name} +function register_input(name, triggers) + inputs[name] = triggers +end + +function input_direction(left, right, up, down) + return bton(is_input_pressed(right)) - bton(is_input_pressed(left)), + bton(is_input_pressed(down)) - bton(is_input_pressed(up)) +end + +function is_input_just_pressed(name) + local inp = inputs[name] + local type + local code + + for _, trig in ipairs(inp) do + type = trig[1] + code = trig[2] + + if type == "mouse" then + if mouseEvents[code] ~= nil then return true end + end + if type == "key" then + if keyEvents[code] ~= nil then return true end + end + end +end + +function is_input_pressed(name) + local inp = inputs[name] + local type + local code + + for _, trig in ipairs(inp) do + type = trig[1] + code = trig[2] + + if type == "mouse" then + if lm.isDown(code) then return true end + end + if type == "key" then + if lk.isDown(code) then return true end + end + end +end + +function love.keypressed(key) + keyEvents[key] = true +end + +function love.mousepressed(x, y, btn) + mouseEvents[btn] = true +end + +function get_mouse_pos() + -- TODO: Fix mouse position relative to games canvas + local scrw, scrh = love.graphics.getDimensions() + return math.floor(lm.getX() / scrw * SCR_WIDTH), math.floor(lm.getY() / scrh * SCR_HEIGHT) +end + +function input_step() + for key in pairs(keyEvents) do + keyEvents[key] = nil + end + for key in pairs(mouseEvents) do + mouseEvents[key] = nil + end +end diff --git a/src/objs/player.lua b/src/objs/player.lua new file mode 100644 index 0000000..229dac9 --- /dev/null +++ b/src/objs/player.lua @@ -0,0 +1,32 @@ +PLAYER_SPEED = 100 + +register_comp("Body", function (ent, x, y) + ent.x = x + ent.y = y + ent.vx = 0 + ent.vy = 0 +end) + +register_comp("Player", TAGCOMP) + +function body_sys(ent, dt) + ent.x = ent.x + ent.vx * dt + ent.y = ent.y + ent.vy * dt +end + +function draw_sys(ent) + love.graphics.circle("fill", ent.x, ent.y, 8) +end + +function player_movement_sys(player, dt) + local inpx, inpy = input_direction("Left", "Right", "Up", "Down") + inpx, inpy = normalize(inpx, inpy) + player.vx = dlerp(player.vx, inpx * PLAYER_SPEED, 25 * dt) + player.vy = dlerp(player.vy, inpy * PLAYER_SPEED, 25 * dt) +end + +function new_player(x, y) + local ent = new_entity() + add_comp(ent, "Body", x, y) + add_comp(ent, "Player") +end diff --git a/src/textures.lua b/src/textures.lua new file mode 100644 index 0000000..5a3a4f9 --- /dev/null +++ b/src/textures.lua @@ -0,0 +1,26 @@ +require "string" +local img_bank = {} + +local lf = love.filesystem +function load_textures_from(path) + path = path or "assets/images" + local files = lf.getDirectoryItems(path) + + for _, file in ipairs(files) do + local filepath = path.."/"..file + + if lf.getInfo(filepath).type == "directory" then + load_textures_from(filepath) + else + if string.find(filepath, ".png") then + local name = string.gsub(filepath, ".png", "") + name = string.gsub(name, "assets/images/", "") + img_bank[name] = love.graphics.newImage(filepath) + end + end + end +end + +function get_tex(name) + return img_bank[name] +end diff --git a/src/utils.lua b/src/utils.lua new file mode 100644 index 0000000..c3e4bea --- /dev/null +++ b/src/utils.lua @@ -0,0 +1,39 @@ + +function lerp(a, b, c) + return a + (b - a) * c +end + +function dlerp(a, b, c) + return lerp(b, a, 0.5^c) +end + +function bton(bool) + return bool and 1 or 0 +end + +function min(a, b) + if a < b then + return a + end + return b +end + +function normalize(x, y) + if x == 0 and y == 0 then + return 0, 0 + end + + local leng = math.sqrt(x*x + y*y) + return x / leng, y / leng +end + +-- Top left corner +function point_in_rect(px, py, rx, ry, rw, rh) + return px > rx and px < rx + rw and py > ry and py < ry + rh +end + +function dist(x1, y1, x2, y2) + local dx = x1 - x2 + local dy = y1 - y2 + return (dx*dx + dy*dy)^0.5 +end -- cgit v1.3-2-g0d8e