local DEFAULT_PLATFORM_COLOR = {255,255,255} local GRAVITY_CONSTANT = 700 local AIR_RES_CONSTANT = 0.5 local DEFAULT_PLATFORM_RES = 0.7 local VERY_SMALL_NUM = 0.01 local PLAYER_MOVE_SPEED = 20 local PUSH_RESISTANCE = math.huge -- Should be >=2, set to math.huge to disable pushing local BOUNCE_CONSTANT_X = 0 local BOUNCE_CONSTANT_Y = 0 local function platform_below_platform (platform, system) local tmp tmp, platform.collided_on_y_axis = platform.collided_on_y_axis, false return tmp end -------------------------------------------------------------------------------- local BulletController local PlayerController = {} PlayerController.__index = PlayerController function PlayerController.new (target, controls) assert(controls.jump and controls.left and controls.right ) local self = setmetatable({}, PlayerController) self.target = target self.controls = controls self.last_dir = 0 return self end function PlayerController:update (dt, system) local not_jumping = platform_below_platform(self.target, system) if love.keyboard.isDown( self.controls.jump ) then self.target.dy = self.target.dy + (not_jumping and -400 or (-400 * dt)) end if love.keyboard.isDown( self.controls.left ) then self.target.dx = self.target.dx - PLAYER_MOVE_SPEED self.last_dir = -1 end if love.keyboard.isDown( self.controls.right ) then self.target.dx = self.target.dx + PLAYER_MOVE_SPEED self.last_dir = 1 end end function PlayerController:canCollide (platform, collided_with) return platform ~= collided_with and (not collided_with.controller or collided_with.controller.__index ~= BulletController) end function PlayerController:onCollide (platform, collided_with, system) end -------------------------------------------------------------------------------- BulletController = {} BulletController.__index = BulletController function BulletController.new (target, owner) local self = setmetatable({}, BulletController) self.target = target self.owner = owner self.owner_im = 0.5 return self end function BulletController:update (dt, system) self.target.x = (self.target.x + self.target.w + (WINDOW_WIDTH + self.target.w)) % (WINDOW_WIDTH + self.target.w) - self.target.w self.owner_im = self.owner_im - dt end function BulletController:canCollide (platform, collided_with) return collided_with ~= platform and (collided_with ~= self.owner or self.owner_im <= 0) and (collided_with.controller and collided_with.controller.__index ~= BulletController) end function BulletController:onCollide (platform, collided_with, system) local dir = platform.dx / math.abs(platform.dx) local force = dir * ((platform.w / (40*0.2) )^1.5) * (40*0.2) local ndx, ndy = collided_with.dx + force * 15, collided_with.dy + -platform.h * 12 if ndx == ndx and ndy == ndy then -- Prevent nan values collided_with.dx, collided_with.dy = ndx, ndy system:removeController(self) system:removePlatform(self.target) end end -------------------------------------------------------------------------------- local Platform = {} Platform.__index = Platform function Platform.new (t) local self = setmetatable(t, Platform) assert(self.x and self.y and self.w and self.y) self.color = self.color or DEFAULT_PLATFORM_COLOR self.resistance = DEFAULT_PLATFORM_RES return self end -------------------------------------------------------------------------------- local PlatformSystem = {} PlatformSystem.__index = PlatformSystem function PlatformSystem.new () local self = setmetatable({}, PlatformSystem) self.platforms = {} self.controllers = {} return self end function PlatformSystem:addPlatform (x, y, w, h) local platform = Platform.new { x = x, y = y, w = w, h = h } self.platforms[platform] = true return platform end function PlatformSystem:removeController( controller ) self.controllers[controller] = nil end function PlatformSystem:removePlatform( platform ) self.platforms[platform] = nil end function PlatformSystem:addPlayer ( x, y, w, h, controls ) local platform = Platform.new { x = x, y = y, w = w, h = h, movable = true, dx = 0, dy = 0 } platform.controller = PlayerController.new(platform, controls) self.platforms[platform] = true self.controllers[platform.controller] = true return platform end function PlatformSystem:addBullet (x, y, w, h, dx, owner) local platform = Platform.new { x = x, y = y, w = w, h = h, movable = true, dx = dx, dy = 0, constant = true } platform.controller = BulletController.new(platform, owner) self.platforms[platform] = true self.controllers[platform.controller] = true return platform end function PlatformSystem:isPointOccupied (x, y, who_is_asking) for platform, _ in pairs(self.platforms) do if platform.x <= x and x <= platform.x + platform.w and platform.y <= y and y <= platform.y + platform.h and who_is_asking.controller:canCollide(who_is_asking, platform) then return platform end end return nil end local function is_movement_on_collision_course (platform, nx, ny, platforms, func) for other in pairs(platforms) do if func(platform, nx, ny, other) then return other end end end local function check_move_x (platform, nx, ny, other) return platform ~= other and nx < other.x + other.w and other.x < nx + platform.w and other.y < platform.y + platform.h and platform.y < other.y + other.h and platform.controller:canCollide(platform, other) end local function check_move_y (platform, nx, ny, other) return platform ~= other and ny < other.y + other.h and other.y < ny + platform.h and other.x < platform.x + platform.w and platform.x < other.x + other.w and platform.controller:canCollide(platform, other) end function PlatformSystem:updatePlatform (platform, dt) local ndx, ndy = platform.dx * (platform.constant and 1 or (1 - AIR_RES_CONSTANT) ^ dt), platform.dy + (platform.constant and 0 or (GRAVITY_CONSTANT * dt)) local nx, ny = platform.x + ndx * dt, platform.y + ndy * dt -- Moving down do local collided_with = is_movement_on_collision_course(platform, nx, ny, self.platforms, check_move_y) if not collided_with then platform.y, platform.dy = ny, ndy else platform.collided_on_y_axis = collided_with and (ndy >= 0) platform.controller:onCollide(platform, collided_with, self) ndx = ndx * (platform.constant and 1 or (1 - collided_with.resistance) ^ dt) platform.y = collided_with.y + ( (ndy >= 0) and -platform.h or collided_with.h) platform.dy = -platform.dy * BOUNCE_CONSTANT_Y end end -- Moving on x axis do local collided_with = is_movement_on_collision_course(platform, nx, ny, self.platforms, check_move_x) if not collided_with then platform.x, platform.dx = nx, ndx else platform.controller:onCollide(platform, collided_with, self) platform.x = collided_with.x + ( (ndx >= 0) and -platform.w or collided_with.w) if platform.movable and collided_with.movable and self.platforms[platform] and PUSH_RESISTANCE ~= math.huge then platform.dx, collided_with.dx = (platform.dx + collided_with.dx)/PUSH_RESISTANCE, (platform.dx + collided_with.dx)/PUSH_RESISTANCE elseif BOUNCE_CONSTANT_X == 0 then platform.dx = 0 end platform.dx = -platform.dx * BOUNCE_CONSTANT_X end end end function PlatformSystem:update (dt) -- Update movable platforms for platform, _ in pairs(self.platforms) do if platform.movable then self:updatePlatform(platform, dt) end end -- Update controllers for controller, _ in pairs(self.controllers) do controller:update(dt, self) end end function PlatformSystem:draw (dt, offset_x, offset_y) local offset_x, offset_y = offset_x or 0, offset_y or 0 for platform, _ in pairs(self.platforms) do love.graphics.setColor(platform.color) love.graphics.rectangle('fill', platform.x - offset_y, platform.y - offset_x, platform.w, platform.h) end end -------------------------------------------------------------------------------- return { PlatformSystem = PlatformSystem, Player = Player, }