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 {} local scene = get_current_scene() assert(scene, "No scene set up.") self.tilemap_mask = opts.tilemap_mask or { scene.tilemap } 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 or self.touchy ~= 0 end function phys.Box:get_rect() return self.x + self.offsetx, self.y + self.offsety, self.width, self.height end function box_in_tilemap(map, box) local x, y, width, height = box:get_rect() if has_tile(map, to_tile_coords(x, y)) then return true end if has_tile(map, to_tile_coords(x + width, y)) then return true end if has_tile(map, to_tile_coords(x, y + height)) then return true end if has_tile(map, to_tile_coords(x + width, y + height)) then return true end local extra_x = math.floor(width / (TILESIZE - 1)) local step = width / (extra_x + 1) for i = 1, extra_x do if has_tile( map, to_tile_coords(x + step * i, y)) then return true end if has_tile( map, to_tile_coords(x + step * i, y + height)) then return true end end local extra_y = math.floor(height / (TILESIZE - 1)) step = height / (extra_y + 1) for i = 1, extra_y do if has_tile( map, to_tile_coords(x, y + step * i)) then return true end if has_tile( map, to_tile_coords(x + width, y + step * i)) then return true end end return value 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 _, tilemap in ipairs(box.tilemap_mask) do if box_in_tilemap(tilemap, box) then return true end end for _, layer in ipairs(box.mask) do for _, other in ipairs(world.layers[layer] or {}) do if other == box then goto collision_check_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 ::collision_check_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) self.touchx = 0 self.touchy = 0 -- this block is fat. much like a certain croatian... -- not anymore :D -- shut up -- welc 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 = vely > 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