Merge pull request #28 from BreizhHardware/dev_felix

Dev felix
This commit is contained in:
Clément Hervouet
2025-04-07 10:17:05 +02:00
committed by GitHub
11 changed files with 186 additions and 51 deletions

BIN
assets/map/enemy/boss.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/map/enemy/turret.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -11,7 +11,7 @@
"y": 780,
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
"texture": "assets/map/platform/grass_texture.png"
},
{
"id": "main_ground_2",
@@ -19,7 +19,7 @@
"y": 900,
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
"texture": "assets/map/platform/stone_texture.png"
},
{
"id": "platform1",
@@ -27,7 +27,7 @@
"y": 600,
"width": 200,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"texture": "assets/map/platform/grass_texture.png",
"is_moving": false
},
{
@@ -36,7 +36,7 @@
"y": 500,
"width": 150,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"texture": "assets/map/platform/wood_texture.png",
"is_moving": true,
"movement": {
"type": "linear",
@@ -54,7 +54,7 @@
"y": 750,
"width": 150,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"texture": "assets/map/platform/grass_texture.png",
"is_moving": true,
"movement": {
"type": "linear",
@@ -72,7 +72,7 @@
"y": 400,
"width": 100,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"texture": "assets/map/platform/wood_texture.png",
"is_moving": true,
"movement": {
"type": "circular",
@@ -124,7 +124,7 @@
"behavior": "stationary",
"attack_interval": 2.0,
"attack_range": 300,
"sprite_sheet": "assets/map/enemy/turret_enemy.png",
"sprite_sheet": "assets/map/enemy/turret.gif",
"size": [50,100]
}
],
@@ -143,14 +143,6 @@
"x": 400,
"y": 540,
"sprite": "assets/map/collectibles/Sanic_Coin.png"
},
{
"id": "power_up1",
"type": "speed_boost",
"x": 900,
"y": 450,
"duration": 5.0,
"sprite": "assets/map/collectibles/speed_boost.png"
}
],
@@ -171,13 +163,13 @@
},
"exits": [
{
"x": 2300,
"y": 700,
"width": 50,
"height": 80,
"next_level": "Level 2",
"sprite": "assets/map/exit/Zeldo.png"
{
"x": 2300,
"y": 700,
"width": 50,
"height": 80,
"next_level": "Level 2",
"sprite": "assets/map/exit/Zeldo.png"
}
]
}

View File

@@ -1,8 +1,10 @@
black==25.1.0
click==8.1.8
colorama==0.4.6
mypy-extensions==1.0.0
numpy==2.2.4
packaging==24.2
pathspec==0.12.1
pillow==11.1.0
platformdirs==4.3.7
pygame==2.6.1
pygame==2.6.1

View File

