Player = class(Entity, { type = "Player", display = Animation:new(animation.nancy.idle), }) function Player:new(x,y) local o = Entity:new(x,y) -- physics o.zero_speed = 0.01 -- gameworld pixels o.move_x = 0 -- gameworld pixels o.move_speed = 1.3 -- gameworld pixels o.nodrift_frames = 0 -- frames o.air_friction = 0.01 -- gameworld pixels o.ground_friction = 0.3 -- gameworld pixels o.wall_friction = 0.3 -- gameworld pixels o.jump_impulse = 3.5 -- gameworld pixels o.coyote_value = 5 -- frames o.coyote_amount = 5 -- int -- dash values o.dash_timer = 0 -- seconds o.dash_time = 0.15 -- seconds o.dash_distance = 40 -- gameworld pixels o.dash_speed = o.dash_distance / (o.dash_time*60) -- pixels o.dash_count = 1 -- int o.dash_amount = 10 -- int o.dash_cooldown_time = 0.1 -- seconds o.dash_cooldown_timer = 0 -- seconds -- hook values o.hook_swing_speed = math.rad(0.05) o.hook_anchor = nil -- walljump values o.walljump_nodrift_amount = 12 o.walljump_impulse = { x = 2.5, y = 3.5 } -- status o.can_jump = true o.can_fall = true o.can_friction = true o.can_hook = true o.can_walljump = true o.is_dashing = false o.is_hooked = false o.is_sliding = false o.is_jumping = false o.is_on_ground = false o.is_on_ladder = false o.mask_type = animation.moth_mask o.wall_hit = 0 o.respawn_anchor = { 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:createBox(o.body,0,4,-1,-5) -- lights local light_data = {} light_data.radius = 40 light_data.shine_radius = 20 light_data.flicker = nil light_data.color = nil o.light = Light:new(o.pos.x,o.pos.y,light_data) o:id() setmetatable(o, self) self.__index = self return o end function Player:doLogic() -- reset coyote_value if self.is_on_ground then self.coyote_value = self.coyote_amount elseif self.coyote_value > 0 then self.coyote_value = self.coyote_value - 1 end -- not dashing, normal movment if self.dash_timer <= 0 then -- horizontal movement if not self.is_hooked then if self.nodrift_frames > 0 then self.move_x = 0 elseif Keybind:checkDown(Keybind.move.left) then self.move_x = -1 self.vel.x = math.min(self.vel.x, -self.move_speed) elseif Keybind:checkDown(Keybind.move.right) then self.move_x = 1 self.vel.x = math.max(self.vel.x, self.move_speed) end end -- jump if on ground (coyotevalue) or if 0 if self.can_jump and Keybind:checkPressed(Keybind.move.jump) then if self.can_walljump and self.wall_hit ~= 0 then self.is_sliding = false self.vel.y = -self.walljump_impulse.y self.vel.x = -self.walljump_impulse.x * self.wall_hit self.move_x = 0 self.sprite_flip.x = -self.sprite_flip.x self.nodrift_frames = self.walljump_nodrift_amount elseif self.coyote_value > 0 then self.vel.y = -self.jump_impulse self.coyote_value = 0 end end end -- dash timer self.dash_cooldown_timer = math.max(0,self.dash_cooldown_timer - current_dt) -- try to dash if Keybind:checkDown(Keybind.move.dash) then if self.dash_cooldown_timer == 0 and not self.is_dashing and self.dash_count > 0 then self:unhook() self.nodrift_frames = 0 -- state player self.is_dashing = true self.is_sliding = false self.dash_count = self.dash_count - 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(vector(horizontal, vertical)) self.dash_timer = math.floor(self.dash_time * game.framerate) end else -- not dashing! self.is_dashing = false end if self.can_hook and Keybind:checkPressed(Keybind.move.hook) then if self.is_hooked then self:unhook() else local anchor = self:checkNearest("HookAnchor","hook_specific") if anchor then self.is_hooked = true self.hook_distance = anchor.hook_distance self.hook_anchor = { x = anchor.pos.x, y = anchor.pos.y } end end end end function Player:doPhysics() if self.dash_timer <= 0 then if self.is_on_ground then self.vel.x = self.vel.x * (1-self.ground_friction) else self.vel.x = self.vel.x * (1-self.air_friction) end self.is_sliding = false if self.wall_hit == 0 then self.vel.y = self.vel.y * (1-self.air_friction) elseif self.nodrift_frames ~= self.walljump_nodrift_amount then self.is_sliding = true self.vel.y = self.vel.y * (1-self.wall_friction) end if math.abs(self.vel.x) < self.zero_speed then self.vel.x = 0 end end -- reset state self.can_fall = true self.is_on_ground = false -- adjust timers self.dash_timer = self.dash_timer - 1 self.nodrift_frames = self.nodrift_frames - 1 -- DASH STATE if self.dash_timer > 0 then self.can_fall = false -- dash particle local particle_data = { animation = self.body, animation_speed = 0, sprite_tint = hex2rgb("#fed100"), sprite_alpha = 0.5, time = 0.2, sprite_flip = { x = self.sprite_flip.x, y = self.sprite_flip.y } } Particle:new(self.pos.x,self.pos.y,particle_data) self.dash_cooldown_timer = self.dash_cooldown_time -- dash movement self.vel.x = self.dash_speed * math.cos(self.dashDirection) self.vel.y = self.dash_speed * math.sin(self.dashDirection) end -- hook state if self.is_hooked then self.move_x = 0 local hook = vector(self.hook_anchor.x - self.pos.x, self.hook_anchor.y - self.pos.y) local dist = math.min(getVectorValue(hook), self.hook_distance) local hook_angle = getAngleFromVector(hook)-math.rad(180) if Keybind:checkDown(Keybind.move.right) then hook_angle = hook_angle - self.hook_swing_speed end if Keybind:checkDown(Keybind.move.left) then hook_angle = hook_angle + self.hook_swing_speed end local particle_data = { animation = self.body, animation_speed = 0, sprite_tint = hex2rgb("#fed100"), sprite_alpha = 0.5, time = 4, time_unit = "frames", 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.hook_anchor.x + dist * math.cos(hook_angle) local pos_y = self.hook_anchor.y + dist * 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:moveX( pos_x - self.pos.x ) self:moveY( pos_y - self.pos.y ) end if self.can_fall then -- not in dash self.dash_timer = 0 self.vel.y = self.vel.y + gravity end -- horizontal collision self.wall_hit = 0 self:moveX( self.vel.x, function() self.wall_hit = math.sign(self.vel.x) self.vel.x = 0 end ) -- vertical collision self:moveY( self.vel.y, function() if self.vel.y > 0 then self.is_on_ground = true self.dash_count = self.dash_amount 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 self:adjustLight(self.target_offset.x,self.target_offset.y) end function Player:respawn() self.pos.x = self.respawn_anchor.x self.pos.y = self.respawn_anchor.y end function Player:handleAnimation() -- flip sprite to look in the direction is moving if self.is_hooked 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.is_sliding then self.body = self.body:change(animation.nancy.slide) self.mask = self.mask:change(self.mask_type.slide) elseif self.vel.y > 1.25 then self.body = self.body:change(animation.nancy.fall) self.mask = self.mask:change(self.mask_type.fall) elseif self.vel.y < 0 then self.body = self.body:change(animation.nancy.jump) self.mask = self.mask:change(self.mask_type.jump) elseif self.vel.x + self.move_x ~= 0 and not self.is_hooked then self.body = self.body:change(animation.nancy.run) self.mask = self.mask:change(self.mask_type.run) elseif not self.is_hooked then self.body = self.body:change(animation.nancy.idle) self.mask = self.mask:change(self.mask_type.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.is_hooked then love.graphics.line( -Camera.pos.x + self.pos.x, -Camera.pos.y + self.pos.y, -Camera.pos.x + self.hook_anchor.x, -Camera.pos.y + self.hook_anchor.y ) end self.body:animate() self:draw(self.body) if self.dash_count > 0 then self:draw(self.mask) end self.move_x = 0 end function Player:unhook() self.is_hooked = false self.hook_anchor = nil self.hook_distance = nil end function Player:debug() Entity.debug(self) love.graphics.print("wall_hit: "..self.wall_hit) end