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(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.touchx = 0 self.touchy = 0 add_box_to_layers(self) return self end function phys.Box:touching_left() return self.touchx < 0 end function phys.Box:touching_right() return self.touchx > 0 end function phys.Box:touching_horizontal() return self.touchx ~= 0 end function phys.Box:touching_up() return self.touchy < 0 end function phys.Box:touching_down() return self.touchy > 0 end function phys.Box:touching_vertical() return self.touchy ~= 0 end function phys.Box:touching() return self.touchx ~= 0 and self.touchy ~= 0 end function phys.Box:get_rect() return self.x + self.offsetx, self.y + self.offsety, self.width, self.height end function collision_check(box) local box_x, box_y, box_width, box_height = box:get_rect() local other_x, other_y, other_width, other_height for _, layer in ipairs(box.mask) do for _, other in ipairs(world.layers[layer] or {}) do if other == box then goto continue end other_x, other_y, other_width, other_height = other:get_rect() if aabb( box_x, box_y, box_width, box_height, other_x, other_y, other_width, other_height ) then return true end ::continue:: end end return false end function phys.Box:update(velx, vely, dt) local small_side = math.min(self.width, self.height) local steps = math.ceil(vec_len(velx * dt, vely * dt) / small_side) -- this block is fat. much like a certain croatian... -- not anymore :D for i = 1, steps do local p = i / steps local before before = self.x self.x = self.x + velx * dt * p if collision_check(self) then self.touchx = velx > 0 and 1 or -1 velx = 0 self.x = before end before = self.y self.y = self.y + vely * dt * p if collision_check(self) then self.touchy = velx > 0 and 1 or -1 vely = 0 self.y = before end if self:touching() then break end end return velx, vely end function phys.Box:draw() local x, y, w, h = self:get_rect() lg.rectangle("line", x, y, w, h) end