mirror of
https://github.com/BreizhHardware/project_sanic.git
synced 2026-01-18 16:47:25 +01:00
Merge remote-tracking branch 'origin/dev' into dev_table_basse
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
.venv
|
||||
.venv/*
|
||||
|
||||
.winvenv
|
||||
.winvenv/*
|
||||
|
||||
.idea
|
||||
.idea/*
|
||||
|
||||
|
||||
243
main.py
243
main.py
@@ -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
77
map/levels/3.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
|
||||
58
src/Editor/EditorSprites.py
Normal file
58
src/Editor/EditorSprites.py
Normal 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
604
src/Editor/LevelEditor.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
68
src/Map/Editor/EditorSprites.py
Normal file
68
src/Map/Editor/EditorSprites.py
Normal 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
|
||||
753
src/Map/Editor/LevelEditor.py
Normal file
753
src/Map/Editor/LevelEditor.py
Normal 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
|
||||
@@ -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)
|
||||
|
||||
161
src/Menu/LevelEditorSelectionMenu.py
Normal file
161
src/Menu/LevelEditorSelectionMenu.py
Normal 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
|
||||
@@ -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.
|
||||
|
||||
10
src/game.py
10
src/game.py
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user