Merge remote-tracking branch 'origin/dev' into dev_table_basse

This commit is contained in:
MateoLT
2025-03-31 11:52:47 +02:00
13 changed files with 1965 additions and 110 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,9 @@
.venv
.venv/*
.winvenv
.winvenv/*
.idea
.idea/*

243
main.py
View File

@@ -18,6 +18,8 @@ from src.Menu.Menu import Menu
from src.Menu.Leaderboard import Leaderboard
from src.Camera import Camera
from src.Database.CheckpointDB import CheckpointDB
from src.Map.Editor.LevelEditor import LevelEditor
from src.Menu.LevelEditorSelectionMenu import LevelEditorSelectionMenu
def main():
@@ -53,99 +55,155 @@ def main():
clear_checkpoint_database()
projectiles = pygame.sprite.Group()
pygame.joystick.quit()
pygame.joystick.init()
joysticks = []
try:
for i in range(pygame.joystick.get_count()):
joystick = pygame.joystick.Joystick(i)
joystick.init()
joysticks.append(joystick)
print(f"Manette détectée: {joystick.get_name()}")
print(f"Nombre de boutons: {joystick.get_numbuttons()}")
print(f"Nombre d'axes: {joystick.get_numaxes()}")
except pygame.error:
print("Erreur lors de l'initialisation des manettes")
# Main game loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
if current_state in [PLAYING, INFINITE]:
current_state = MENU
else:
pygame.quit()
sys.exit()
elif event.key == K_F11:
fullscreen = not fullscreen
if fullscreen:
# Store current window size before going fullscreen
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = displaysurface.get_size()
displaysurface = pygame.display.set_mode(
(0, 0), pygame.FULLSCREEN
)
else:
# Return to windowed mode with previous size
displaysurface = pygame.display.set_mode(
(ORIGINAL_WIDTH, ORIGINAL_HEIGHT), pygame.RESIZABLE
)
elif (
event.type == VIDEORESIZE
): # Fixed indentation - moved out of K_F11 condition
if not fullscreen:
displaysurface = pygame.display.set_mode(
(event.w, event.h), pygame.RESIZABLE
)
# Update window dimensions
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
elif event.type == USEREVENT:
if event.action == "player_death":
db = CheckpointDB()
checkpoint_pos = db.get_checkpoint(level_file)
running = True
while running:
try:
events = []
try:
events = pygame.event.get()
except Exception as e:
print(f"Erreur lors de la récupération des événements: {e}")
pygame.joystick.quit()
pygame.joystick.init()
continue
if checkpoint_pos:
# Respawn player at checkpoint
P1, platforms, all_sprites, background, checkpoints = (
reset_game_with_checkpoint(level_file, game_resources)
for event in events:
if event.type == QUIT:
running = False
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
if current_state in [PLAYING, INFINITE]:
current_state = MENU
else:
pygame.quit()
sys.exit()
elif event.key == K_F11:
fullscreen = not fullscreen
if fullscreen:
# Store current window size before going fullscreen
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = displaysurface.get_size()
displaysurface = pygame.display.set_mode(
(0, 0), pygame.FULLSCREEN
)
else:
# Return to windowed mode with previous size
displaysurface = pygame.display.set_mode(
(ORIGINAL_WIDTH, ORIGINAL_HEIGHT), pygame.RESIZABLE
)
elif (
event.type == VIDEORESIZE
): # Fixed indentation - moved out of K_F11 condition
if not fullscreen:
displaysurface = pygame.display.set_mode(
(event.w, event.h), pygame.RESIZABLE
)
projectiles.empty()
else:
# No checkpoint found, return to menu
current_state = MENU
if event.dict.get("action") == "create_projectile":
projectile = event.dict.get("projectile")
projectiles.add(projectile)
# Update window dimensions
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
elif event.type == USEREVENT:
if event.action == "player_death":
db = CheckpointDB()
checkpoint_pos = db.get_checkpoint(level_file)
# Handle menu events
if current_state == MENU:
if current_menu == "main":
action = main_menu.handle_event(event)
if action == "level_select":
level_select_menu = LevelSelectMenu(game_resources)
if checkpoint_pos:
# Respawn player at checkpoint
P1, platforms, all_sprites, background, checkpoints = (
reset_game_with_checkpoint(level_file, game_resources)
)
projectiles.empty()
else:
# No checkpoint found, return to menu
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:
if current_menu == "main":
action = main_menu.handle_event(event)
if action == "level_select":
level_select_menu = LevelSelectMenu(game_resources)
current_menu = "level_select"
elif action == "infinite":
current_state = INFINITE
elif action == "leaderboard":
current_state = LEADERBOARD
elif action == "quit":
pygame.quit()
sys.exit()
elif current_menu == "level_select":
action = level_select_menu.handle_event(event)
if action == "back_to_main":
current_menu = "main"
elif (
isinstance(action, dict)
and action.get("action") == "select_level"
):
level_file = action.get("level_file")
print(level_file)
(
P1,
PT1,
platforms,
all_sprites,
background,
checkpoints,
exits,
) = initialize_game(game_resources, level_file)
projectiles.empty()
current_state = PLAYING
elif action == "open_editor":
editor_select_menu = LevelEditorSelectionMenu(
game_resources
)
current_state = "editor_select"
# Handle leaderboard events
elif current_state == LEADERBOARD:
action = leaderboard.handle_event(event)
if action == "menu":
current_state = MENU
elif current_state == "editor_select":
action = editor_select_menu.handle_event(event)
if action == "back_to_levels":
current_state = MENU
current_menu = "level_select"
elif action == "infinite":
current_state = INFINITE
elif action == "leaderboard":
current_state = LEADERBOARD
elif action == "quit":
pygame.quit()
sys.exit()
elif current_menu == "level_select":
action = level_select_menu.handle_event(event)
if action == "back_to_main":
current_menu = "main"
elif (
isinstance(action, dict)
and action.get("action") == "select_level"
):
level_file = action.get("level_file")
(
P1,
PT1,
platforms,
all_sprites,
background,
checkpoints,
exits,
) = initialize_game(game_resources, level_file)
projectiles.empty()
current_state = PLAYING
elif isinstance(action, dict):
if action["action"] == "edit_level":
level_editor = LevelEditor(
game_resources, action["level_file"]
)
current_state = "level_editor"
elif action["action"] == "new_level":
level_editor = LevelEditor(game_resources)
current_state = "level_editor"
# Handle leaderboard events
elif current_state == LEADERBOARD:
action = leaderboard.handle_event(event)
if action == "menu":
current_state = MENU
elif current_state == "level_editor":
result = level_editor.handle_event(event)
if result == "back_to_levels":
current_state = "editor_select"
except Exception as e:
print(f"Erreur lors du traitement de l'événement: {e}")
continue
# Clear screen
displaysurface.fill((0, 0, 0))
@@ -156,6 +214,12 @@ def main():
main_menu.draw(displaysurface)
elif current_menu == "level_select":
level_select_menu.draw(displaysurface)
elif current_state == "editor_select":
editor_select_menu.draw(displaysurface)
elif current_state == "level_editor":
level_editor.draw(displaysurface)
elif current_state == LEADERBOARD:
leaderboard.draw(displaysurface)
elif current_state == PLAYING:
# Regular game code
@@ -235,7 +299,10 @@ def main():
camera_adjusted_rect.y += camera.camera.y
displaysurface.blit(projectile.surf, camera_adjusted_rect)
checkpoints_hit = pygame.sprite.spritecollide(P1, checkpoints, False)
if checkpoints is not None:
checkpoints_hit = pygame.sprite.spritecollide(P1, checkpoints, False)
else:
checkpoints_hit = []
for checkpoint in checkpoints_hit:
checkpoint.activate()

77
map/levels/3.json Normal file
View File

@@ -0,0 +1,77 @@
{
"name": "Level 3",
"width": 2400,
"height": 800,
"background": "assets/map/background/forest_bg.jpg",
"gravity": 1.0,
"ground": [
],
"platforms": [
{
"id": "platform1",
"x": -1000,
"y": 680,
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform2",
"x": 240,
"y": 360,
"width": 540,
"height": 160,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform3",
"x": 320,
"y": 170,
"width": 200,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
}
],
"enemies": [
{
"id": "enemy1",
"type": "walker",
"x": 440,
"y": 280,
"health": 1,
"damage": 1,
"sprite_sheet": "assets/map/enemy/walker_enemy.png",
"behavior": "patrol",
"patrol_points": [
{
"x": 340,
"y": 280
},
{
"x": 540,
"y": 280
}
],
"speed": 1.5
}
],
"checkpoints": [],
"exits": [
{
"x": 650,
"y": 80,
"width": 50,
"height": 80,
"next_level": "map/levels/1.json",
"sprite": "assets/map/exit/door.png"
}
],
"collectibles": [],
"spawn_point": {
"x": 260.0,
"y": 320.0
}
}

View File

@@ -80,5 +80,6 @@ class CheckpointDB:
"""
try:
self.cursor.execute("DELETE FROM checkpoints")
self.conn.commit()
except Exception as e:
print(f"Error clearing checkpoint database: {e}")

View File

@@ -0,0 +1,58 @@
import pygame
class EditorPlatform(pygame.sprite.Sprite):
"""Platform object for the level editor"""
def __init__(self, width, height, x, y):
super().__init__()
self.rect = pygame.Rect(x, y, width, height)
# Create surface for drawing
self.image = pygame.Surface((width, height))
self.image.fill((100, 200, 100)) # Green color for platforms
# Store original dimensions
self.width = width
self.height = height
# Attributes for moving platforms
self.moving = False
self.direction = "horizontal"
self.speed = 2
self.distance = 100
class EditorCheckpoint(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.rect = pygame.Rect(x, y, 30, 30)
self.image = pygame.Surface((30, 30))
self.image.fill((255, 255, 0)) # Yellow
class EditorEnemy(pygame.sprite.Sprite):
def __init__(self, game_resources, x, y, enemy_type="walker"):
super().__init__()
self.rect = pygame.Rect(x, y, 40, 40)
self.image = pygame.Surface((40, 40))
self.image.fill((255, 0, 0)) # Red
self.enemy_type = enemy_type
class EditorExit(pygame.sprite.Sprite):
def __init__(self, x, y, width=50, height=50):
super().__init__()
self.rect = pygame.Rect(x, y, width, height)
self.image = pygame.Surface((width, height))
self.image.fill((0, 255, 255)) # Cyan
class EditorCollectible(pygame.sprite.Sprite):
def __init__(self, x, y, collectible_type="coin"):
super().__init__()
self.rect = pygame.Rect(x, y, 20, 20)
self.image = pygame.Surface((20, 20))
self.image.fill((255, 215, 0)) # Gold color for coins
self.collectible_type = collectible_type
self.value = 10 if collectible_type == "coin" else 0
self.duration = 5.0 if collectible_type == "power_up" else 0

604
src/Editor/LevelEditor.py Normal file
View File

@@ -0,0 +1,604 @@
import pygame
import os
import json
import sys
from pygame.locals import *
from src.Entity.Platform import Platform
from src.Entity.Player import Player
from src.Entity.Checkpoint import Checkpoint
from src.Entity.Enemy import Enemy
from src.Entity.Exit import Exit
from src.Menu.Button import Button
from src.Map.parser import MapParser
from src.Editor.EditorSprites import (
EditorPlatform,
EditorCheckpoint,
EditorEnemy,
EditorExit,
EditorCollectible,
)
class LevelEditor:
"""
A graphical level editor for creating and modifying game levels.
Allows placing and configuring game elements and saving to JSON files.
"""
def __init__(self, game_resources, level_file=None):
"""Initialize the level editor."""
self.game_resources = game_resources
self.level_file = level_file
self.grid_size = 20 # Grid size for snapping
# Initialize level data
self.platforms = pygame.sprite.Group()
self.enemies = pygame.sprite.Group()
self.checkpoints = pygame.sprite.Group()
self.collectibles = pygame.sprite.Group() # Add collectibles group
self.all_sprites = pygame.sprite.Group()
self.player_start = None
self.exit_point = None
# UI elements
self.buttons = []
self.tools = [
"select",
"player",
"platform",
"enemy",
"checkpoint",
"exit",
"collectible",
]
self.current_tool = "select"
self.selected_object = None
# For creating platforms
self.start_pos = None
self.creating = False
# For moving objects
self.dragging = False
self.offset_x = 0
self.offset_y = 0
# For platform properties
self.platform_moving = False
self.platform_speed = 2
self.platform_direction = "horizontal" # or "vertical"
self.platform_distance = 100
# For collectible properties
self.collectible_type = "coin" # Default collectible type
# Create toolbar buttons
self._create_toolbar()
# Load level if specified
if level_file:
self._load_level(level_file)
def _create_toolbar(self):
"""Create buttons for the editor toolbar"""
# Tool selection buttons
button_width = 100
button_height = 30
x = 10
y = 10
for tool in self.tools:
self.buttons.append(
Button(
tool.capitalize(),
x,
y,
button_width,
button_height,
{"action": "select_tool", "tool": tool},
)
)
y += button_height + 5
# Save and exit buttons
self.buttons.append(
Button(
"Save",
10,
self.game_resources.HEIGHT - 70,
button_width,
button_height,
"save_level",
)
)
self.buttons.append(
Button(
"Exit",
10,
self.game_resources.HEIGHT - 35,
button_width,
button_height,
"exit_editor",
)
)
def _load_level(self, level_file):
"""Load an existing level for editing"""
parser = MapParser(self.game_resources)
map_objects = parser.load_map(level_file)
if not map_objects:
return
# Convert loaded platforms to editor platforms
if "platforms" in map_objects:
for platform in map_objects["platforms"]:
editor_platform = EditorPlatform(
platform.rect.width,
platform.rect.height,
platform.rect.x,
platform.rect.y,
)
# Transfer movement properties if any
if hasattr(platform, "moving") and platform.moving:
editor_platform.moving = platform.moving
editor_platform.direction = platform.direction
editor_platform.speed = platform.speed
editor_platform.distance = platform.distance
self.platforms.add(editor_platform)
self.all_sprites.add(editor_platform)
# Set player start position
if "player" in map_objects and map_objects["player"]:
self.player_start = map_objects["player"].pos
# Convert checkpoints
if "checkpoints" in map_objects:
for checkpoint in map_objects["checkpoints"]:
editor_checkpoint = EditorCheckpoint(
checkpoint.rect.x, checkpoint.rect.y
)
self.checkpoints.add(editor_checkpoint)
self.all_sprites.add(editor_checkpoint)
# Handle exits
if "exits" in map_objects and map_objects["exits"]:
exits_sprites = list(map_objects["exits"])
if exits_sprites:
exit_sprite = exits_sprites[0]
self.exit_point = EditorExit(
exit_sprite.rect.x,
exit_sprite.rect.y,
exit_sprite.rect.width,
exit_sprite.rect.height,
)
self.all_sprites.add(self.exit_point)
# Load enemies
if "enemies" in map_objects:
for enemy in map_objects["enemies"]:
editor_enemy = EditorEnemy(
self.game_resources, enemy.rect.x, enemy.rect.y
)
if hasattr(enemy, "enemy_type"):
editor_enemy.enemy_type = enemy.enemy_type
self.enemies.add(editor_enemy)
self.all_sprites.add(editor_enemy)
def _snap_to_grid(self, pos):
"""Snap a position to the grid"""
x, y = pos
return (
round(x / self.grid_size) * self.grid_size,
round(y / self.grid_size) * self.grid_size,
)
def save_level(self):
"""Save the level to a JSON file"""
if not self.level_file:
# If no file specified, create a new one
level_dir = "map/levels/"
# Find the next available level number
existing_levels = [
int(f.split(".")[0])
for f in os.listdir(level_dir)
if f.endswith(".json") and f.split(".")[0].isdigit()
]
new_level_num = 1 if not existing_levels else max(existing_levels) + 1
self.level_file = f"{level_dir}{new_level_num}.json"
level_data = {
"player": {
"x": self.player_start.x if self.player_start else 100,
"y": self.player_start.y if self.player_start else 100,
},
"platforms": [],
"checkpoints": [],
"exits": [],
"enemies": [],
}
# Add platforms
for platform in self.platforms:
platform_data = {
"x": platform.rect.x,
"y": platform.rect.y,
"width": platform.rect.width,
"height": platform.rect.height,
}
# Add movement data if platform moves
if hasattr(platform, "moving") and platform.moving:
platform_data["moving"] = True
platform_data["direction"] = platform.direction
platform_data["speed"] = platform.speed
platform_data["distance"] = platform.distance
level_data["platforms"].append(platform_data)
# Add checkpoints
for checkpoint in self.checkpoints:
level_data["checkpoints"].append(
{"x": checkpoint.rect.x, "y": checkpoint.rect.y}
)
# Add exit
if self.exit_point:
level_data["exits"].append(
{
"x": self.exit_point.rect.x,
"y": self.exit_point.rect.y,
"width": self.exit_point.rect.width,
"height": self.exit_point.rect.height,
}
)
# Add enemies
for enemy in self.enemies:
enemy_data = {
"x": enemy.rect.x,
"y": enemy.rect.y,
"type": enemy.enemy_type if hasattr(enemy, "enemy_type") else "basic",
}
level_data["enemies"].append(enemy_data)
# Save to file
try:
with open(self.level_file, "w") as f:
json.dump(level_data, f, indent=2)
print(f"Level saved to {self.level_file}")
return True
except Exception as e:
print(f"Error saving level: {e}")
return False
def handle_event(self, event):
"""
Handle user input events.
Args:
event: Pygame event to process
Returns:
str/dict/None: Action to perform based on user interaction, or None
"""
# Check for CTRL+S to save
if (
event.type == KEYDOWN
and event.key == K_s
and pygame.key.get_mods() & KMOD_CTRL
):
self.save_level()
return None
# Check UI button clicks
for button in self.buttons:
action = button.handle_event(event)
if action:
if action == "save_level":
self.save_level()
return None
elif action == "exit_editor":
return "back_to_levels"
elif isinstance(action, dict) and action.get("action") == "select_tool":
self.current_tool = action.get("tool")
self.selected_object = None
self.creating = False
return None
# Handle mouse actions based on current tool
if event.type == MOUSEBUTTONDOWN:
pos = self._snap_to_grid(pygame.mouse.get_pos())
# Select object
if self.current_tool == "select":
self.selected_object = None
for sprite in self.all_sprites:
if sprite.rect.collidepoint(event.pos):
self.selected_object = sprite
self.dragging = True
self.offset_x = sprite.rect.x - event.pos[0]
self.offset_y = sprite.rect.y - event.pos[1]
break
# Place player start point
elif self.current_tool == "player":
self.player_start = self.game_resources.vec(pos[0], pos[1])
# Start creating platform
elif self.current_tool == "platform":
self.creating = True
self.start_pos = pos
# Place checkpoint
elif self.current_tool == "checkpoint":
checkpoint = EditorCheckpoint(pos[0], pos[1])
self.checkpoints.add(checkpoint)
self.all_sprites.add(checkpoint)
# Place exit
elif self.current_tool == "exit":
if self.exit_point:
self.all_sprites.remove(self.exit_point)
self.exit_point = EditorExit(pos[0], pos[1], 50, 50)
self.all_sprites.add(self.exit_point)
# Place enemy
elif self.current_tool == "enemy":
enemy = EditorEnemy(self.game_resources, pos[0], pos[1])
self.enemies.add(enemy)
self.all_sprites.add(enemy)
# Place collectible
elif self.current_tool == "collectible":
collectible = EditorCollectible(pos[0], pos[1], self.collectible_type)
self.collectibles.add(collectible)
self.all_sprites.add(collectible)
# Handle mouse movement during platform creation or object dragging
elif event.type == MOUSEMOTION:
if self.dragging and self.selected_object:
pos = self._snap_to_grid(
(event.pos[0] + self.offset_x, event.pos[1] + self.offset_y)
)
self.selected_object.rect.x = pos[0]
self.selected_object.rect.y = pos[1]
# Update position attribute if exists
if hasattr(self.selected_object, "pos"):
self.selected_object.pos.x = pos[0]
self.selected_object.pos.y = pos[1]
# Finish creating object or stop dragging
elif event.type == MOUSEBUTTONUP:
if self.creating and self.current_tool == "platform":
end_pos = self._snap_to_grid(pygame.mouse.get_pos())
width = abs(end_pos[0] - self.start_pos[0])
height = abs(end_pos[1] - self.start_pos[1])
# Ensure minimum size
width = max(width, 20)
height = max(height, 20)
x = min(self.start_pos[0], end_pos[0])
y = min(self.start_pos[1], end_pos[1])
platform = EditorPlatform(width, height, x, y)
self.platforms.add(platform)
self.all_sprites.add(platform)
self.selected_object = platform
self.creating = False
self.dragging = False
# Handle keyboard controls for platform properties
if (
event.type == KEYDOWN
and self.selected_object
and isinstance(self.selected_object, Platform)
):
if event.key == K_m: # Toggle movement
if not hasattr(self.selected_object, "moving"):
self.selected_object.moving = True
self.selected_object.direction = "horizontal"
self.selected_object.speed = 2
self.selected_object.distance = 100
self.selected_object.start_pos = self.game_resources.vec(
self.selected_object.rect.x, self.selected_object.rect.y
)
else:
self.selected_object.moving = not self.selected_object.moving
elif event.key == K_d: # Toggle direction
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.direction = (
"vertical"
if self.selected_object.direction == "horizontal"
else "horizontal"
)
elif event.key == K_UP: # Increase speed
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.speed += 0.5
elif event.key == K_DOWN: # Decrease speed
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.speed = max(
0.5, self.selected_object.speed - 0.5
)
elif event.key == K_RIGHT: # Increase distance
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.distance += 20
elif event.key == K_LEFT: # Decrease distance
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.distance = max(
20, self.selected_object.distance - 20
)
elif event.key == K_DELETE:
self.all_sprites.remove(self.selected_object)
if isinstance(self.selected_object, EditorPlatform):
self.platforms.remove(self.selected_object)
elif isinstance(self.selected_object, EditorCheckpoint):
self.checkpoints.remove(self.selected_object)
elif isinstance(self.selected_object, EditorEnemy):
self.enemies.remove(self.selected_object)
elif isinstance(self.selected_object, EditorCollectible):
self.collectibles.remove(self.selected_object)
elif self.selected_object == self.exit_point:
self.exit_point = None
self.selected_object = None
elif (
event.key == K_t
and self.selected_object
and isinstance(self.selected_object, EditorCollectible)
):
types = ["coin", "speed_boost", "health", "shield"]
current_index = (
types.index(self.selected_object.collectible_type)
if self.selected_object.collectible_type in types
else 0
)
next_index = (current_index + 1) % len(types)
self.selected_object.collectible_type = types[next_index]
# 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
return None
def draw(self, surface):
"""
Draw the level editor and all its elements.
Args:
surface: Pygame surface to draw on
"""
# Clear the screen
surface.fill((40, 40, 40))
# Draw grid
for x in range(0, self.game_resources.WIDTH, self.grid_size):
pygame.draw.line(
surface, (60, 60, 60), (x, 0), (x, self.game_resources.HEIGHT)
)
for y in range(0, self.game_resources.HEIGHT, self.grid_size):
pygame.draw.line(
surface, (60, 60, 60), (0, y), (self.game_resources.WIDTH, y)
)
# Draw all sprites
self.all_sprites.draw(surface)
# Draw player start position
if self.player_start:
pygame.draw.circle(
surface,
(0, 255, 0),
(int(self.player_start.x), int(self.player_start.y)),
10,
)
# Draw outline for selected object
if self.selected_object:
pygame.draw.rect(surface, (255, 255, 0), self.selected_object.rect, 2)
# Show properties for selected platform
if isinstance(self.selected_object, EditorPlatform):
info_text = [
f"Size: {self.selected_object.rect.width}x{self.selected_object.rect.height}",
f"Pos: ({self.selected_object.rect.x}, {self.selected_object.rect.y})",
]
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
info_text.extend(
[
f"Moving: Yes",
f"Direction: {self.selected_object.direction}",
f"Speed: {self.selected_object.speed}",
f"Distance: {self.selected_object.distance}",
]
)
else:
info_text.append("Moving: No")
y_offset = 120
for text in info_text:
text_surf = self.game_resources.font.render(
text, True, (255, 255, 255)
)
surface.blit(text_surf, (10, y_offset))
y_offset += 25
# Draw platform being created
if self.creating and self.current_tool == "platform":
start_pos = self.start_pos
end_pos = self._snap_to_grid(pygame.mouse.get_pos())
rect = pygame.Rect(
min(start_pos[0], end_pos[0]),
min(start_pos[1], end_pos[1]),
abs(end_pos[0] - start_pos[0]) or self.grid_size,
abs(end_pos[1] - start_pos[1]) or self.grid_size,
)
pygame.draw.rect(surface, (0, 150, 255), rect, 2)
# Draw UI buttons
for button in self.buttons:
button.draw(surface, self.game_resources.font)
# Draw current tool indicator
tool_text = f"Current Tool: {self.current_tool.capitalize()}"
tool_surf = self.game_resources.font.render(tool_text, True, (255, 255, 255))
surface.blit(tool_surf, (self.game_resources.WIDTH - 250, 10))
# Draw help text
help_text = [
"Controls:",
"CTRL+S: Save level",
"DEL: Delete selected object",
"For platforms:",
" M: Toggle movement",
" D: Toggle direction",
" Arrow keys: Adjust speed/distance",
"For collectibles:",
" T: Change type",
]
y_offset = self.game_resources.HEIGHT - 170
for text in help_text:
text_surf = self.game_resources.font.render(text, True, (200, 200, 200))
surface.blit(text_surf, (self.game_resources.WIDTH - 250, y_offset))
y_offset += 20

View File

@@ -11,6 +11,20 @@ class Player(Entity):
# Game ressources
self.game_resources = game_resources
self.has_joystick = False
self.joystick = None
self.jump_button = 0
self.dash_button = 1
try:
if pygame.joystick.get_count() > 0:
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
self.has_joystick = True
except pygame.error:
self.has_joystick = False
# Animation variables
self.animation_frames = []
self.jump_frames = []
@@ -173,18 +187,46 @@ class Player(Entity):
# Reset flags
self.moving = False
# Keyboard controls
pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_q]:
move_left = pressed_keys[K_q]
move_right = pressed_keys[K_d]
jump = pressed_keys[K_SPACE]
dash_key = pressed_keys[K_a]
if self.has_joystick and self.joystick:
try:
# Joystick gauche pour mouvement
if self.joystick.get_numaxes() > 0:
joystick_x = self.joystick.get_axis(0)
if abs(joystick_x) > 0.2:
if joystick_x < 0:
move_left = True
elif joystick_x > 0:
move_right = True
# Boutons pour sauter/dasher
if self.joystick.get_numbuttons() > self.jump_button:
if self.joystick.get_button(self.jump_button):
jump = True
if self.joystick.get_numbuttons() > self.dash_button:
if self.joystick.get_button(self.dash_button):
dash_key = True
except pygame.error:
pass # Ignorer les erreurs de manette
if move_left:
# Check if X is > 0 to prevent player from going off screen
if self.pos.x > 0:
self.acc.x = -self.game_resources.ACC
self.moving = True
if pressed_keys[K_a]:
if dash_key:
self.dash(-self.game_resources.ACC)
if pressed_keys[K_d]:
if move_right:
self.acc.x = self.game_resources.ACC
self.moving = True
if pressed_keys[K_a]:
if dash_key:
self.dash(self.game_resources.ACC)
# Also consider the player moving if they have significant horizontal velocity
@@ -192,7 +234,7 @@ class Player(Entity):
self.moving = True
# Jumping logic
if pressed_keys[K_SPACE] and not self.jumping:
if jump and not self.jumping:
self.vel.y = -30
self.jumping = True

View File

@@ -0,0 +1,68 @@
import pygame
class EditorPlatform(pygame.sprite.Sprite):
"""Platform object for the level editor"""
def __init__(self, width, height, x, y):
super().__init__()
self.rect = pygame.Rect(x, y, width, height)
# Create surface for drawing
self.image = pygame.Surface((width, height))
self.image.fill((100, 200, 100)) # Green color for platforms
# Store original dimensions
self.width = width
self.height = height
# Attributes for moving platforms
self.moving = False
self.direction = "horizontal"
self.speed = 2
self.distance = 100
class EditorCheckpoint(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.rect = pygame.Rect(x, y, 30, 30)
self.image = pygame.Surface((30, 30))
self.image.fill((255, 255, 0)) # Yellow
class EditorEnemy(pygame.sprite.Sprite):
def __init__(self, game_resources, x, y, enemy_type="walker"):
super().__init__()
self.rect = pygame.Rect(x, y, 30, 30)
self.image = pygame.Surface((30, 30))
self.enemy_type = enemy_type
self.update_appearance()
def update_appearance(self):
if self.enemy_type == "walker":
self.image.fill((255, 0, 0)) # Rouge
elif self.enemy_type == "flyer":
self.image.fill((255, 165, 0)) # Orange
elif self.enemy_type == "turret":
self.image.fill((128, 0, 128)) # Violet
class EditorExit(pygame.sprite.Sprite):
def __init__(self, x, y, width=50, height=50, next_level="map/levels/1.json"):
super().__init__()
self.rect = pygame.Rect(x, y, width, height)
self.image = pygame.Surface((width, height))
self.image.fill((0, 255, 255)) # Cyan
self.next_level = next_level
self.sprite = "assets/map/exit/door.png"
class EditorCollectible(pygame.sprite.Sprite):
def __init__(self, x, y, collectible_type="coin"):
super().__init__()
self.rect = pygame.Rect(x, y, 20, 20)
self.image = pygame.Surface((20, 20))
self.image.fill((255, 215, 0)) # Gold color for coins
self.collectible_type = collectible_type
self.value = 10 if collectible_type == "coin" else 0
self.duration = 5.0 if collectible_type == "power_up" else 0

View File

@@ -0,0 +1,753 @@
import pygame
import os
import json
from pygame.locals import *
from src.Entity.Platform import Platform
from src.Menu.Button import Button
from src.Map.parser import MapParser
from src.Map.Editor.EditorSprites import (
EditorPlatform,
EditorCheckpoint,
EditorEnemy,
EditorExit,
EditorCollectible,
)
class LevelEditor:
"""
A graphical level editor for creating and modifying game levels.
Allows placing and configuring game elements and saving to JSON files.
"""
def __init__(self, game_resources, level_file=None):
"""Initialize the level editor."""
self.game_resources = game_resources
self.level_file = level_file
self.grid_size = 20 # Grid size for snapping
# Initialize level data
self.platforms = pygame.sprite.Group()
self.enemies = pygame.sprite.Group()
self.checkpoints = pygame.sprite.Group()
self.collectibles = pygame.sprite.Group()
self.all_sprites = pygame.sprite.Group()
self.player_start = None
self.exit_point = None
# UI elements
self.buttons = []
self.tools = [
"select",
"player",
"platform",
"enemy",
"checkpoint",
"exit",
"collectible",
]
self.current_tool = "select"
self.selected_object = None
# For creating platforms
self.start_pos = None
self.creating = False
# For moving objects
self.dragging = False
self.offset_x = 0
self.offset_y = 0
# For platform properties
self.platform_moving = False
self.platform_speed = 2
self.platform_direction = "horizontal"
self.platform_distance = 100
# For collectible properties
self.collectible_type = "coin"
# Create toolbar buttons
self._create_toolbar()
# Load level if specified
if level_file:
self._load_level(level_file)
def _create_toolbar(self):
"""Create buttons for the editor toolbar"""
# Tool selection buttons
button_width = 100
button_height = 30
x = 10
y = 10
for tool in self.tools:
self.buttons.append(
Button(
tool.capitalize(),
x,
y,
button_width,
button_height,
{"action": "select_tool", "tool": tool},
)
)
y += button_height + 5
# Save and exit buttons
self.buttons.append(
Button(
"Save",
10,
self.game_resources.HEIGHT - 70,
button_width,
button_height,
"save_level",
)
)
self.buttons.append(
Button(
"Exit",
10,
self.game_resources.HEIGHT - 35,
button_width,
button_height,
"exit_editor",
)
)
def _load_level(self, level_file):
"""Load an existing level for editing"""
parser = MapParser(self.game_resources)
map_objects = parser.load_map(level_file)
if not map_objects:
return
# Convert loaded platforms to editor platforms
if "platforms" in map_objects:
for platform in map_objects["platforms"]:
editor_platform = EditorPlatform(
platform.rect.width,
platform.rect.height,
platform.rect.x,
platform.rect.y,
)
# Transfer movement properties if any
if hasattr(platform, "moving") and platform.moving:
editor_platform.moving = platform.moving
editor_platform.direction = platform.direction
editor_platform.speed = platform.speed
editor_platform.distance = platform.distance
self.platforms.add(editor_platform)
self.all_sprites.add(editor_platform)
# Set player start position
if "player" in map_objects and map_objects["player"]:
self.player_start = map_objects["player"].pos
# Convert checkpoints
if "checkpoints" in map_objects:
for checkpoint in map_objects["checkpoints"]:
editor_checkpoint = EditorCheckpoint(
checkpoint.rect.x, checkpoint.rect.y
)
self.checkpoints.add(editor_checkpoint)
self.all_sprites.add(editor_checkpoint)
# Handle exits
if "exits" in map_objects and map_objects["exits"]:
exits_sprites = list(map_objects["exits"])
if exits_sprites:
exit_sprite = exits_sprites[0]
self.exit_point = EditorExit(
exit_sprite.rect.x,
exit_sprite.rect.y,
exit_sprite.rect.width,
exit_sprite.rect.height,
)
self.all_sprites.add(self.exit_point)
# Load enemies
if "enemies" in map_objects:
for enemy in map_objects["enemies"]:
editor_enemy = EditorEnemy(
self.game_resources, enemy.rect.x, enemy.rect.y
)
if hasattr(enemy, "enemy_type"):
editor_enemy.enemy_type = enemy.enemy_type
self.enemies.add(editor_enemy)
self.all_sprites.add(editor_enemy)
def _snap_to_grid(self, pos):
"""Snap a position to the grid"""
x, y = pos
return (
round(x / self.grid_size) * self.grid_size,
round(y / self.grid_size) * self.grid_size,
)
def save_level(self):
"""Save the level to a JSON file"""
if not self.level_file:
# If no file specified, create a new one
level_dir = "map/levels/"
# Find the next available level number
existing_levels = [
int(f.split(".")[0])
for f in os.listdir(level_dir)
if f.endswith(".json") and f.split(".")[0].isdigit()
]
new_level_num = 1 if not existing_levels else max(existing_levels) + 1
self.level_file = f"{level_dir}{new_level_num}.json"
# Name of the level based on the file name
level_name = f"Level {self.level_file.split('/')[-1].split('.')[0]}"
level_data = {
"name": level_name,
"width": 2400,
"height": 800,
"background": "assets/map/background/forest_bg.jpg",
"gravity": 1.0,
"ground": [
{
"id": "main_ground",
"x": -1000,
"y": 780,
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg",
}
],
"platforms": [],
"enemies": [],
"checkpoints": [],
"exits": [],
"collectibles": [],
"spawn_point": {
"x": self.player_start.x if self.player_start else 100,
"y": self.player_start.y if self.player_start else 100,
},
}
# Add platforms
for i, platform in enumerate(self.platforms):
platform_data = {
"id": f"platform{i + 1}",
"x": platform.rect.x,
"y": platform.rect.y,
"width": platform.rect.width,
"height": platform.rect.height,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": False,
}
# Add movement data if platform moves
if hasattr(platform, "moving") and platform.moving:
platform_data["is_moving"] = True
if platform.direction == "horizontal":
platform_data["movement"] = {
"type": "linear",
"points": [
{"x": platform.rect.x, "y": platform.rect.y},
{
"x": platform.rect.x + platform.distance,
"y": platform.rect.y,
},
],
"speed": platform.speed,
"wait_time": 1.0,
}
else: # vertical
platform_data["movement"] = {
"type": "linear",
"points": [
{"x": platform.rect.x, "y": platform.rect.y},
{
"x": platform.rect.x,
"y": platform.rect.y + platform.distance,
},
],
"speed": platform.speed,
"wait_time": 1.0,
}
level_data["platforms"].append(platform_data)
# Add collectibles
for i, collectible in enumerate(self.collectibles):
collectible_data = {
"id": f"collectible{i + 1}",
"type": collectible.collectible_type,
"x": collectible.rect.x,
"y": collectible.rect.y,
"sprite": f"assets/map/collectibles/{collectible.collectible_type}.png",
}
# Add specific attributes based on type
if collectible.collectible_type == "coin":
collectible_data["value"] = 10
elif collectible.collectible_type == "speed_boost":
collectible_data["duration"] = 5.0
level_data["collectibles"].append(collectible_data)
# Add checkpoints
for i, checkpoint in enumerate(self.checkpoints):
level_data["checkpoints"].append(
{
"id": f"checkpoint{i + 1}",
"x": checkpoint.rect.x,
"y": checkpoint.rect.y,
"width": 50,
"height": 50,
"sprite": "assets/map/checkpoints/checkpoint.png",
}
)
# Add exit
if self.exit_point:
next_level_num = int(self.level_file.split("/")[-1].split(".")[0]) + 1
next_level = f"Level {next_level_num}"
level_data["exits"].append(
{
"x": self.exit_point.rect.x,
"y": self.exit_point.rect.y,
"width": 50,
"height": 80,
"next_level": getattr(self.exit_point, "next_level", next_level),
"sprite": "assets/map/exit/door.png",
}
)
# Add enemies
for i, enemy in enumerate(self.enemies):
enemy_type = getattr(enemy, "enemy_type", "walker")
enemy_data = {
"id": f"enemy{i + 1}",
"type": enemy_type,
"x": enemy.rect.x,
"y": enemy.rect.y,
"health": 1,
"damage": 1,
"sprite_sheet": f"assets/map/enemy/{enemy_type}_enemy.png",
}
if enemy_type == "walker":
enemy_data["behavior"] = "patrol"
enemy_data["patrol_points"] = [
{"x": enemy.rect.x - 100, "y": enemy.rect.y},
{"x": enemy.rect.x + 100, "y": enemy.rect.y},
]
enemy_data["speed"] = 1.5
elif enemy_type == "flyer":
enemy_data["behavior"] = "chase"
enemy_data["detection_radius"] = 200
enemy_data["speed"] = 2.0
elif enemy_type == "turret":
enemy_data["behavior"] = "stationary"
enemy_data["attack_interval"] = 2.0
enemy_data["attack_range"] = 300
level_data["enemies"].append(enemy_data)
# Save to file
try:
with open(self.level_file, "w") as f:
json.dump(level_data, f, indent=2)
print(f"Level saved to {self.level_file}")
return True
except Exception as e:
print(f"Error saving level: {e}")
return False
def handle_event(self, event):
"""
Handle user input events.
Args:
event: Pygame event to process
Returns:
str/dict/None: Action to perform based on user interaction, or None
"""
# Check for CTRL+S to save
if (
event.type == KEYDOWN
and event.key == K_s
and pygame.key.get_mods() & KMOD_CTRL
):
self.save_level()
return None
# Check UI button clicks
for button in self.buttons:
action = button.handle_event(event)
if action:
if action == "save_level":
self.save_level()
return None
elif action == "exit_editor":
return "back_to_levels"
elif isinstance(action, dict) and action.get("action") == "select_tool":
self.current_tool = action.get("tool")
self.selected_object = None
self.creating = False
return None
# Handle mouse actions based on current tool
if event.type == MOUSEBUTTONDOWN:
pos = self._snap_to_grid(pygame.mouse.get_pos())
# Select object
if self.current_tool == "select":
self.selected_object = None
for sprite in self.all_sprites:
if sprite.rect.collidepoint(event.pos):
self.selected_object = sprite
self.dragging = True
self.offset_x = sprite.rect.x - event.pos[0]
self.offset_y = sprite.rect.y - event.pos[1]
break
# Place player start point
elif self.current_tool == "player":
self.player_start = self.game_resources.vec(pos[0], pos[1])
# Start creating platform
elif self.current_tool == "platform":
self.creating = True
self.start_pos = pos
# Place checkpoint
elif self.current_tool == "checkpoint":
checkpoint = EditorCheckpoint(pos[0], pos[1])
self.checkpoints.add(checkpoint)
self.all_sprites.add(checkpoint)
# Place exit
elif self.current_tool == "exit":
if self.exit_point:
self.all_sprites.remove(self.exit_point)
self.exit_point = EditorExit(pos[0], pos[1], 50, 50)
self.all_sprites.add(self.exit_point)
# Place enemy
elif self.current_tool == "enemy":
enemy = EditorEnemy(self.game_resources, pos[0], pos[1])
self.enemies.add(enemy)
self.all_sprites.add(enemy)
# Place collectible
elif self.current_tool == "collectible":
collectible = EditorCollectible(pos[0], pos[1], self.collectible_type)
self.collectibles.add(collectible)
self.all_sprites.add(collectible)
# Handle mouse movement during platform creation or object dragging
elif event.type == MOUSEMOTION:
if self.dragging and self.selected_object:
pos = self._snap_to_grid(
(event.pos[0] + self.offset_x, event.pos[1] + self.offset_y)
)
self.selected_object.rect.x = pos[0]
self.selected_object.rect.y = pos[1]
# Update position attribute if exists
if hasattr(self.selected_object, "pos"):
self.selected_object.pos.x = pos[0]
self.selected_object.pos.y = pos[1]
# Finish creating object or stop dragging
elif event.type == MOUSEBUTTONUP:
if self.creating and self.current_tool == "platform":
end_pos = self._snap_to_grid(pygame.mouse.get_pos())
width = abs(end_pos[0] - self.start_pos[0])
height = abs(end_pos[1] - self.start_pos[1])
# Ensure minimum size
width = max(width, 20)
height = max(height, 20)
x = min(self.start_pos[0], end_pos[0])
y = min(self.start_pos[1], end_pos[1])
platform = EditorPlatform(width, height, x, y)
self.platforms.add(platform)
self.all_sprites.add(platform)
self.selected_object = platform
self.creating = False
self.dragging = False
# Handle keyboard controls for platform properties
if event.type == KEYDOWN:
print(f"Touche pressée: {pygame.key.name(event.key)}")
print(
f"Objet sélectionné: {type(self.selected_object).__name__ if self.selected_object else 'Aucun'}"
)
if event.key == K_BACKSPACE and self.selected_object:
print(
f"Tentative de suppression de {type(self.selected_object).__name__}"
)
# Vérifier que l'objet est bien dans les groupes avant de le supprimer
if self.selected_object in self.all_sprites:
self.all_sprites.remove(self.selected_object)
print("Supprimé de all_sprites")
else:
print("L'objet n'est pas dans all_sprites")
if isinstance(self.selected_object, EditorPlatform):
print("Objet sélectionné est une plateforme")
if self.selected_object in self.platforms:
self.platforms.remove(self.selected_object)
print("Supprimé de platforms")
else:
print("L'objet n'est pas dans platforms")
elif isinstance(self.selected_object, EditorCheckpoint):
self.checkpoints.remove(self.selected_object)
elif isinstance(self.selected_object, EditorEnemy):
self.enemies.remove(self.selected_object)
elif isinstance(self.selected_object, EditorCollectible):
self.collectibles.remove(self.selected_object)
elif self.selected_object == self.exit_point:
self.exit_point = None
self.selected_object = None
print("Objet sélectionné réinitialisé")
if self.selected_object and isinstance(
self.selected_object, EditorPlatform
):
if event.key == K_m: # Toggle movement
if not hasattr(self.selected_object, "moving"):
self.selected_object.moving = True
self.selected_object.direction = "horizontal"
self.selected_object.speed = 2
self.selected_object.distance = 100
self.selected_object.start_pos = self.game_resources.vec(
self.selected_object.rect.x, self.selected_object.rect.y
)
else:
self.selected_object.moving = not self.selected_object.moving
elif event.key == K_d: # Toggle direction
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.direction = (
"vertical"
if self.selected_object.direction == "horizontal"
else "horizontal"
)
elif event.key == K_UP: # Increase speed
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.speed += 0.5
elif event.key == K_DOWN: # Decrease speed
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.speed = max(
0.5, self.selected_object.speed - 0.5
)
elif event.key == K_RIGHT: # Increase distance
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.distance += 20
elif event.key == K_LEFT: # Decrease distance
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
self.selected_object.distance = max(
20, self.selected_object.distance - 20
)
elif self.selected_object and isinstance(
self.selected_object, EditorCollectible
):
if event.key == K_t:
types = ["coin", "speed_boost", "health", "shield"]
current_index = (
types.index(self.selected_object.collectible_type)
if self.selected_object.collectible_type in types
else 0
)
next_index = (current_index + 1) % len(types)
self.selected_object.collectible_type = types[next_index]
# 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:
# Cycle de navigation entre les niveaux
level_dir = "map/levels/"
levels = [f for f in os.listdir(level_dir) if f.endswith(".json")]
if not hasattr(
self.selected_object, "next_level"
) or self.selected_object.next_level not in [
level_dir + l for l in levels
]:
self.selected_object.next_level = (
level_dir + levels[0] if levels else "map/levels/1.json"
)
else:
current_index = levels.index(
self.selected_object.next_level.replace(level_dir, "")
)
next_index = (current_index + 1) % len(levels)
self.selected_object.next_level = level_dir + levels[next_index]
print(f"Next level set to: {self.selected_object.next_level}")
elif self.selected_object and isinstance(self.selected_object, EditorEnemy):
if event.key == K_t:
types = ["walker", "flyer", "turret"]
current_index = (
types.index(self.selected_object.enemy_type)
if self.selected_object.enemy_type in types
else 0
)
next_index = (current_index + 1) % len(types)
self.selected_object.enemy_type = types[next_index]
self.selected_object.update_appearance()
print(f"Enemy type set to: {self.selected_object.enemy_type}")
return None
def draw(self, surface):
"""
Draw the level editor and all its elements.
Args:
surface: Pygame surface to draw on
"""
# Clear the screen
surface.fill((40, 40, 40))
# Draw grid
for x in range(0, self.game_resources.WIDTH, self.grid_size):
pygame.draw.line(
surface, (60, 60, 60), (x, 0), (x, self.game_resources.HEIGHT)
)
for y in range(0, self.game_resources.HEIGHT, self.grid_size):
pygame.draw.line(
surface, (60, 60, 60), (0, y), (self.game_resources.WIDTH, y)
)
# Draw all sprites
self.all_sprites.draw(surface)
# Draw player start position
if self.player_start:
pygame.draw.circle(
surface,
(0, 255, 0),
(int(self.player_start.x), int(self.player_start.y)),
10,
)
# Draw outline for selected object
if self.selected_object:
pygame.draw.rect(surface, (255, 255, 0), self.selected_object.rect, 2)
# Show properties for selected platform
if isinstance(self.selected_object, EditorPlatform):
info_text = [
f"Size: {self.selected_object.rect.width}x{self.selected_object.rect.height}",
f"Pos: ({self.selected_object.rect.x}, {self.selected_object.rect.y})",
]
if (
hasattr(self.selected_object, "moving")
and self.selected_object.moving
):
info_text.extend(
[
f"Moving: Yes",
f"Direction: {self.selected_object.direction}",
f"Speed: {self.selected_object.speed}",
f"Distance: {self.selected_object.distance}",
]
)
else:
info_text.append("Moving: No")
y_offset = 120
for text in info_text:
text_surf = self.game_resources.font.render(
text, True, (255, 255, 255)
)
surface.blit(text_surf, (10, y_offset))
y_offset += 25
# Draw platform being created
if self.creating and self.current_tool == "platform":
start_pos = self.start_pos
end_pos = self._snap_to_grid(pygame.mouse.get_pos())
rect = pygame.Rect(
min(start_pos[0], end_pos[0]),
min(start_pos[1], end_pos[1]),
abs(end_pos[0] - start_pos[0]) or self.grid_size,
abs(end_pos[1] - start_pos[1]) or self.grid_size,
)
pygame.draw.rect(surface, (0, 150, 255), rect, 2)
# Draw UI buttons
for button in self.buttons:
button.draw(surface, self.game_resources.font)
# Draw current tool indicator
tool_text = f"Current Tool: {self.current_tool.capitalize()}"
tool_surf = self.game_resources.font.render(tool_text, True, (255, 255, 255))
surface.blit(tool_surf, (self.game_resources.WIDTH - 250, 10))
# Draw help text
help_text = [
"Controls:",
"CTRL+S: Save level",
"DEL: Delete selected object",
"For platforms:",
" M: Toggle movement",
" D: Toggle direction",
" Arrow keys: Adjust speed/distance",
"For collectibles:",
" T: Change type",
"Pour les sorties:",
" N: Changer le niveau suivant",
"Pour les ennemis:",
" T: Changer le type d'ennemi",
]
y_offset = self.game_resources.HEIGHT - 300
for text in help_text:
text_surf = self.game_resources.font.render(text, True, (200, 200, 200))
surface.blit(text_surf, (self.game_resources.WIDTH - 250, y_offset))
y_offset += 20

View File

@@ -71,6 +71,14 @@ class MapParser:
self.platforms.add(platform)
self.all_sprites.add(platform)
# Create enemies
if "enemies" in map_data:
# Create enemies
for enemy_data in map_data["enemies"]:
enemy = Enemy(enemy_data)
self.enemies.add(enemy)
self.all_sprites.add(enemy)
# Create platforms
if "platforms" in map_data:
for platform_data in map_data["platforms"]:
@@ -107,22 +115,6 @@ class MapParser:
self.platforms.add(platform)
self.all_sprites.add(platform)
# Create player at spawn point
spawn = map_data.get("spawn_point", {"x": 50, "y": 700})
self.player = Player(self.game_resources)
self.player.pos.x = spawn["x"]
self.player.pos.y = spawn["y"]
self.all_sprites.add(self.player)
# Create enemies (requires Enemy class implementation)
if "enemies" in map_data:
# Create enemies
if "enemies" in map_data:
for enemy_data in map_data["enemies"]:
enemy = Enemy(enemy_data)
self.enemies.add(enemy)
self.all_sprites.add(enemy)
# Create collectibles (requires Collectible class implementation)
if "collectibles" in map_data:
pass # You'll need to implement collectible creation
@@ -163,3 +155,10 @@ class MapParser:
)
self.exits.add(exit)
self.all_sprites.add(exit)
# Create player at spawn point
spawn = map_data.get("spawn_point", {"x": 50, "y": 700})
self.player = Player(self.game_resources)
self.player.pos.x = spawn["x"]
self.player.pos.y = spawn["y"]
self.all_sprites.add(self.player)

View File

@@ -0,0 +1,161 @@
import pygame
import os
import re
from src.Menu.Button import Button
class LevelEditorSelectionMenu:
"""
A menu for selecting an existing level to edit or creating a new level.
"""
def __init__(self, game_resources):
"""
Initialize the level editor selection menu.
Args:
game_resources: GameResources object containing game settings and resources
"""
self.game_resources = game_resources
self.buttons = []
self.levels = []
# Button dimensions
self.button_width = 250
self.button_height = 60
self.button_spacing = 20
# Scan for level files
self._scan_levels()
# Generate level buttons
self._create_buttons()
def _scan_levels(self):
"""
Scan the levels directory for JSON level files and sort them numerically.
"""
try:
# Get all JSON files in the levels directory
level_dir = "map/levels/"
if not os.path.exists(level_dir):
os.makedirs(level_dir) # Create directory if it doesn't exist
files = [f for f in os.listdir(level_dir) if f.endswith(".json")]
# Extract level numbers using regex and sort numerically
level_pattern = re.compile(r"(\d+)\.json$")
self.levels = []
for file in files:
match = level_pattern.search(file)
if match:
level_number = int(match.group(1))
self.levels.append((level_number, f"{level_dir}{file}"))
# Sort levels by number
self.levels.sort(key=lambda x: x[0])
except Exception as e:
print(f"Error scanning levels: {e}")
self.levels = []
def _create_buttons(self):
"""
Create buttons for each available level and new level button.
"""
# Calculate how many buttons can fit per row
buttons_per_row = 3
button_width_with_spacing = self.button_width + self.button_spacing
# Start position for the grid of buttons
start_x = (
self.game_resources.WIDTH
- (button_width_with_spacing * min(buttons_per_row, len(self.levels) or 1))
) // 2
start_y = self.game_resources.HEIGHT // 3
# Create buttons for each level
for i, (level_num, level_file) in enumerate(self.levels):
# Calculate position in grid
row = i // buttons_per_row
col = i % buttons_per_row
x = start_x + (col * button_width_with_spacing)
y = start_y + (row * (self.button_height + self.button_spacing))
# Create button
self.buttons.append(
Button(
f"Edit Level {level_num}",
x,
y,
self.button_width,
self.button_height,
{"action": "edit_level", "level_file": level_file},
)
)
# Add "Create New Level" button
new_level_y = start_y + ((len(self.levels) // buttons_per_row) + 1) * (
self.button_height + self.button_spacing
)
self.buttons.append(
Button(
"Create New Level",
self.game_resources.WIDTH // 2 - self.button_width // 2,
new_level_y,
self.button_width,
self.button_height,
{"action": "new_level"},
)
)
# Add Back button
self.buttons.append(
Button(
"Back",
self.game_resources.WIDTH // 2 - self.button_width // 2,
self.game_resources.HEIGHT - 100,
self.button_width,
self.button_height,
"back_to_levels",
)
)
def draw(self, surface):
"""
Draw the level selection menu.
Args:
surface: Pygame surface to draw on
"""
# Draw title
title = pygame.font.SysFont("Arial", 48).render(
"Level Editor", True, (0, 191, 255)
)
title_rect = title.get_rect(
center=(self.game_resources.WIDTH // 2, self.game_resources.HEIGHT // 6)
)
surface.blit(title, title_rect)
# Draw buttons
for button in self.buttons:
button.draw(surface, self.game_resources.font)
def handle_event(self, event):
"""
Handle user input events.
Args:
event: Pygame event to process
Returns:
dict/str/None: Action to perform based on button clicked, or None
"""
for button in self.buttons:
action = button.handle_event(event)
if action:
return action
return None

View File

@@ -141,7 +141,7 @@ class LevelSelectMenu:
self.buttons.append(
Button(
"Reset Progress",
3 * self.game_resources.WIDTH // 4 - self.button_width // 2,
2 * self.game_resources.WIDTH // 4 - self.button_width // 2,
self.game_resources.HEIGHT - 100,
self.button_width,
self.button_height,
@@ -149,6 +149,18 @@ class LevelSelectMenu:
)
)
# Level Editor button
self.buttons.append(
Button(
"Level Editor",
3 * self.game_resources.WIDTH // 4 - self.button_width // 2,
self.game_resources.HEIGHT - 100,
self.button_width,
self.button_height,
"open_editor",
)
)
def draw(self, surface):
"""
Draw the level selection menu.

View File

@@ -1,3 +1,5 @@
import json
import pygame
import sys
from pygame.locals import *
@@ -51,6 +53,14 @@ def initialize_game(game_resources, map_file="map/levels/1.json"):
for exit_obj in exits:
exit_obj.set_player(map_objects["player"])
with open(map_file, "r") as f:
level_data = json.load(f)
print(f"Chargement du niveau: {map_file}")
if "enemies" in level_data:
print(f"Ennemis trouvés dans le JSON: {len(level_data['enemies'])}")
else:
print("Aucun ennemi trouvé dans le JSON")
return (
map_objects["player"],
None,