import pygame from settings import * class Player: def __init__(self, x, y, weapon_manager=None): # ========================= # POSITION # ========================= self.pos = pygame.Vector2(x, y) self.rect = pygame.Rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT) # ========================= # MOVEMENT # ========================= self.velocity = pygame.Vector2(0, 0) self.on_ground = False self.facing_right = True # 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.max_health = MAX_HEALTH self.knockback_timer = 0 self.invulnerable_timer = 0 self.weapon_manager = weapon_manager # ========================= # STATE # ========================= self.state = "idle" # ========================================================== # UPDATE # ========================================================== def update(self, dt, world): dt = min(dt, 0.05) 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) # World border check if self.rect.left < 0: self.rect.left = 0 self.pos.x = 0 self.velocity.x = 0 if self.rect.right > world.width * TILE_SIZE: self.rect.right = world.width * TILE_SIZE self.pos.x = self.rect.x self.velocity.x = 0 # ---- Vertical ---- self.pos.y += self.velocity.y * dt self.rect.y = round(self.pos.y) self.handle_vertical_collisions(world) # World border - bottom if self.rect.bottom > world.height * TILE_SIZE: self.rect.bottom = world.height * TILE_SIZE self.pos.y = self.rect.y self.on_ground = True self.velocity.y = 0 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 # ========================================================== def handle_input(self, dt): keys = pygame.key.get_pressed() if self.knockback_timer > 0: return move = 0 if keys[pygame.K_a]: move -= 1 self.facing_right = False if keys[pygame.K_d]: move += 1 self.facing_right = True accel = self.move_accel if self.on_ground else self.air_accel * self.air_control if move != 0: self.velocity.x += move * accel * dt 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 # ========================================================== # COLLISIONS # ========================================================== def handle_horizontal_collisions(self, world): for tile in world.get_nearby_tiles(self.rect): if tile["solid"] and self.rect.colliderect(tile["rect"]): if self.velocity.x > 0: self.rect.right = tile["rect"].left elif self.velocity.x < 0: self.rect.left = tile["rect"].right self.pos.x = self.rect.x self.velocity.x = 0 def handle_vertical_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"]): if self.velocity.y > 0: self.rect.bottom = tile["rect"].top self.on_ground = True elif self.velocity.y < 0: self.rect.top = tile["rect"].bottom self.pos.y = self.rect.y 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 # ========================================================== def draw(self, screen, camera): draw_rect = camera.apply(self.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: pygame.draw.rect(screen, (0, 255, 0), draw_rect, 2) # Draw health bar health_bar_width = 28 health_bar_height = 4 health_percent = self.health / self.max_health health_bar_rect = pygame.Rect( draw_rect.x, draw_rect.y - 8, health_bar_width * health_percent, health_bar_height ) pygame.draw.rect(screen, (0, 255, 0), health_bar_rect) pygame.draw.rect(screen, (255, 0, 0), pygame.Rect( draw_rect.x, draw_rect.y - 8, health_bar_width, health_bar_height ), 1) if self.invulnerable_timer > 0: color = (255, 255, 255) else: color = (255, 50, 50) pygame.draw.rect(screen, color, draw_rect) if SHOW_COLLIDERS: pygame.draw.rect(screen, (0, 255, 0), draw_rect, 2)