diff --git a/assets/player/Sanic Head.png b/assets/player/Sanic Head.png new file mode 100644 index 0000000..122db77 Binary files /dev/null and b/assets/player/Sanic Head.png differ diff --git a/main.py b/main.py index 13127d6..285a3df 100644 --- a/main.py +++ b/main.py @@ -18,6 +18,7 @@ from src.constant import ( from src.Menu.Menu import Menu from src.Menu.Leaderboard import Leaderboard from src.Camera import Camera +from src.Entity.Projectile import Projectile def main(): @@ -40,6 +41,7 @@ def main(): # Initialize game components P1, PT1, platforms, all_sprites, background = initialize_game("map_test.json") + projectiles = pygame.sprite.Group() # Main game loop while True: @@ -79,6 +81,9 @@ def main(): elif event.type == USEREVENT: if event.action == "player_death": current_state = MENU + if event.dict.get("action") == "create_projectile": + projectile = event.dict.get("projectile") + projectiles.add(projectile) # Handle menu events if current_state == MENU: @@ -138,6 +143,19 @@ def main(): else: sprite.update() + projectiles.update(WIDTH, HEIGHT, P1, camera) + + for projectile in projectiles: + # Calculate position adjusted for camera (comme pour les autres sprites) + camera_adjusted_rect = projectile.rect.copy() + camera_adjusted_rect.x += camera.camera.x # SOUSTRAIT au lieu d'ajouter + camera_adjusted_rect.y += camera.camera.y # SOUSTRAIT au lieu d'ajouter + displaysurface.blit(projectile.surf, camera_adjusted_rect) + + print( + f"Projectile: pos={projectile.pos}, rect={projectile.rect}, camera={camera.camera}" + ) + # Display FPS and coordinates (fixed position UI elements) fps = int(FramePerSec.get_fps()) fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255)) diff --git a/src/Entity/Enemy.py b/src/Entity/Enemy.py index c694389..8c74aa8 100644 --- a/src/Entity/Enemy.py +++ b/src/Entity/Enemy.py @@ -1,8 +1,8 @@ import pygame -import math from src.Entity.Entity import Entity from src.constant import FPS from pygame.math import Vector2 as vec +from src.Entity.Projectile import Projectile class Enemy(Entity): @@ -37,7 +37,7 @@ class Enemy(Entity): self.current_patrol_point = 0 self.patrol_points = enemy_data.get("patrol_points", []) self.detection_radius = enemy_data.get("detection_radius", 200) - self.attack_interval = enemy_data.get("attack_interval", 2.0) + self.attack_interval = enemy_data.get("attack_interval", 0.3) self.attack_range = enemy_data.get("attack_range", 300) self.attack_timer = 0 self.direction = 1 # 1 = right, -1 = left @@ -89,7 +89,6 @@ class Enemy(Entity): if distance_to_player <= self.detection_radius: self.detected_player = True - print("Player detected!") direction = vec(player.pos.x - self.pos.x, player.pos.y - self.pos.y) if direction.length() > 0: @@ -113,11 +112,28 @@ class Enemy(Entity): self.attack(player) def attack(self, player): - """Réalise une attaque sur le joueur""" + """Do an attack action on the player""" self.is_attacking = True - # TO DO: Implement attack logic based on enemy type - # Example: turret shoots a projectile - print("Enemy attacks player!") + + # For turret-type enemies, create a projectile + if self.enemy_type == "turret": + # Calculate direction to player + direction = vec(player.pos.x - self.pos.x, player.pos.y - self.pos.y) + + projectile = Projectile( + pos=vec(self.pos.x, self.pos.y), + direction=direction, + speed=self.speed, + damage=self.damage, + ) + + # Add projectile to the sprite group (to be placed in main.py) + pygame.event.post( + pygame.event.Event( + pygame.USEREVENT, + {"action": "create_projectile", "projectile": projectile}, + ) + ) def take_damage(self, amount): """Deal damage to the enemy and check if it should be destroyed""" diff --git a/src/Entity/Player.py b/src/Entity/Player.py index 1d06981..772e133 100644 --- a/src/Entity/Player.py +++ b/src/Entity/Player.py @@ -1,5 +1,5 @@ from src.Entity.Entity import Entity -from src.constant import WIDTH, vec, ACC, FRIC, platforms, FPS +from src.constant import WIDTH, vec, ACC, FRIC, platforms, FPS, life_icon_width from pygame import * import pygame import os @@ -33,6 +33,7 @@ class Player(Entity): self.invulnerable = False self.invulnerable_timer = 0 self.invulnerable_duration = 1.5 + self.life_icon = None # Load images self.load_images() @@ -92,13 +93,26 @@ class Player(Entity): dash_frame_height = dash_sheet.get_height() - for i in range(4): # Assuming 4 frames + for i in range(4): frame = dash_sheet.subsurface( (i * 2000, 0, dash_frame_height, dash_frame_height) ) frame = pygame.transform.scale(frame, (80, 80)) self.dash_frames.append(frame) + # Load life icon + if os.path.isfile("assets/player/Sanic Head.png"): + self.life_icon = pygame.image.load( + "assets/player/Sanic Head.png" + ).convert_alpha() + self.life_icon = pygame.transform.scale( + self.life_icon, (life_icon_width, life_icon_width) + ) + else: + # Backup: use a red square + self.life_icon = pygame.Surface((life_icon_width, life_icon_width)) + self.life_icon.fill((255, 0, 0)) + except Exception as e: print(f"Error loading player images: {e}") @@ -196,7 +210,9 @@ class Player(Entity): pygame.draw.rect(surface, (100, 100, 100), (x, y, bar_width, bar_height)) # Filled portion (based on cooldown progress) - pygame.draw.rect(surface, (58, 83, 200), (x, y, bar_width * cooldown_progress, bar_height)) + pygame.draw.rect( + surface, (58, 83, 200), (x, y, bar_width * cooldown_progress, bar_height) + ) def update(self): hits = pygame.sprite.spritecollide(self, platforms, False) @@ -231,17 +247,26 @@ class Player(Entity): print("Player died! Returning to menu...") def draw_lives(self, surface): - """Display remaning live on the top right of the screen""" - radius = 10 + """Draws the player's remaining lives as icons in the top right corner.""" spacing = 5 - start_x = surface.get_width() - (self.max_lives * (radius * 2 + spacing)) - start_y = 20 + start_x = surface.get_width() - (self.max_lives * (life_icon_width + spacing)) + start_y = 10 for i in range(self.max_lives): - color = (255, 0, 0) if i < self.lives else (100, 100, 100) - pygame.draw.circle( - surface, - color, - (start_x + i * (radius * 2 + spacing) + radius, start_y), - radius, - ) + if i < self.lives: + # Vie active: afficher l'icône normale + surface.blit( + self.life_icon, (start_x + i * (life_icon_width + spacing), start_y) + ) + else: + # Vie perdue: afficher l'icône grisée + grayscale_icon = self.life_icon.copy() + # Appliquer un filtre gris + for x in range(grayscale_icon.get_width()): + for y in range(grayscale_icon.get_height()): + color = grayscale_icon.get_at((x, y)) + gray = (color[0] + color[1] + color[2]) // 3 + grayscale_icon.set_at((x, y), (gray, gray, gray, color[3])) + surface.blit( + grayscale_icon, (start_x + i * (life_icon_width + spacing), start_y) + ) diff --git a/src/Entity/Projectile.py b/src/Entity/Projectile.py new file mode 100644 index 0000000..64ab92d --- /dev/null +++ b/src/Entity/Projectile.py @@ -0,0 +1,46 @@ +import pygame +from pygame.math import Vector2 as vec + + +class Projectile(pygame.sprite.Sprite): + def __init__(self, pos, direction, speed, damage, color=(0, 0, 255)): + super().__init__() + + # Base attributes + self.pos = vec(pos) + self.direction = direction.normalize() if direction.length() > 0 else vec(1, 0) + self.speed = speed + self.damage = damage + + # Create projectile surface + self.surf = pygame.Surface((10, 10)) + self.surf.fill(color) + self.rect = self.surf.get_rect(center=(pos.x, pos.y)) + + def update(self, screen_width, screen_height, player=None, camera=None): + """Move the projectile and check for collisions""" + # Movement of the projectile + self.pos += self.direction * self.speed + self.rect.center = (int(self.pos.x), int(self.pos.y)) + + # Check if projectile is out of screen + if camera: + # Screen position of the projectile = position + camera position + screen_x = self.pos.x + camera.camera.x + screen_y = self.pos.y + camera.camera.y + + # Safety margin to avoid killing the projectile too early + margin = 50 + + if ( + screen_x < -margin + or screen_x > screen_width + margin + or screen_y < -margin + or screen_y > screen_height + margin + ): + self.kill() + + # Check for collision with player + if player and self.rect.colliderect(player.rect): + player.take_damage(self.damage) + self.kill() diff --git a/src/Map/parser.py b/src/Map/parser.py index 779e665..223ca53 100644 --- a/src/Map/parser.py +++ b/src/Map/parser.py @@ -126,6 +126,7 @@ class MapParser: background = pygame.image.load(map_data["background"]).convert_alpha() background = pygame.transform.scale(background, (WIDTH, HEIGHT)) self.background = background + print("Background image loaded") else: print(f"Background image not found: {map_data['background']}") else: diff --git a/src/constant.py b/src/constant.py index efd4c36..c008544 100644 --- a/src/constant.py +++ b/src/constant.py @@ -16,6 +16,7 @@ all_sprites = pygame.sprite.Group() fullscreen = False ORIGINAL_WIDTH = WIDTH ORIGINAL_HEIGHT = HEIGHT +life_icon_width = 50 try: font = pygame.font.SysFont("Arial", 20) diff --git a/src/game.py b/src/game.py index 23f65a2..72201a3 100644 --- a/src/game.py +++ b/src/game.py @@ -1,11 +1,7 @@ -import pygame -import sys -from pygame.locals import * from src.Entity.Platform import Platform from src.Entity.Player import Player from src.constant import displaysurface, FramePerSec, font, FPS, platforms, all_sprites from src.Map.parser import MapParser -from src.Camera import Camera def initialize_game(map_file="map_test.json"):