Projectdd/player.py

271 lines
8.4 KiB
Python

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)