Update player.py
parent
ee37f5688a
commit
9a40840b8e
229
player.py
229
player.py
|
|
@ -4,108 +4,221 @@ from settings import *
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
# Position & Size
|
# =========================
|
||||||
|
# POSITION
|
||||||
|
# =========================
|
||||||
|
self.pos = pygame.Vector2(x, y)
|
||||||
self.rect = pygame.Rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT)
|
self.rect = pygame.Rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT)
|
||||||
|
|
||||||
# Movement
|
# =========================
|
||||||
|
# MOVEMENT
|
||||||
|
# =========================
|
||||||
self.velocity = pygame.Vector2(0, 0)
|
self.velocity = pygame.Vector2(0, 0)
|
||||||
self.acceleration = pygame.Vector2(0, 0)
|
|
||||||
|
|
||||||
# State
|
|
||||||
self.on_ground = False
|
self.on_ground = False
|
||||||
self.facing_right = True
|
self.facing_right = True
|
||||||
|
|
||||||
# Stats
|
# Movement tuning
|
||||||
|
self.move_accel = 2800
|
||||||
|
self.air_accel = 2200
|
||||||
|
self.max_run_speed = 320
|
||||||
|
self.ground_friction = -12
|
||||||
|
self.air_control = 0.65
|
||||||
|
|
||||||
|
self.gravity = 2200
|
||||||
|
self.max_fall_speed = 1000
|
||||||
|
self.fast_fall_speed = 1400
|
||||||
|
|
||||||
|
self.jump_force = -700
|
||||||
|
self.jump_cut_multiplier = 0.5
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# JUMP TECH
|
||||||
|
# =========================
|
||||||
|
self.coyote_time_max = 0.1
|
||||||
|
self.jump_buffer_max = 0.12
|
||||||
|
|
||||||
|
self.coyote_timer = 0
|
||||||
|
self.jump_buffer_timer = 0
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# COMBAT
|
||||||
|
# =========================
|
||||||
self.health = MAX_HEALTH
|
self.health = MAX_HEALTH
|
||||||
|
self.max_health = MAX_HEALTH
|
||||||
|
|
||||||
|
self.knockback_timer = 0
|
||||||
|
self.invulnerable_timer = 0
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# STATE
|
||||||
|
# =========================
|
||||||
|
self.state = "idle"
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# UPDATE
|
# UPDATE
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
def update(self, dt, world):
|
def update(self, dt, world):
|
||||||
self.handle_input()
|
dt = min(dt, 0.05)
|
||||||
self.apply_physics(dt)
|
|
||||||
self.handle_collisions(world)
|
self.handle_timers(dt)
|
||||||
|
self.handle_input(dt)
|
||||||
|
self.apply_gravity(dt)
|
||||||
|
|
||||||
|
# ---- Horizontal ----
|
||||||
|
self.pos.x += self.velocity.x * dt
|
||||||
|
self.rect.x = round(self.pos.x)
|
||||||
|
self.handle_horizontal_collisions(world)
|
||||||
|
|
||||||
|
# ---- Vertical ----
|
||||||
|
self.pos.y += self.velocity.y * dt
|
||||||
|
self.rect.y = round(self.pos.y)
|
||||||
|
self.handle_vertical_collisions(world)
|
||||||
|
|
||||||
|
self.update_state()
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# TIMERS
|
||||||
|
# ==========================================================
|
||||||
|
def handle_timers(self, dt):
|
||||||
|
if self.coyote_timer > 0:
|
||||||
|
self.coyote_timer -= dt
|
||||||
|
|
||||||
|
if self.jump_buffer_timer > 0:
|
||||||
|
self.jump_buffer_timer -= dt
|
||||||
|
|
||||||
|
if self.knockback_timer > 0:
|
||||||
|
self.knockback_timer -= dt
|
||||||
|
|
||||||
|
if self.invulnerable_timer > 0:
|
||||||
|
self.invulnerable_timer -= dt
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# INPUT
|
# INPUT
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
def handle_input(self):
|
def handle_input(self, dt):
|
||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
|
|
||||||
self.acceleration.x = 0
|
if self.knockback_timer > 0:
|
||||||
|
return
|
||||||
|
|
||||||
# Horizontal movement
|
move = 0
|
||||||
if keys[pygame.K_a]:
|
if keys[pygame.K_a]:
|
||||||
self.acceleration.x = -PLAYER_ACCELERATION
|
move -= 1
|
||||||
self.facing_right = False
|
self.facing_right = False
|
||||||
|
|
||||||
if keys[pygame.K_d]:
|
if keys[pygame.K_d]:
|
||||||
self.acceleration.x = PLAYER_ACCELERATION
|
move += 1
|
||||||
self.facing_right = True
|
self.facing_right = True
|
||||||
|
|
||||||
# Apply friction
|
accel = self.move_accel if self.on_ground else self.air_accel * self.air_control
|
||||||
self.acceleration.x += self.velocity.x * PLAYER_FRICTION
|
|
||||||
|
|
||||||
# Jump
|
if move != 0:
|
||||||
if keys[pygame.K_SPACE] and self.on_ground:
|
self.velocity.x += move * accel * dt
|
||||||
self.velocity.y = JUMP_FORCE
|
else:
|
||||||
|
if self.on_ground:
|
||||||
|
self.velocity.x += self.velocity.x * self.ground_friction * dt
|
||||||
|
|
||||||
|
# Clamp horizontal speed
|
||||||
|
self.velocity.x = max(-self.max_run_speed,
|
||||||
|
min(self.max_run_speed, self.velocity.x))
|
||||||
|
|
||||||
|
# Jump buffering
|
||||||
|
if keys[pygame.K_SPACE]:
|
||||||
|
self.jump_buffer_timer = self.jump_buffer_max
|
||||||
|
|
||||||
|
# Variable jump height (jump cut)
|
||||||
|
if not keys[pygame.K_SPACE] and self.velocity.y < 0:
|
||||||
|
self.velocity.y *= self.jump_cut_multiplier
|
||||||
|
|
||||||
|
# Fast fall
|
||||||
|
if keys[pygame.K_s] and not self.on_ground:
|
||||||
|
self.velocity.y = min(self.velocity.y + 3000 * dt,
|
||||||
|
self.fast_fall_speed)
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# GRAVITY
|
||||||
|
# ==========================================================
|
||||||
|
def apply_gravity(self, dt):
|
||||||
|
self.velocity.y += self.gravity * dt
|
||||||
|
|
||||||
|
if self.velocity.y > self.max_fall_speed:
|
||||||
|
self.velocity.y = self.max_fall_speed
|
||||||
|
|
||||||
|
if self.on_ground:
|
||||||
|
self.coyote_timer = self.coyote_time_max
|
||||||
|
|
||||||
|
# Perform jump if allowed
|
||||||
|
if self.jump_buffer_timer > 0 and self.coyote_timer > 0:
|
||||||
|
self.velocity.y = self.jump_force
|
||||||
|
self.jump_buffer_timer = 0
|
||||||
|
self.coyote_timer = 0
|
||||||
self.on_ground = False
|
self.on_ground = False
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# PHYSICS
|
|
||||||
# ==========================================================
|
|
||||||
def apply_physics(self, dt):
|
|
||||||
# Apply gravity
|
|
||||||
self.acceleration.y = GRAVITY
|
|
||||||
|
|
||||||
# Update velocity
|
|
||||||
self.velocity += self.acceleration * dt
|
|
||||||
|
|
||||||
# Clamp fall speed
|
|
||||||
if self.velocity.y > MAX_FALL_SPEED:
|
|
||||||
self.velocity.y = MAX_FALL_SPEED
|
|
||||||
|
|
||||||
# Move horizontally
|
|
||||||
self.rect.x += self.velocity.x * dt
|
|
||||||
|
|
||||||
# Move vertically
|
|
||||||
self.rect.y += self.velocity.y * dt
|
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# COLLISIONS
|
# COLLISIONS
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
def handle_collisions(self, world):
|
def handle_horizontal_collisions(self, world):
|
||||||
self.on_ground = False
|
for tile in world.get_nearby_tiles(self.rect):
|
||||||
|
if tile["solid"] and self.rect.colliderect(tile["rect"]):
|
||||||
# Get nearby tiles once
|
if self.velocity.x > 0:
|
||||||
nearby_tiles = world.get_nearby_tiles(self.rect)
|
|
||||||
|
|
||||||
# Horizontal collisions
|
|
||||||
for tile in nearby_tiles:
|
|
||||||
if tile["rect"].colliderect(self.rect) and tile["solid"]:
|
|
||||||
if self.velocity.x > 0: # Moving right
|
|
||||||
self.rect.right = tile["rect"].left
|
self.rect.right = tile["rect"].left
|
||||||
elif self.velocity.x < 0: # Moving left
|
elif self.velocity.x < 0:
|
||||||
self.rect.left = tile["rect"].right
|
self.rect.left = tile["rect"].right
|
||||||
|
|
||||||
|
self.pos.x = self.rect.x
|
||||||
self.velocity.x = 0
|
self.velocity.x = 0
|
||||||
|
|
||||||
# Vertical collisions
|
def handle_vertical_collisions(self, world):
|
||||||
for tile in nearby_tiles:
|
self.on_ground = False
|
||||||
if tile["rect"].colliderect(self.rect) and tile["solid"]:
|
|
||||||
if self.velocity.y > 0: # Falling
|
for tile in world.get_nearby_tiles(self.rect):
|
||||||
|
if tile["solid"] and self.rect.colliderect(tile["rect"]):
|
||||||
|
if self.velocity.y > 0:
|
||||||
self.rect.bottom = tile["rect"].top
|
self.rect.bottom = tile["rect"].top
|
||||||
self.on_ground = True
|
self.on_ground = True
|
||||||
elif self.velocity.y < 0: # Jumping up
|
elif self.velocity.y < 0:
|
||||||
self.rect.top = tile["rect"].bottom
|
self.rect.top = tile["rect"].bottom
|
||||||
|
|
||||||
|
self.pos.y = self.rect.y
|
||||||
self.velocity.y = 0
|
self.velocity.y = 0
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# STATE MACHINE
|
||||||
|
# ==========================================================
|
||||||
|
def update_state(self):
|
||||||
|
if not self.on_ground:
|
||||||
|
self.state = "jump" if self.velocity.y < 0 else "fall"
|
||||||
|
elif abs(self.velocity.x) > 10:
|
||||||
|
self.state = "run"
|
||||||
|
else:
|
||||||
|
self.state = "idle"
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# DAMAGE / KNOCKBACK
|
||||||
|
# ==========================================================
|
||||||
|
def take_damage(self, amount, direction):
|
||||||
|
if self.invulnerable_timer > 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.health -= amount
|
||||||
|
self.invulnerable_timer = 0.5
|
||||||
|
|
||||||
|
self.velocity.x = direction * 500
|
||||||
|
self.velocity.y = -400
|
||||||
|
self.knockback_timer = 0.2
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# DRAW
|
# DRAW
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
def draw(self, screen, camera):
|
def draw(self, screen, camera):
|
||||||
draw_rect = camera.apply(self.rect)
|
draw_rect = camera.apply(self.rect)
|
||||||
|
|
||||||
pygame.draw.rect(screen, (255, 50, 50), draw_rect)
|
# Flash when invulnerable
|
||||||
|
if self.invulnerable_timer > 0:
|
||||||
|
color = (255, 255, 255)
|
||||||
|
else:
|
||||||
|
color = (255, 50, 50)
|
||||||
|
|
||||||
|
pygame.draw.rect(screen, color, draw_rect)
|
||||||
|
|
||||||
if SHOW_COLLIDERS:
|
if SHOW_COLLIDERS:
|
||||||
pygame.draw.rect(screen, (0, 255, 0), draw_rect, 2)
|
pygame.draw.rect(screen, (0, 255, 0), draw_rect, 2)
|
||||||
Loading…
Reference in New Issue