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() lg.rectangle("line", x, y, w, h) end