class('Player').extends(playdate.graphics.sprite) -- local references local Point = playdate.geometry.point local Rect = playdate.geometry.rect local vector2D = playdate.geometry.vector2D local affineTransform = playdate.geometry.affineTransform local min, max, abs, floor = math.min, math.max, math.abs, math.floor -- constants local dt = 0.05 -- time between frames at 20fps local MAX_VELOCITY = 600 local GRAVITY_CONSTANT = 1680 local GRAVITY_STEP = vector2D.new(0, GRAVITY_CONSTANT * dt) local JUMP_VELOCITY = 390 local SUPERJUMP_VELOCITY = 440 local MAX_JUMP_VELOCITY = 420 local MAX_FALL_VELOCITY = 280 local GROUND_FRICTION = 0.8 -- could be increased for different types of terrain local SKID_FRICTION = 0.7 -- stop more quickly if the player is trying to change directions local RUN_VELOCITY = 14 -- velocity change for running local AIR_RUN_VELOCITY = 10 -- velocity change for moving in the air local SPEED_OF_RUNNING = 50 -- thea velocity at which we decide the player is running rather than walking local LEFT, RIGHT = 1, 2 local STAND, RUN1, RUN2, RUN3, TURN, JUMP, CROUCH = 1, 2, 3, 4, 5, 6, 7 -- timer used for player jumps local jumpTimer = playdate.frameTimer.new(5, 45, 45, playdate.easingFunctions.outQuad) jumpTimer:pause() jumpTimer.discardOnCompletion = false -- local variables - these are "class local" but since we only have one player this isn't a problem local minXPosition = 8 local maxXPosition = 0 -- real value set in level.lua importTilemapsFromTiledJSON() playerStates = {} local spriteHeight = 32 local onGround = true -- true if player's feet are on the ground local skidding = false -- true if player is moving one way but the player is trying to move the opposite direction local crouch = false local facing = RIGHT local MAX_RUN_VELOCITY = 240 local runImageIndex = 1 function Player:init() Player.super.init(self) self.playerImages = playdate.graphics.imagetable.new('img/player') self:setImage(self.playerImages:getImage(1)) self:setZIndex(1000) self:setCenter(0.5, 1) -- set center point to center bottom middle self:moveTo(102, 210) self:setCollideRect(2,0,16-4,32) self.position = Point.new(102, 210) self.velocity = vector2D.new(0,0) end function Player:reset() self.position = Point.new(102, 108) self.velocity = vector2D.new(0,0) end function Player:collisionResponse(other) if other:isa(Coin) or (other:isa(Enemy) and other.crushed == true) then return "overlap" end return "slide" end -- called every frame, handles new input and does simple physics simulation function Player:update() if playdate.buttonIsPressed("down") and onGround then self:setCrouching(true) else if onGround then self:setCrouching(false) end if playdate.buttonIsPressed("left") then if onGround then self:runLeft() else self:airRunLeft() -- can still move left and right in the air, but not as quickly end if playdate.buttonJustPressed("left") then if self.velocity.x > SPEED_OF_RUNNING then -- skid if Player moving fast enough and now is trying to go the other way skidding = true else skidding = false end end elseif playdate.buttonIsPressed("right") then if onGround then self:runRight() else self:airRunRight() end if playdate.buttonJustPressed("right") then if self.velocity.x < -SPEED_OF_RUNNING then skidding = true else skidding = false end end end end if onGround and (skidding == true or crouch == true or (playdate.buttonIsPressed("left") == false and playdate.buttonIsPressed("right") == false)) then if skidding == true then self.velocity.x = self.velocity.x * SKID_FRICTION else self.velocity.x = self.velocity.x * GROUND_FRICTION end end -- no longer skidding if we've slowed down this much if abs(self.velocity.x) < 10 then skidding = false end if playdate.buttonJustPressed("A") then self:jump() elseif playdate.buttonIsPressed("A") then self:continueJump() end -- set the maximum velocity based on if the O button is down or not if playdate.buttonIsPressed("B") then MAX_RUN_VELOCITY = 240 else MAX_RUN_VELOCITY = 120 end -- gravity self.velocity = self.velocity + GRAVITY_STEP -- don't accellerate past max velocity if self.velocity.x > MAX_RUN_VELOCITY then self.velocity.x = MAX_RUN_VELOCITY elseif self.velocity.x < -MAX_RUN_VELOCITY then self.velocity.x = -MAX_RUN_VELOCITY end if self.velocity.y > MAX_FALL_VELOCITY then self.velocity.y = MAX_FALL_VELOCITY elseif self.velocity.y < -MAX_JUMP_VELOCITY then self.velocity.y = -MAX_JUMP_VELOCITY end -- update the index used to control which frame of the run animation Player is in. Switch frames faster when running quickly. if abs(self.velocity.x) < 10 then self.velocity.x = 0 runImageIndex = 1 elseif abs(self.velocity.x) < 140 then runImageIndex = runImageIndex + 0.5 else runImageIndex = runImageIndex + 1 end if runImageIndex > 3.5 then runImageIndex = 1 end -- update Player position based on current velocity local velocityStep = self.velocity * dt self.position = self.position + velocityStep -- don't move outside the walls of the game if self.position.x < minXPosition then self.velocity.x = 0 self.position.x = minXPosition elseif self.position.x > maxXPosition then self.velocity.x = 0 self.position.x = maxXPosition end self:updateImage() end -- sets the appropriate sprite image for Player based on the current conditions function Player:updateImage() if crouch then if facing == LEFT then self:setImage(self.playerImages:getImage(CROUCH), "flipX") else self:setImage(self.playerImages:getImage(CROUCH)) end elseif onGround then if facing == LEFT then if skidding then self:setImage(self.playerImages:getImage(TURN), "flipX") elseif self.velocity.x == 0 then self:setImage(self.playerImages:getImage(STAND), "flipX") else self:setImage(self.playerImages:getImage(floor(runImageIndex+1)), "flipX") end else if skidding then self:setImage(self.playerImages:getImage(TURN)) elseif self.velocity.x == 0 then self:setImage(self.playerImages:getImage(STAND)) else self:setImage(self.playerImages:getImage(floor(runImageIndex+1))) end end else if facing == LEFT then self:setImage(self.playerImages:getImage(JUMP), "flipX") else self:setImage(self.playerImages:getImage(JUMP)) end end end function Player:setMaxX(x) maxXPosition = x end function Player:setOnGround(flag) onGround = flag end function Player:setCrouching(flag) crouch = flag if crouch then spriteHeight = 22 else spriteHeight = 32 end end function Player:isCrouching() return crouch end function Player:runLeft() facing = LEFT self.velocity.x = max(self.velocity.x - RUN_VELOCITY, -MAX_VELOCITY) end function Player:runRight() facing = RIGHT self.velocity.x = min(self.velocity.x + RUN_VELOCITY, MAX_VELOCITY) end function Player:airRunLeft() facing = LEFT self.velocity.x = self.velocity.x - AIR_RUN_VELOCITY if self.velocity.y < -MAX_VELOCITY then self.velocity.x = -MAX_VELOCITY end end function Player:airRunRight() facing = RIGHT self.velocity.x = self.velocity.x + AIR_RUN_VELOCITY if self.velocity.x > MAX_VELOCITY then self.velocity.x = MAX_VELOCITY end end function Player:jump() -- if feet on ground if onGround then if playdate.buttonIsPressed("A") and abs(self.velocity.x) > SPEED_OF_RUNNING then -- super jump self.velocity.y = -SUPERJUMP_VELOCITY else -- regular jump self.velocity.y = -JUMP_VELOCITY end jumpTimer:reset() jumpTimer:start() skidding = false SoundManager:playSound(SoundManager.kSoundJump) end end function Player:continueJump() self.velocity.y = self.velocity.y - jumpTimer.value end