mirror of
https://github.com/BreizhHardware/project_sanic.git
synced 2026-03-18 21:50:33 +01:00
Feat(Projectile) - Add Projectile class for enemy attacks; update enemy attack logic to create projectiles
This commit is contained in:
BIN
assets/player/Sanic Head.png
Normal file
BIN
assets/player/Sanic Head.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
18
main.py
18
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))
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
46
src/Entity/Projectile.py
Normal file
46
src/Entity/Projectile.py
Normal file
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user