Files
pixel-platformer/game/sim/player.py
T

232 lines
8.4 KiB
Python

from .. import constants
from .entity import Actor
from .geometry import sign, approach
from . import physics
from .inventory import Inventory
class Player(Actor):
def __init__(self, x, y):
super().__init__(x, y, 12, 16)
self.maxHp = constants.playerMaxHp
self.hp = self.maxHp
self.iframes = 0
self.coyoteTimer = 0
self.jumpBufferTimer = 0
self.airJumps = constants.maxAirJumps
self.dashTimer = 0
self.dashCooldown = 0
self.wallStickTimer = 0
self.wallSliding = False
self.onLadder = False
self.jumpHeldPrev = False
self.inventory = Inventory()
self.score = 0
self.coins = 0
self.spawnX = float(x)
self.spawnY = float(y)
def hurtBox(self):
return self.aabb.inflated(-1.0, 0.0)
def isInvulnerable(self):
return self.iframes > 0 or self.dashTimer > 0
def setCheckpoint(self, x, y):
self.spawnX = float(x)
self.spawnY = float(y)
def respawn(self):
self.x = self.spawnX
self.y = self.spawnY
self.vx = 0.0
self.vy = 0.0
self.iframes = constants.iframesOnHit
self.dashTimer = 0
self.alive = True
def takeDamage(self, amount, sourceX, world):
if self.isInvulnerable() or not self.alive:
return False
self.hp -= amount
self.iframes = constants.iframesOnHit
away = sign(self.centerX - sourceX) or self.facing
self.vx = constants.knockbackX * away
self.vy = constants.knockbackY
self.dashTimer = 0
world.playSound("hurt", self.centerX)
world.spawnBurst("hurt", self.centerX, self.centerY, 8)
if self.hp <= 0:
self.hp = 0
self.alive = False
world.playSound("die", self.centerX)
return True
def heal(self, amount):
self.hp = min(self.maxHp, self.hp + amount)
def update(self, world, command):
if not self.alive:
return
if self.iframes > 0:
self.iframes -= 1
if self.dashCooldown > 0:
self.dashCooldown -= 1
terrain = world.level
platforms = world.platforms
self.updateLadder(world, command, terrain)
if self.onLadder:
self.moveOnLadder(world, command, terrain, platforms)
return
self.applyHorizontal(command)
self.updateTimers(command, terrain, platforms)
self.handleDash(world, command)
if self.dashTimer > 0:
self.runDash(world, terrain, platforms)
return
self.handleJump(world, command, platforms)
self.applyGravity(command)
self.applyWallSlide(command, terrain, platforms)
physics.moveHorizontal(self, terrain, platforms)
wasOnGround = self.onGround
physics.moveVertical(self, terrain, platforms, dropThrough=self.wantsDropThrough(command))
physics.resolveSlope(self, terrain, wasOnGround)
if self.onGround:
self.airJumps = constants.maxAirJumps
self.coyoteTimer = constants.coyoteFrames
self.jumpHeldPrev = command.jumpHeld
def wantsDropThrough(self, command):
return command.down and command.jump
def applyHorizontal(self, command):
axis = command.moveAxis
topSpeed = constants.runSpeed if command.run else constants.moveSpeed
accel = constants.moveAccel if self.onGround else constants.airAccel
if axis != 0:
self.vx += accel * axis
self.facing = axis
if self.vx > topSpeed:
self.vx = topSpeed
if self.vx < -topSpeed:
self.vx = -topSpeed
else:
damping = constants.friction if self.onGround else constants.airFriction
self.vx *= damping
if abs(self.vx) < 0.1:
self.vx = 0.0
def updateTimers(self, command, terrain, platforms):
if self.coyoteTimer > 0:
self.coyoteTimer -= 1
if self.jumpBufferTimer > 0:
self.jumpBufferTimer -= 1
if command.jump and not command.down:
self.jumpBufferTimer = constants.jumpBufferFrames
if self.wallStickTimer > 0:
self.wallStickTimer -= 1
def handleDash(self, world, command):
if command.dash and self.dashCooldown == 0 and self.dashTimer == 0:
direction = command.moveAxis or self.facing
self.facing = direction
self.dashTimer = constants.dashFrames
self.dashCooldown = constants.dashCooldownFrames
self.vx = constants.dashSpeed * direction
self.vy = 0.0
world.playSound("dash", self.centerX)
world.spawnBurst("dash", self.centerX, self.centerY, 6)
def runDash(self, world, terrain, platforms):
self.dashTimer -= 1
self.vy = 0.0
physics.moveHorizontal(self, terrain, platforms)
wasOnGround = self.onGround
physics.moveVertical(self, terrain, platforms)
physics.resolveSlope(self, terrain, wasOnGround)
if self.onGround:
self.airJumps = constants.maxAirJumps
self.coyoteTimer = constants.coyoteFrames
def handleJump(self, world, command, platforms):
if command.down:
return
touchLeft = physics.touchingSolidSide(self, world.level, platforms, -1)
touchRight = physics.touchingSolidSide(self, world.level, platforms, 1)
onWall = (touchLeft or touchRight) and not self.onGround
if self.jumpBufferTimer > 0:
if self.onGround or self.coyoteTimer > 0:
self.vy = constants.jumpVelocity
self.jumpBufferTimer = 0
self.coyoteTimer = 0
world.playSound("jump", self.centerX)
world.spawnBurst("jump", self.centerX, self.bottom, 4)
elif onWall:
awayDir = 1 if touchLeft else -1
self.vx = constants.wallJumpVelocityX * awayDir
self.vy = constants.wallJumpVelocityY
self.facing = awayDir
self.jumpBufferTimer = 0
self.wallStickTimer = 0
world.playSound("jump", self.centerX)
elif self.airJumps > 0:
self.vy = constants.doubleJumpVelocity
self.airJumps -= 1
self.jumpBufferTimer = 0
world.playSound("doubleJump", self.centerX)
world.spawnBurst("jump", self.centerX, self.centerY, 6)
if self.jumpHeldPrev and not command.jumpHeld and self.vy < 0:
self.vy *= constants.jumpCutMultiplier
def applyGravity(self, command):
pull = constants.gravity
cap = constants.fastFallSpeed if command.down and not self.onGround else constants.maxFallSpeed
self.vy += pull
if self.vy > cap:
self.vy = cap
def applyWallSlide(self, command, terrain, platforms):
self.wallSliding = False
if self.onGround or self.vy <= 0:
return
touchLeft = physics.touchingSolidSide(self, terrain, platforms, -1)
touchRight = physics.touchingSolidSide(self, terrain, platforms, 1)
pressingIntoWall = (command.left and touchLeft) or (command.right and touchRight)
if pressingIntoWall:
self.wallSliding = True
self.wallStickTimer = constants.wallStickFrames
if self.vy > constants.wallSlideSpeed:
self.vy = constants.wallSlideSpeed
def updateLadder(self, world, command, terrain):
onLadderTile = physics.overlapsLadder(self, terrain)
if not onLadderTile:
self.onLadder = False
return
if command.up or command.down:
self.onLadder = True
if self.onLadder and self.onGround and command.down and not onLadderTile:
self.onLadder = False
def moveOnLadder(self, world, command, terrain, platforms):
climbSpeed = 1.4
self.vy = command.verticalAxis * climbSpeed
self.vx = command.moveAxis * (constants.moveSpeed * 0.6)
if command.jump:
self.onLadder = False
self.vy = constants.jumpVelocity * 0.7
return
physics.moveHorizontal(self, terrain, platforms)
physics.moveVertical(self, terrain, platforms)
if self.onGround and command.verticalAxis >= 0:
self.onLadder = False
if not physics.overlapsLadder(self, terrain):
self.onLadder = False