Player = Entity:New() function Player:New(x,y) local o = Entity:New(x,y) o.type = "player" -- physics o.moveSpeed = 1.3 -- gameworld pixels o.zeroSpeed = 0.01 -- gameworld pixels o.move_x = 0 -- gameworld pixels o.airFriction = 0.01 -- gameworld pixels o.groundFriction = 0.3 -- gameworld pixels o.jumpImpulse = 3.5 -- gameworld pixels self.wallJumpImpulse = { x = 2.5, y = 3.5 } o.coyoteAmount = 5 -- int o.coyoteValue = 5 -- frames o.dashCooldownTime = 0.1 -- seconds o.dashCooldownTimer = 0 -- seconds -- dash values o.dashTimer = 0 -- seconds o.dashTime = 0.15 -- seconds o.dashDistance = 40 -- gameworld pixels o.dashSpeed = o.dashDistance / (o.dashTime*60) -- pixels o.dashCount = 1 -- int o.dashAmount = 10 -- int -- hook values o.hookAnchor = { x = nil, y = nil } o.lightRange = 40 -- screen pixels -- status o.isDashing = false o.isJumping = false o.isHooked = false o.isOnGround = true o.isOnLadder = false o.canJump = true o.canFall = true o.canFriction = true o.maskType = animation.moth_mask o.wallHit = 0 o.anchorRespawn = { x = o.pos.x, y = o.pos.y } -- sprite o.target_offset = {x = 0, y = 0} o.body = Animation:New(animation.nancy.idle) o.mask = Animation:New(animation.moth_mask.idle) o:centerOffset(o.body) o:getBoundingBox(o.body,0,3,-1,-3) -- lights o.light = Light:New(o.pos.x,o.pos.y,o.lightRange) table.insert(LoadedObjects.Entities,o) o.id = #LoadedObjects.Entities setmetatable(o, self) self.__index = self return o end function Player:Smart() self:LightAdjust(self.target_offset.x,self.target_offset.y) -- reset coyoteValue if self.isOnGround then self.coyoteValue = self.coyoteAmount elseif self.coyoteValue > 0 then self.coyoteValue = self.coyoteValue - 1 end if self.dashTimer <= 0 then -- horizontal movement if Keybind:CheckDown(Keybind.move.left) then self.move_x = -self.moveSpeed elseif Keybind:CheckDown(Keybind.move.right) then self.move_x = self.moveSpeed end -- jump if on ground (coyotevalue) if Keybind:CheckDown(Keybind.move.jump) then if self.coyoteValue > 0 then self.vel.y = -self.jumpImpulse self.coyoteValue = 0 elseif self.wallHit ~= 0 then self.vel.y = -self.wallJumpImpulse.y self.vel.x = -self.wallJumpImpulse.x * self.wallHit end end end -- dash timer self.dashCooldownTimer = math.max(0,self.dashCooldownTimer - current_dt) -- try to dash if Keybind:CheckDown(Keybind.move.dash) then if self.dashCooldownTimer == 0 and not self.isDashing and self.dashCount > 0 then self:Unhook() -- state player self.isDashing = true self.dashCount = self.dashCount - 1 -- get dash direction local vertical = 0 if Keybind:CheckDown(Keybind.move.down) then vertical = vertical + 1 end if Keybind:CheckDown(Keybind.move.up) then vertical = vertical - 1 end local horizontal = 0 if Keybind:CheckDown(Keybind.move.right) then horizontal = horizontal + 1 end if Keybind:CheckDown(Keybind.move.left) then horizontal = horizontal - 1 end -- if no direction, then dash forward if horizontal == 0 and vertical == 0 then horizontal = self.sprite_flip.x end -- set dash values self.dashDirection = GetAngleFromVector(horizontal, vertical) self.dashTimer = self.dashTime end else -- not dashing! self.isDashing = false end if Keybind:CheckPressed(Keybind.move.hook) then if self.isHooked then self:Unhook() else local anchor = self:CheckNearest("hook_anchor",self.hookDistance) if anchor then self.isHooked = true self.hookDistance = anchor.hookDistance self.hookAnchor = { x = anchor.pos.x, y = anchor.pos.y } end end end end function Player:DoPhysics() if self.dashTimer <= 0 then if self.isOnGround then self.vel.x = self.vel.x * (1-self.groundFriction) else self.vel.x = self.vel.x * (1-self.airFriction) end self.vel.y = self.vel.y * (1-self.airFriction) if math.abs(self.vel.x) < self.zeroSpeed then self.vel.x = 0 end end -- reset state self.canFall = true self.isOnGround = false -- adjust timers self.dashTimer = self.dashTimer - current_dt -- DASH STATE if self.dashTimer > 0 then self.canFall = false -- dash particle local particle_data = { animation = self.body, sprite_tint = HEX2RGB("#fed100"), sprite_alpha = 0.5, sprite_flip = { x = self.sprite_flip.x, y = self.sprite_flip.y } } Particle:New(self.pos.x,self.pos.y,particle_data) self.dashCooldownTimer = self.dashCooldownTime -- dash movement self.vel.x = self.dashSpeed * math.cos(self.dashDirection) self.vel.y = self.dashSpeed * math.sin(self.dashDirection) end -- hook state if self.isHooked then local hook = Vector(self.pos.x, self.pos.y, self.hookAnchor.x, self.hookAnchor.y) if GetVectorValue(hook) > self.hookDistance then local hook_angle = GetAngleFromVector(hook[1],hook[2])-math.rad(180) if Keybind:CheckDown(Keybind.move.right) then hook_angle = hook_angle - math.rad(0.05) self.move_x = 0 end if Keybind:CheckDown(Keybind.move.left) then hook_angle = hook_angle + math.rad(0.05) self.move_x = 0 end local particle_data = { animation = self.body, sprite_tint = HEX2RGB("#fed100"), sprite_alpha = 0.5, sprite_flip = { x = self.sprite_flip.x, y = self.sprite_flip.y } } Particle:New(self.pos.x,self.pos.y,particle_data) local pos_x = self.hookAnchor.x + self.hookDistance * math.cos(hook_angle) local pos_y = self.hookAnchor.y + self.hookDistance * math.sin(hook_angle) self.vel.x = self.vel.x + pos_x - self.pos.x self.vel.y = self.vel.y + pos_y - self.pos.y self.pos.x = pos_x self.pos.y = pos_y end end if self.canFall then -- not in dash self.dashTimer = 0 self.vel.y = self.vel.y + gravity end -- horizontal collision if not self:isCollidingAt(self.pos.x + self.vel.x + self.move_x, self.pos.y, LoadedObjects.Collisions) then self.pos.x = self.pos.x + self.vel.x + self.move_x self.wallHit = 0 else self.wallHit = math.sign(self.vel.x + self.move_x) self.vel.x = 0 end -- vertical collision if not self:isCollidingAt(self.pos.x, self.pos.y + self.vel.y, LoadedObjects.Collisions) then self.pos.y = self.pos.y + self.vel.y else if self.vel.y > 0 then self.isOnGround = true self.dashCount = self.dashAmount end self.vel.y = 0 end -- if u collision w hazard, respawn if self:isCollidingAt(self.pos.x, self.pos.y, LoadedObjects.Hazards) then self:Respawn() end end function Player:Respawn() self.pos.x = self.anchorRespawn.x self.pos.y = self.anchorRespawn.y end function Player:HandleAnimation() -- flip sprite to look in the direction is moving if self.isHooked then if self.vel.x ~= 0 then self.sprite_flip.x = math.sign(self.vel.x) end elseif self.move_x ~= 0 then self.sprite_flip.x = math.sign(self.move_x) end -- animation priority if self.vel.y > 1.25 then self.body = self.body:ChangeTo(animation.nancy.fall) self.mask = self.mask:ChangeTo(self.maskType.fall) elseif self.vel.y < 0 then self.body = self.body:ChangeTo(animation.nancy.jump) self.mask = self.mask:ChangeTo(self.maskType.jump) elseif self.vel.x + self.move_x ~= 0 then self.body = self.body:ChangeTo(animation.nancy.run) self.mask = self.mask:ChangeTo(self.maskType.run) else self.body = self.body:ChangeTo(animation.nancy.idle) self.mask = self.mask:ChangeTo(self.maskType.idle) end -- special case: idle animation gets slower by time if self.body.anim_path == animation.nancy.idle.path then if self.body.anim_speed < 0.5 then self.body.anim_speed = self.body.anim_speed + 0.001 end end if self.isHooked then love.graphics.line( -Camera.pos.x + self.pos.x, -Camera.pos.y + self.pos.y, -Camera.pos.x + self.hookAnchor.x, -Camera.pos.y + self.hookAnchor.y ) end self.body:Animate() self:Draw(self.body) if self.dashCount > 0 then self:Draw(self.mask) end self.move_x = 0 end function Player:Unhook() self.isHooked = false self.hookAnchor = nil end function Player:Debug() love.graphics.print("wallHit: "..self.wallHit) end