@@ -1,4 +1,6 @@
import pygame
import os
from PIL import Image, ImageSequence
import random
from src.Entity.Entity import Entity
from pygame.math import Vector2 as vec
@@ -7,7 +9,8 @@ from src.Entity.Projectile import Projectile
class Enemy(Entity):
def __init__(self, enemy_data):
super().__init__()
self.size = enemy_data.get("size", [50, 50])
super().__init__(self.size)
# Base attributes
self.enemy_type = enemy_data.get("type", "turret")
@@ -15,19 +18,35 @@ class Enemy(Entity):
self.damage = enemy_data.get("damage", 1)
self.behavior = enemy_data.get("behavior", "stationary")
self.speed = enemy_data.get("speed", 1.5)
self.size = enemy_data.get("size", [50, 50])
# Initial position
self.pos = vec(enemy_data.get("x", 0), enemy_data.get("y", 0))
# Animation attributes
self.frames = []
self.current_frame = 0
self.animation_speed = enemy_data.get("animation_speed", 0.1)
self.animation_timer = 0
sprite_path = enemy_data.get("sprite_sheet", "assets/enemy/default_enemy.png")
try:
self.surf = pygame.image.load(sprite_path).convert_alpha()
self.surf = pygame.transform.scale(self.surf, self.size)
except:
# Default sprite
self.surf = pygame.Surface((40, 40))
self.surf.fill((255, 0, 0))
# Load sprite sheet or GIF depending on enemy type and file extension
if sprite_path.lower().endswith(".gif") and self.enemy_type == "turret":
self.load_gif_frames(sprite_path)
if self.frames:
self.surf = self.frames[0]
else:
# Default sprite
self.surf = pygame.Surface((40, 40))
self.surf.fill((255, 0, 0))
else:
try:
self.surf = pygame.image.load(sprite_path).convert_alpha()
self.surf = pygame.transform.scale(self.surf, self.size)
except:
# Default sprite
self.surf = pygame.Surface((40, 40))
self.surf.fill((255, 0, 0))
# Initial rectangle
self.rect = self.surf.get_rect(center=(self.pos.x, self.pos.y))
@@ -45,7 +64,26 @@ class Enemy(Entity):
self.is_attacking = False
self.detected_player = False
def update(self, player=None):
def load_gif_frames(self, gif_path):
"""Load frames from a GIF file"""
try:
gif = Image.open(gif_path)
frame_count = 0
for frame in ImageSequence.Iterator(gif):
frame_surface = pygame.image.fromstring(
frame.convert("RGBA").tobytes(), frame.size, "RGBA"
)
frame_surface = pygame.transform.scale(frame_surface, (80, 80))
self.frames.append(frame_surface)
frame_count += 1
print(f"Chargé {frame_count} frames depuis {gif_path}")
except Exception as e:
print(f"Erreur lors du chargement du GIF: {e}")
self.frames = []
def update(self, player=None, dt=1 / 60):
"""Updates enemy's status and position"""
if player:
self.check_collision_with_player(player)
@@ -57,6 +95,14 @@ class Enemy(Entity):
elif self.behavior == "stationary" and player:
self.stationary_attack(player)
# Animation management for turret enemies
if self.enemy_type == "turret" and self.frames:
self.animation_timer += dt
if self.animation_timer >= self.animation_speed:
self.animation_timer = 0
self.current_frame = (self.current_frame + 1) % len(self.frames)
self.surf = self.frames[self.current_frame]
# Update rect position based on entity position
self.rect.centerx = self.pos.x
self.rect.centery = self.pos.y

View File

