From 8121de73db8acfe5264fd6e0218dc5413ffac95d Mon Sep 17 00:00:00 2001 From: iamcheeseman Date: Wed, 11 Mar 2026 13:19:11 -0400 Subject: basic AABB collision resolution --- main.lua | 13 +++++ src/init.lua | 1 + src/objs/player.lua | 17 ++++-- src/phys.lua | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/phys.lua diff --git a/main.lua b/main.lua index 2b23b5b..804a355 100644 --- a/main.lua +++ b/main.lua @@ -12,6 +12,14 @@ function love.load() set_scene(scn) new_player(100, 100) + + box1 = phys.Box.new(20, 20, 16, 16, { + layers = {}, + mask = {"hard"}, + }) + + ground1 = phys.Box.new(5, 50, 64, 16, {}) + ground2 = phys.Box.new(64-16, 50+16, 16, 64, {}) end function love.update(dt) @@ -19,6 +27,7 @@ function love.update(dt) assert(scn, "No scene set.") fire_event(scn.on_update, dt) + box1:update(0, 5, dt) flush_scene() end @@ -32,6 +41,10 @@ function love.draw(dt) fire_event(scn.on_draw, dt) + box1:draw() + ground1:draw() + ground2:draw() + -- TODO: Take care of weird displays lg.setCanvas() local scr_width, scr_height = lg.getDimensions() diff --git a/src/init.lua b/src/init.lua index fd13bcb..4764a2a 100644 --- a/src/init.lua +++ b/src/init.lua @@ -3,6 +3,7 @@ require "src.ecs" require "src.utils" require "src.input" require "src.textures" +require "src.phys" SCR_WIDTH = 320 SCR_HEIGHT = 180 diff --git a/src/objs/player.lua b/src/objs/player.lua index 229dac9..83b05fe 100644 --- a/src/objs/player.lua +++ b/src/objs/player.lua @@ -1,17 +1,17 @@ PLAYER_SPEED = 100 -register_comp("Body", function (ent, x, y) - ent.x = x - ent.y = y +register_comp("Body", function (ent, x, y, w, h, opts) ent.vx = 0 ent.vy = 0 + ent.box = phys.Box.new(x, y, w, h, opts) end) register_comp("Player", TAGCOMP) function body_sys(ent, dt) - ent.x = ent.x + ent.vx * dt - ent.y = ent.y + ent.vy * dt + ent.vx, ent.vy = ent.box:update(ent.vx, ent.vy, dt) + ent.x = ent.box.x + ent.y = ent.box.y end function draw_sys(ent) @@ -27,6 +27,11 @@ end function new_player(x, y) local ent = new_entity() - add_comp(ent, "Body", x, y) + add_comp(ent, "Body", x, y, 16, 16, { + offsetx = -8, + offsety = -8, + layers = {}, + mask = {"hard"}, + }) add_comp(ent, "Player") end diff --git a/src/phys.lua b/src/phys.lua new file mode 100644 index 0000000..a57bccd --- /dev/null +++ b/src/phys.lua @@ -0,0 +1,163 @@ +phys = {} + +local world = {} +phys.world = world + +world.boxes = {} +world.layers = {} + +-- There are a few primary physics layers: +-- +-- * Hard: for hard collisions that do not allow things in whatsoever +-- * Soft: for collisions that push other things away over time +-- * Player_Hurtbox: collision box that hurts the player +-- * Player_Hitbox: collision box from the player that damages things +-- * Enemy_Hurtbox: collision box that hurts enemies +-- * Enemy_Hitbox: collision box from an enemy that damages things +-- +-- You can use whatever physics layer you want, but these are the standard ones + +local function aabb_x(ax, aw, bx, bw) + return ax < bx + bw and bx < ax + aw +end + +local function aabb_y(ay, ah, by, bh) + return ay < by + bh and by < ay + ah +end + +local function aabb(ax, ay, aw, ah, bx, by, bw, bh) + return + ax < bx + bw and bx < ax + aw and + ay < by + bh and by < ay + ah +end + +local function add_box_to_layers(box) + for _, layer in ipairs(box.layers) do + if not world.layers[layer] then + world.layers[layer] = {} + end + table.insert(world.layers[layer], box) + end +end + +phys.Box = {} +phys.Box.__index = phys.Box + +function phys.Box.new( + x, y, + width, height, + opts +) + opts = opts or {} + + local self = setmetatable({}, phys.Box) + + self.x = x + self.y = y + + self.offsetx = opts.offsetx or 0 + self.offsety = opts.offsety or 0 + + self.width = width + self.height = height + + self.layers = opts.layers or {"hard"} + self.mask = opts.mask or {} + + self.touching_down = false + self.touching_up = false + self.touching_vertically = false + self.touching_left = false + self.touching_right = false + self.touching_horizontally = false + + add_box_to_layers(self) + + return self +end + +function phys.Box:get_rect() + return + self.x + self.offsetx, + self.y + self.offsety, + self.width, + self.height +end + +function phys.Box:update(velx, vely, dt) + local resx = self.x + velx * dt + local resy = self.y + vely * dt + + local small_side = math.min(self.width, self.height) + local steps = math.ceil(vec_len(velx * dt, vely * dt) / small_side) + + self.touching_down = false + self.touching_up = false + self.touching_vertically = false + self.touching_left = false + self.touching_right = false + self.touching_horizontally = false + + -- this block is fat. much like a certain croatian... + for i=1, steps do + local p = i / steps + + local boxx = resx + self.offsetx + velx * dt * p + local boxy = resy + self.offsety + vely * dt * p + + for _, layer in ipairs(self.mask) do + for _, other in ipairs(world.layers[layer] or {}) do + if other ~= self then + local ox = other.x + other.offsetx + local oy = other.y + other.offsety + + if aabb( + boxx, boxy, self.width, self.height, + ox, oy, other.width, other.height + ) then + if aabb_x(self.x + self.offsetx, self.width, ox, other.width) then + if vely > 0 then + resy = oy - self.height - self.offsety + self.touching_down = true + else + resy = oy + other.height - self.offsety + self.touching_up = true + end + self.touching_vertically = true + elseif aabb_y(self.y + self.offsety, self.height, oy, other.height) then + if velx > 0 then + resx = ox - self.width - self.offsetx + self.touching_left = true + else + resx = ox + other.width - self.offsetx + self.touching_right = true + end + self.touching_horizontally = true + end + end + end + end + end + + if self.touching_horizontally then + velx = 0 + end + if self.touching_vertically then + vely = 0 + end + + if self.touching_horizontally or self.touching_vertically then + break + end + end + + self.x = resx + self.y = resy + + return velx, vely +end + +function phys.Box:draw() + local x, y, w, h = self:get_rect() + love.graphics.rectangle("line", x, y, w, h) +end -- cgit v1.3-2-g0d8e