feat(sim): player controller and movement states
This commit is contained in:
1 parent
58f916c660
commit
c39b5ff1e6
1 file changed
+231
@@ -0,0 +1,231 @@
|
|||||||
|
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
|
||||||
Reference in new issue
Block a user