@@ -348,6 +348,7 @@ class LevelEditor:
"health": 1,
"damage": 1,
"sprite_sheet": f"assets/map/enemy/{enemy_type}_enemy.png",
"size": [50, 100],
}
if enemy_type == "walker":
@@ -365,6 +366,7 @@ class LevelEditor:
enemy_data["behavior"] = "stationary"
enemy_data["attack_interval"] = 2.0
enemy_data["attack_range"] = 300
enemy_data["sprite_sheet"] = "assets/map/enemy/turret.gif"
level_data["enemies"].append(enemy_data)
@@ -600,7 +602,7 @@ class LevelEditor:
self.selected_object, EditorCollectible
):
if event.key == K_t:
types = ["coin", "speed_boost", "health", "shield"]
types = ["coin"]
current_index = (
types.index(self.selected_object.collectible_type)
if self.selected_object.collectible_type in types
@@ -612,12 +614,6 @@ class LevelEditor:
# Update appearance based on type
if self.selected_object.collectible_type == "coin":
self.selected_object.image.fill((255, 215, 0)) # Gold
elif self.selected_object.collectible_type == "speed_boost":
self.selected_object.image.fill((0, 0, 255)) # Blue
elif self.selected_object.collectible_type == "health":
self.selected_object.image.fill((255, 0, 0)) # Red
elif self.selected_object.collectible_type == "shield":
self.selected_object.image.fill((128, 128, 128)) # Gray
elif self.selected_object and isinstance(self.selected_object, EditorExit):
if event.key == K_n:

View File

@@ -120,13 +120,41 @@ class InfiniteMapGenerator:
enemy_types = ["walker", "flyer", "turret"]
for i in range(num_enemies):
type = random.choice(enemy_types)
enemy = {
"id": f"enemy{i+1}",
"type": random.choice(enemy_types),
"type": type,
"x": random.randint(600, self.width - 200),
"y": random.randint(100, 400),
"patrol_distance": random.randint(100, 300),
}
if type == "flyer":
enemy["sprite_sheet"] = "assets/map/enemy/flying_enemy.png"
enemy["health"] = 1
enemy["damage"] = 1
enemy["behavior"] = "chase"
enemy["detection_radius"] = random.randint(100, 500)
enemy["speed"] = 2.0
enemy["size"] = [50, 50]
elif type == "walker":
enemy["sprite_sheet"] = "assets/map/enemy/walker_enemy.png"
enemy["health"] = 1
enemy["damage"] = 1
enemy["behavior"] = "patrol"
enemy["patrol_points"] = [
{"x": enemy["x"], "y": enemy["y"]},
{"x": enemy["x"] + enemy["patrol_distance"], "y": enemy["y"]},
]
enemy["speed"] = 1.5
enemy["size"] = [50, 50]
elif type == "turret":
enemy["sprite_sheet"] = "assets/map/enemy/turret.gif"
enemy["health"] = 1
enemy["damage"] = 1
enemy["behavior"] = "stationary"
enemy["attack_interval"] = random.uniform(0.5, 3.0)
enemy["attack_range"] = random.randint(100, 500)
enemy["size"] = [50, 50]
enemies.append(enemy)
return enemies
@@ -134,7 +162,7 @@ class InfiniteMapGenerator:
def _generate_collectibles(self, difficulty):
collectibles = []
num_collectibles = 5 + difficulty
collectible_types = ["coin", "health", "shield"]
collectible_types = ["coin"]
for i in range(num_collectibles):
rand = random.choice(collectible_types)

View File

@@ -1,6 +1,7 @@
import json
import pygame
import os
from PIL import Image, ImageSequence
from src.Entity.Platform import Platform
from src.Entity.Player import Player
from src.Entity.Enemy import Enemy
@@ -8,6 +9,7 @@ from src.Entity.Checkpoint import Checkpoint
from src.Entity.Exit import Exit
from src.Entity.Coin import Coin
class MapParser:
def __init__(self, game_resources):
self.game_resources = game_resources
@@ -19,12 +21,31 @@ class MapParser:
self.checkpoints = pygame.sprite.Group()
self.player = None
self.boss_gif = Image.open("assets/map/enemy/boss.gif")
self.boss_frames = [
frame.copy() for frame in ImageSequence.Iterator(self.boss_gif)
]
self.boss_frame_index = 0
self.player_image = pygame.image.load(
"assets/player/Sanic Base.png"
).convert_alpha()
self.princess_image = pygame.image.load(
"assets/map/exit/Zeldo.png"
).convert_alpha()
self.cinematic_played = False
def load_map(self, map_file):
"""Load and parse a map from JSON file"""
try:
with open(map_file, "r") as file:
map_data = json.load(file)
# If it's level 1, play the cinematic
if map_data.get("name") == "Level 1" and not self.cinematic_played:
self.play_cinematic(self.game_resources)
self.cinematic_played = True
# Create all game objects from map data
self.create_map_objects(map_data, map_file)
@@ -108,14 +129,16 @@ class MapParser:
print(f"Found {len(map_data['collectibles'])} collectibles")
for collectible_data in map_data["collectibles"]:
if collectible_data["type"] == "coin":
print(f"Creating coin at ({collectible_data['x']}, {collectible_data['y']})")
print(
f"Creating coin at ({collectible_data['x']}, {collectible_data['y']})"
)
sprite_path = collectible_data.get("sprite", "")
print(f"Using sprite path: {sprite_path}")
# Create and add the coin
coin = Coin(
pos=(collectible_data["x"], collectible_data["y"]),
texturePath=sprite_path
texturePath=sprite_path,
)
self.collectibles.add(coin)
self.all_sprites.add(coin)
@@ -166,3 +189,45 @@ class MapParser:
self.player.pos.x = spawn["x"]
self.player.pos.y = spawn["y"]
self.all_sprites.add(self.player)
def play_cinematic(self, game_resources):
"""Play the cinematic for level 1"""
screen = game_resources.displaysurface
font = pygame.font.Font(None, 36)
lore_text = [
"Once upon a time in a land far away...",
"A brave hero named Sanic...",
"And a beautiful princess named Zeldo...",
"Has been captured by the evil boss...",
"Wheatly !!!",
"Sanic must rescue Zeldo...",
]
self.player_image = pygame.transform.scale(self.player_image, (200, 200))
self.princess_image = pygame.transform.scale(self.princess_image, (200, 200))
screen.fill((0, 0, 0))
for i, line in enumerate(lore_text):
if "Sanic" in line:
screen.blit(self.player_image, (100, 400))
if "Zeldo" in line:
screen.blit(self.princess_image, (700, 400))
if "Wheatly" in line:
for _ in range(46):
boss_frame = self.boss_frames[self.boss_frame_index]
boss_frame = boss_frame.convert("RGBA")
boss_frame = pygame.image.fromstring(
boss_frame.tobytes(), boss_frame.size, boss_frame.mode
)
boss_frame = pygame.transform.scale(boss_frame, (200, 200))
screen.blit(boss_frame, (400, 400))
pygame.display.flip()
pygame.time.wait(100)
self.boss_frame_index = (self.boss_frame_index + 1) % len(
self.boss_frames
)
text_surface = font.render(line, True, (255, 255, 255))
screen.blit(text_surface, (50, 50 + i * 40))
pygame.display.flip()
pygame.time.wait(2000)

View File

@@ -85,26 +85,22 @@ class Menu:
def draw(self, surface):
if self.background:
# Réduire le facteur de parallaxe
parallax_factor = 0.4
time_factor = pygame.time.get_ticks() / 1000
# Calculer le centre du background
center_x = (self.background.get_width() - surface.get_width()) / 2
center_y = (self.background.get_height() - surface.get_height()) / 2
# Appliquer un léger mouvement de parallaxe autour du centre
bg_x = -center_x + math.sin(time_factor) * 50 * parallax_factor
bg_y = -center_y + math.cos(time_factor) * 30 * parallax_factor
# Afficher le background
surface.blit(self.background, (bg_x, bg_y))
else:
surface.fill((0, 0, 0))
# Draw title
title = pygame.font.SysFont("Arial", 72).render(
"Project Sanic", True, (0, 191, 255)
"Sanic et la princesse Zeldo", True, (0, 191, 255)
)
title_rect = title.get_rect(
center=(self.game_resources.WIDTH // 2, self.game_resources.HEIGHT // 4)

View File

@@ -146,8 +146,16 @@ def handler():
if death_sound:
death_sound.play()
db = CheckpointDB()
checkpoint_data = db.get_checkpoint(level_file)
is_infinite_mode = (
hasattr(game_resources, "infinite_mode")
and game_resources.infinite_mode
)
if not is_infinite_mode:
db = CheckpointDB()
checkpoint_data = db.get_checkpoint(level_file)
else:
checkpoint_data = None
if event.dict.get("action") == "create_projectile":
projectile = event.dict.get("projectile")
@@ -479,6 +487,8 @@ def handler():
projectiles.empty()
current_state = PLAYING
else:
if hasattr(game_resources, "infinite_mode"):
game_resources.infinite_mode = False
current_state = MENU
pygame.display.update()