local TMX = require('tmx') local TILE = 16 local FRAME_W = 16 local Player = {} Player.__index = Player function Player.new(cfg, sheet) local self = setmetatable({}, Player) self.cfg = cfg self.sheet = sheet self.frameCount = math.floor(sheet:getWidth() / FRAME_W) self.x = 0 self.y = 0 self.w = 14 -- hitbox slightly smaller than the sprite self.h = 15 self.vx = 0 self.vy = 0 self.facing = 1 self.grounded = false self.hp = cfg.health or 0 self.speed = 92 self.gravity = 760 self.maxFall = 320 self.jumpVel = -300 self.jumpCut = -110 self.coyoteTime = 0.08 self.jumpBuffer = 0.12 self._coyote = 0 self._buffer = 0 self.anim = 'idle' self.frameIndex = 1 self.animTimer = 0 return self end function Player:setPosition(x, y) self.x = x self.y = y self.vx = 0 self.vy = 0 end function Player:queueJump() self._buffer = self.jumpBuffer end local function solidAt(map, px, py) return TMX.isSolid(map, math.floor(px / TILE), math.floor(py / TILE)) end function Player:update(dt, map, input) dt = math.min(dt, 1 / 30) -- horizontal movement (instant, arcade style) local dir = (input.right and 1 or 0) - (input.left and 1 or 0) if dir ~= 0 then self.facing = dir end self.vx = dir * self.speed -- timers self._coyote = math.max(0, self._coyote - dt) self._buffer = math.max(0, self._buffer - dt) -- jumping (buffer + coyote time for good feel) if self._buffer > 0 and (self.grounded or self._coyote > 0) then self.vy = self.jumpVel self.grounded = false self._coyote = 0 self._buffer = 0 end -- variable jump height: cut the jump when the button is released early if not input.jumpHeld and self.vy < self.jumpCut then self.vy = self.jumpCut end self.vy = math.min(self.vy + self.gravity * dt, self.maxFall) -- move X, resolve collisions against tiles self.x = self.x + self.vx * dt if self.vx > 0 then local edge = self.x + self.w for ty = math.floor(self.y / TILE), math.floor((self.y + self.h - 0.001) / TILE) do if solidAt(map, edge, ty * TILE + self.h / 2) or solidAt(map, edge, (ty + 1) * TILE - 1) then self.x = (math.floor(edge / TILE)) * TILE - self.w - 0.001 break end end elseif self.vx < 0 then local edge = self.x for ty = math.floor(self.y / TILE), math.floor((self.y + self.h - 0.001) / TILE) do if solidAt(map, edge, ty * TILE + self.h / 2) or solidAt(map, edge, (ty + 1) * TILE - 1) then self.x = (math.floor(edge / TILE) + 1) * TILE + 0.001 break end end end -- move Y, resolve collisions against tiles self.y = self.y + self.vy * dt self.grounded = false if self.vy > 0 then local edge = self.y + self.h for tx = math.floor(self.x / TILE), math.floor((self.x + self.w - 0.001) / TILE) do if solidAt(map, tx * TILE + self.w / 2, edge) or solidAt(map, (tx + 1) * TILE - 1, edge) then self.y = math.floor(edge / TILE) * TILE - self.h self.vy = 0 self.grounded = true break end end elseif self.vy < 0 then local edge = self.y for tx = math.floor(self.x / TILE), math.floor((self.x + self.w - 0.001) / TILE) do if solidAt(map, tx * TILE + self.w / 2, edge) or solidAt(map, (tx + 1) * TILE - 1, edge) then self.y = (math.floor(edge / TILE) + 1) * TILE + 0.001 self.vy = 0 break end end end if self.grounded then self._coyote = self.coyoteTime end self:updateAnimation(dt) end function Player:updateAnimation(dt) local animName, frames if not self.grounded then animName, frames = 'jump', self.cfg.animations.jump elseif math.abs(self.vx) > 1 then animName, frames = 'walk', self.cfg.animations.walk else animName, frames = 'idle', self.cfg.animations.idle end if animName ~= self.anim then self.anim = animName self.frameIndex = 1 self.animTimer = 0 end if #frames > 1 then self.animTimer = self.animTimer + dt local fps = 8 while self.animTimer >= 1 / fps do self.animTimer = self.animTimer - 1 / fps self.frameIndex = (self.frameIndex % #frames) + 1 end else self.frameIndex = 1 end self.currentFrame = frames[self.frameIndex] end function Player:draw(camX, camY) local frame = self.currentFrame or 1 local quad = love.graphics.newQuad((frame - 1) * FRAME_W, 0, FRAME_W, TILE, self.sheet) love.graphics.push() love.graphics.translate(math.floor(self.x + self.w / 2) - camX, math.floor(self.y) - camY) love.graphics.scale(self.facing, 1) love.graphics.draw(self.sheet, quad, -self.w / 2, 0) love.graphics.pop() -- draw the weapon? not yet end return Player