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

# Conflicts:
#	main.py
#	src/Entity/Player.py
This commit is contained in:
ClementHVT
2025-04-04 09:43:28 +02:00
24 changed files with 1500 additions and 1363 deletions

223
Test.py
View File

@@ -1,223 +0,0 @@
import pygame
from pygame.locals import *
import sys
import os
import time
pygame.init()
vec = pygame.math.Vector2
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
FramePerSec = pygame.time.Clock()
displaysurface = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
WIDTH, HEIGHT = displaysurface.get_size()
pygame.display.set_caption("Project Sanic")
# Initialize font for FPS counter
try:
font = pygame.font.SysFont("Arial", 24)
except:
font = pygame.font.Font(None, 24) # Default font if Arial is not available
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
# Animation variables
self.animation_frames = []
self.current_frame = 0
self.animation_speed = 0.1
self.last_update = time.time()
self.static_image = None
self.moving = False # Track if player is moving
self.dashing = False
# Load static image and animation frames
self.load_images()
# Set initial surface
if self.static_image:
self.surf = self.static_image
elif self.animation_frames:
self.surf = self.animation_frames[0]
else:
# Fallback to a colored rectangle
self.surf = pygame.Surface((30, 30))
self.surf.fill((128, 255, 40))
self.rect = self.surf.get_rect()
self.pos = vec((10, 385))
self.vel = vec(0, 0)
self.acc = vec(0, 0)
self.jumping = False
def load_images(self):
try:
# Load static image
if os.path.isfile("assets/player/Sanic Base.png"):
self.static_image = pygame.image.load("assets/player/Sanic Base.png").convert_alpha()
self.static_image = pygame.transform.scale(self.static_image, (160, 160))
# Load animation sprite sheet
if os.path.isfile("assets/player/Sanic Annimate.png"):
sprite_sheet = pygame.image.load("assets/player/Sanic Annimate.png").convert_alpha()
# Extract the 4 frames
frame_width = sprite_sheet.get_height()
for i in range(4):
# Cut out a region of the sprite sheet
frame = sprite_sheet.subsurface((i * 2207, 0, frame_width, frame_width))
# Resize the frame
frame = pygame.transform.scale(frame, (160, 160))
self.animation_frames.append(frame)
except Exception as e:
print(f"Error loading player images: {e}")
def update_animation(self):
if self.moving:
# Only animate when moving
if self.animation_frames and time.time() - self.last_update > self.animation_speed:
self.current_frame = (self.current_frame + 1) % len(self.animation_frames)
self.surf = self.animation_frames[self.current_frame]
self.last_update = time.time()
else:
# Use static image when not moving
if self.static_image:
self.surf = self.static_image
def dash(self, acc):
self.acc.x = 5 * acc
if acc<0:
self.acc.y = 5 * acc
else:
self.acc.y = -5 * acc
def move(self):
self.acc = vec(0, 1) # Gravity
# Reset moving flag
self.moving = False
pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_q]:
self.acc.x = -ACC
self.moving = True # Set moving to True when moving left
if pressed_keys[K_a] :
self.dash(-ACC)
if pressed_keys[K_d]:
self.acc.x = ACC
self.moving = True # Set moving to True when moving right
if pressed_keys[K_a] :
self.dash(ACC)
# Also consider the player moving if they have significant horizontal velocity
if abs(self.vel.x) > 0.5:
self.moving = True
# Jumping logic
if pressed_keys[K_SPACE] and not self.jumping:
self.vel.y = -30
self.jumping = True
# Apply friction
self.acc.y += self.vel.y * FRIC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# Prevent the player from moving off-screen horizontally
if self.pos.x > WIDTH - self.rect.width / 2:
self.pos.x = WIDTH - self.rect.width / 2
self.vel.x = 0
if self.pos.x < self.rect.width / 2:
self.pos.x = self.rect.width / 2
self.vel.x = 0
self.rect.midbottom = self.pos
# Update animation frame
self.update_animation()
def update(self):
hits = pygame.sprite.spritecollide(self, platforms, False)
if hits:
if self.vel.y > 0:
self.pos.y = hits[0].rect.top
self.vel.y = 0
self.jumping = False
class platform(pygame.sprite.Sprite):
def __init__(self, Long, R, G, B):
super().__init__()
self.surf = pygame.Surface((Long, 20))
self.surf.fill((R, G, B))
self.rect = self.surf.get_rect(center=(WIDTH / 2, HEIGHT - 10))
PT1 = platform(WIDTH, 255, 0, 0)
a = 200
PT2 = platform(a, 0, 0, 255)
PT2.rect.y=(HEIGHT-50)
P1 = Player()
platforms = pygame.sprite.Group()
platforms.add(PT1)
platforms.add(PT2)
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(PT2)
all_sprites.add(P1)
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:
pygame.quit()
sys.exit()
displaysurface.fill((0, 0, 0))
P1.move()
P1.update()
for entity in all_sprites:
displaysurface.blit(entity.surf, entity.rect)
# Gestion déplacement plateforme PT2
if PT2.rect.x < 50:
a = 1
if PT2.rect.x > 1000:
a=-1
PT2.rect.x += 2*a
# Le rectangle P1 reste sur la plateforme mouvante
if P1.rect.colliderect(PT2.rect) and P1.pos.y == PT2.rect.y:
P1.pos.x += 2 * a
# Display FPS
fps = int(FramePerSec.get_fps())
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
displaysurface.blit(fps_text, (10, 10))
# Display player coordinates
pos_text = font.render(f"X: {int(P1.pos.x)}, Y: {int(P1.pos.y)}", True, (255, 255, 255))
displaysurface.blit(pos_text, (10, 40))
pygame.display.update()
FramePerSec.tick(FPS)

BIN
assets/player/dead.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/sound/Death.mp3 Normal file

Binary file not shown.

335
main.py
View File

@@ -1,339 +1,8 @@
import re
import pygame
import sys
from pygame.locals import *
from src.Database.LevelDB import LevelDB
from src.Entity.Enemy import Enemy
from src.Menu.LevelSelectMenu import LevelSelectMenu
from src.game import (
initialize_game,
reset_game_with_checkpoint,
clear_checkpoint_database,
)
from src.constant import GameResources
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
from src.handler import handler
def main():
# Initialize Pygame and game resources
game_resources = GameResources()
displaysurface = game_resources.displaysurface
FramePerSec = game_resources.FramePerSec
font = game_resources.font
FPS = game_resources.FPS
WIDTH = game_resources.WIDTH
HEIGHT = game_resources.HEIGHT
ORIGINAL_WIDTH = game_resources.ORIGINAL_WIDTH
ORIGINAL_HEIGHT = game_resources.ORIGINAL_HEIGHT
fullscreen = game_resources.fullscreen
# Add camera initialization
camera = Camera(WIDTH, HEIGHT, game_resources)
# Game states
MENU = 0
PLAYING = 1
INFINITE = 2
LEADERBOARD = 3
# Initialize game state and objects
current_state = MENU
main_menu = Menu(game_resources)
level_select_menu = None
level_file = "map/levels/1.json"
current_menu = "main"
leaderboard = Leaderboard(WIDTH, HEIGHT, font)
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
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
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
)
# 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)
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 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"
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))
# Draw appropriate screen based on state
if current_state == MENU:
if current_menu == "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
P1.move()
P1.update()
# Update camera to follow player
camera.update(P1)
# Clear screen
displaysurface.fill((0, 0, 0))
for platform in platforms:
if platform.is_moving and platform.movement_type == "linear":
if (
platform.movement_points[0]["x"]
- platform.movement_points[1]["x"]
== 0
):
dir = 0
else:
dir = 1
platform.move_linear(
dir,
platform.movement_points,
platform.movement_speed,
platform.wait_time,
platform.coeff,
)
if background:
parallax_factor = 0.3
bg_x = camera.camera.x * parallax_factor
bg_y = camera.camera.y * parallax_factor
displaysurface.blit(background, (bg_x, bg_y))
# Draw all sprites with camera offset applied
for entity in all_sprites:
# Calculate position adjusted for camera
camera_adjusted_rect = entity.rect.copy()
camera_adjusted_rect.x += camera.camera.x
camera_adjusted_rect.y += camera.camera.y
displaysurface.blit(entity.surf, camera_adjusted_rect)
for sprite in all_sprites:
if isinstance(sprite, Enemy):
sprite.update(P1)
else:
sprite.update()
projectiles.update(WIDTH, HEIGHT, P1, camera)
for projectile in projectiles:
# Calculate position adjusted for camera (comme pour les autres sprites)
camera_adjusted_rect = projectile.rect.copy()
camera_adjusted_rect.x += camera.camera.x
camera_adjusted_rect.y += camera.camera.y
displaysurface.blit(projectile.surf, camera_adjusted_rect)
if checkpoints is not None:
checkpoints_hit = pygame.sprite.spritecollide(P1, checkpoints, False)
else:
checkpoints_hit = []
for checkpoint in checkpoints_hit:
checkpoint.activate()
exits_hit = pygame.sprite.spritecollide(P1, exits, False) if exits else []
for exit in exits_hit:
current_level_match = re.search(r"(\d+)\.json$", level_file)
if current_level_match:
current_level = int(current_level_match.group(1))
next_level = current_level + 1
# Unlock next level
db = LevelDB()
db.unlock_level(next_level)
db.close()
# Return to level select menu
current_state = MENU
current_menu = "level_select"
level_select_menu = LevelSelectMenu(game_resources)
# Display FPS and coordinates (fixed position UI elements)
fps = int(FramePerSec.get_fps())
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
displaysurface.blit(fps_text, (10, 10))
coins_hit = pygame.sprite.spritecollide(P1, collectibles,
False) # Set to False to handle removal in on_collision
for coin in coins_hit:
coin.on_collision() # This will handle the coin removal
P1.collect_coin(displaysurface) # This updates the player's coin counter
P1.draw_dash_cooldown_bar(displaysurface)
pos_text = font.render(
f"X: {int(P1.pos.x)}, Y: {int(P1.pos.y)}", True, (255, 255, 255)
)
displaysurface.blit(pos_text, (10, 40))
P1.draw_dash_cooldown_bar(displaysurface)
P1.draw_lives(displaysurface)
P1.draw_coins(displaysurface)
elif current_state == INFINITE:
# Placeholder for infinite mode
text = font.render("Mode Infini - À implémenter", True, (255, 255, 255))
displaysurface.blit(text, (WIDTH // 2 - text.get_width() // 2, HEIGHT // 2))
elif current_state == LEADERBOARD:
leaderboard.draw(displaysurface)
pygame.display.update()
FramePerSec.tick(FPS)
handler()
if __name__ == "__main__":

285
map/infinite/2e9b8d03.json Normal file
View File

@@ -0,0 +1,285 @@
{
"name": "Niveau Infini 1",
"width": 2400,
"height": 800,
"background": "assets/map/background/desert_bg.jpg",
"gravity": 1.0,
"platforms": [
{
"id": "platform_start",
"x": 180,
"y": 260,
"width": 540,
"height": 60,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": false
},
{
"id": "platform2",
"x": 694,
"y": 259,
"width": 241,
"height": 40,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": false
},
{
"id": "platform3",
"x": 1080,
"y": 167,
"width": 163,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform4",
"x": 1399,
"y": 255,
"width": 184,
"height": 60,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 1399,
"y": 255
},
{
"x": 1539,
"y": 255
}
],
"speed": 2,
"wait_time": 0.5
}
},
{
"id": "platform5",
"x": 1684,
"y": 189,
"width": 197,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform6",
"x": 2030,
"y": 337,
"width": 162,
"height": 60,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 2030,
"y": 337
},
{
"x": 2030,
"y": 462
}
],
"speed": 1,
"wait_time": 0.5
}
},
{
"id": "platform7",
"x": 2291,
"y": 241,
"width": 234,
"height": 20,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 2291,
"y": 241
},
{
"x": 2291,
"y": 395
}
],
"speed": 3,
"wait_time": 0.5
}
},
{
"id": "platform8",
"x": 2671,
"y": 336,
"width": 243,
"height": 20,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": false
},
{
"id": "platform9",
"x": 3021,
"y": 388,
"width": 233,
"height": 60,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 3021,
"y": 388
},
{
"x": 3021,
"y": 498
}
],
"speed": 1,
"wait_time": 0.5
}
},
{
"id": "platform10",
"x": 3380,
"y": 347,
"width": 198,
"height": 20,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 3380,
"y": 347
},
{
"x": 3380,
"y": 475
}
],
"speed": 2,
"wait_time": 0.5
}
},
{
"id": "platform11",
"x": 3704,
"y": 394,
"width": 218,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 3704,
"y": 394
},
{
"x": 3826,
"y": 394
}
],
"speed": 2,
"wait_time": 0.5
}
},
{
"id": "platform12",
"x": 4059,
"y": 198,
"width": 270,
"height": 40,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform13",
"x": 4426,
"y": 254,
"width": 156,
"height": 40,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": false
}
],
"enemies": [
{
"id": "enemy1",
"type": "turret",
"x": 951,
"y": 376,
"patrol_distance": 291
},
{
"id": "enemy2",
"type": "turret",
"x": 2173,
"y": 377,
"patrol_distance": 150
}
],
"checkpoints": [],
"exits": [
{
"x": 2300,
"y": 200,
"width": 50,
"height": 80,
"next_level": "NEXT_INFINITE_LEVEL",
"sprite": "assets/map/exit/door.png"
}
],
"collectibles": [
{
"id": "collectible1",
"type": "shield",
"x": 1013,
"y": 101
},
{
"id": "collectible2",
"type": "shield",
"x": 1749,
"y": 183
},
{
"id": "collectible3",
"type": "coin",
"x": 2070,
"y": 196
},
{
"id": "collectible4",
"type": "health",
"x": 758,
"y": 375
},
{
"id": "collectible5",
"type": "shield",
"x": 681,
"y": 353
},
{
"id": "collectible6",
"type": "shield",
"x": 581,
"y": 163
}
],
"spawn_point": {
"x": 260.0,
"y": 200.0
}
}

225
map/infinite/822377c7.json Normal file
View File

@@ -0,0 +1,225 @@
{
"name": "Niveau Infini 1",
"width": 2400,
"height": 800,
"background": "assets/map/background/desert_bg.jpg",
"gravity": 1.0,
"platforms": [
{
"id": "platform_start",
"x": 180,
"y": 260,
"width": 540,
"height": 60,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": false
},
{
"id": "platform2",
"x": 762,
"y": 316,
"width": 238,
"height": 20,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": false
},
{
"id": "platform3",
"x": 1184,
"y": 317,
"width": 131,
"height": 40,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 1184,
"y": 317
},
{
"x": 1326,
"y": 317
}
],
"speed": 1,
"wait_time": 0.5
}
},
{
"id": "platform4",
"x": 1400,
"y": 225,
"width": 256,
"height": 40,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": false
},
{
"id": "platform5",
"x": 1736,
"y": 282,
"width": 178,
"height": 60,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform6",
"x": 2025,
"y": 170,
"width": 159,
"height": 40,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": false
},
{
"id": "platform7",
"x": 2347,
"y": 367,
"width": 150,
"height": 20,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": false
},
{
"id": "platform8",
"x": 2632,
"y": 385,
"width": 256,
"height": 60,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform9",
"x": 2984,
"y": 227,
"width": 221,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform10",
"x": 3326,
"y": 313,
"width": 273,
"height": 40,
"texture": "assets/map/platform/stone_texture.jpg",
"is_moving": false
},
{
"id": "platform11",
"x": 3715,
"y": 330,
"width": 189,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform12",
"x": 4007,
"y": 304,
"width": 112,
"height": 60,
"texture": "assets/map/platform/wood_texture.jpg",
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 4007,
"y": 304
},
{
"x": 4007,
"y": 407
}
],
"speed": 3,
"wait_time": 0.5
}
},
{
"id": "platform13",
"x": 4216,
"y": 383,
"width": 285,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
}
],
"enemies": [
{
"id": "enemy1",
"type": "turret",
"x": 776,
"y": 356,
"patrol_distance": 121
},
{
"id": "enemy2",
"type": "walker",
"x": 1080,
"y": 305,
"patrol_distance": 254
}
],
"checkpoints": [],
"exits": [
{
"x": 2300,
"y": 200,
"width": 50,
"height": 80,
"next_level": "map/infinite/2e9b8d03.json",
"sprite": "assets/map/exit/door.png"
}
],
"collectibles": [
{
"id": "collectible1",
"type": "health",
"x": 1672,
"y": 119
},
{
"id": "collectible2",
"type": "coin",
"x": 775,
"y": 251
},
{
"id": "collectible3",
"type": "health",
"x": 814,
"y": 207
},
{
"id": "collectible4",
"type": "coin",
"x": 1578,
"y": 278
},
{
"id": "collectible5",
"type": "health",
"x": 2069,
"y": 209
},
{
"id": "collectible6",
"type": "shield",
"x": 1163,
"y": 304
}
],
"spawn_point": {
"x": 260.0,
"y": 200.0
}
}

View File

@@ -4,8 +4,7 @@
"height": 800,
"background": "assets/map/background/forest_bg.jpg",
"gravity": 1.0,
"ground": [
"platforms": [
{
"id": "main_ground",
"x": -1000,
@@ -14,14 +13,6 @@
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
},
{
"id": "pit",
"x": 800,
"y": 780,
"width": 200,
"height": 20,
"is_hole": true
},
{
"id": "main_ground_2",
"x": 1000,
@@ -29,10 +20,7 @@
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
}
],
"platforms": [
},
{
"id": "platform1",
"x": 300,
@@ -54,7 +42,7 @@
"type": "linear",
"points": [
{"x": 700, "y": 300},
{"x": 700, "y": 500}
{"x": 700, "y": 600}
],
"speed": 2.0,
"wait_time": 1.0
@@ -62,8 +50,8 @@
},
{
"id": "platform21",
"x": 900,
"y": 500,
"x": 1200,
"y": 750,
"width": 150,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
@@ -71,8 +59,8 @@
"movement": {
"type": "linear",
"points": [
{"x": 700, "y": 500},
{"x": 800, "y": 500}
{"x": 1200, "y": 550},
{"x": 1500, "y": 550}
],
"speed": 2.0,
"wait_time": 1.0
@@ -89,7 +77,7 @@
"movement": {
"type": "circular",
"center": {"x": 1200, "y": 400},
"radius": 100,
"radius": 3,
"speed": 0.02,
"clockwise": true
}
@@ -176,7 +164,7 @@
"spawn_point": {
"x": 50,
"y": 700
"y": 650
},
"exits": [

View File

@@ -4,8 +4,7 @@
"height": 800,
"background": "assets/map/background/forest_bg.jpg",
"gravity": 1.0,
"ground": [
"platforms": [
{
"id": "main_ground",
"x": -1000,
@@ -14,14 +13,6 @@
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
},
{
"id": "pit",
"x": 800,
"y": 780,
"width": 200,
"height": 20,
"is_hole": true
},
{
"id": "main_ground_2",
"x": 1000,
@@ -29,10 +20,7 @@
"width": 1800,
"height": 200,
"texture": "assets/map/platform/grass_texture.jpg"
}
],
"platforms": [
},
{
"id": "platform1",
"x": 300,
@@ -128,7 +116,7 @@
"spawn_point": {
"x": 50,
"y": 700
"y": 650
},
"exits": [

View File

@@ -4,65 +4,47 @@
"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,
"x": 180,
"y": 260,
"width": 540,
"height": 160,
"texture": "assets/map/platform/grass_texture.jpg",
"is_moving": false
},
{
"id": "platform3",
"id": "platform2",
"x": 320,
"y": 170,
"y": 120,
"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
"is_moving": true,
"movement": {
"type": "linear",
"points": [
{
"x": 320,
"y": 120
},
{
"x": 460,
"y": 120
}
],
"speed": 2,
"wait_time": 1.0
}
}
],
"enemies": [],
"checkpoints": [],
"exits": [
{
"x": 650,
"y": 80,
"x": 555,
"y": -200,
"width": 50,
"height": 80,
"next_level": "map/levels/1.json",
@@ -72,6 +54,6 @@
"collectibles": [],
"spawn_point": {
"x": 260.0,
"y": 320.0
"y": 200.0
}
}

View File

@@ -54,7 +54,7 @@
"type": "linear",
"points": [
{"x": 700, "y": 300},
{"x": 700, "y": 500}
{"x": 700, "y": 600}
],
"speed": 2.0,
"wait_time": 1.0
@@ -62,8 +62,8 @@
},
{
"id": "platform21",
"x": 900,
"y": 500,
"x": 1200,
"y": 750,
"width": 150,
"height": 20,
"texture": "assets/map/platform/grass_texture.jpg",
@@ -71,8 +71,8 @@
"movement": {
"type": "linear",
"points": [
{"x": 700, "y": 500},
{"x": 800, "y": 500}
{"x": 1200, "y": 550},
{"x": 1500, "y": 550}
],
"speed": 2.0,
"wait_time": 1.0
@@ -89,7 +89,7 @@
"movement": {
"type": "circular",
"center": {"x": 1200, "y": 400},
"radius": 100,
"radius": 3,
"speed": 0.02,
"clockwise": true
}

View File

@@ -1,6 +1,7 @@
black==25.1.0
click==8.1.8
mypy-extensions==1.0.0
numpy==2.2.4
packaging==24.2
pathspec==0.12.1
platformdirs==4.3.7

View File

@@ -1,58 +0,0 @@
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

View File

@@ -1,604 +0,0 @@
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

@@ -124,6 +124,7 @@ class Enemy(Entity):
direction=direction,
speed=self.speed,
damage=self.damage,
enemy_proj=True,
)
# Add projectile to the sprite group (to be placed in main.py)

View File

@@ -1,3 +1,4 @@
import numpy as np
import pygame
from src.Entity.Entity import Entity
@@ -16,6 +17,10 @@ class Platform(Entity):
movement_points=[{"x": 0, "y": 0}, {"x": 0, "y": 0}],
movement_speed=0,
wait_time=0,
center=0,
radius=0,
angular_speed=0,
clockwise=False,
):
super().__init__(
pos=(x, y), size=(width, height), color=color, texturePath=texturePath
@@ -29,6 +34,15 @@ class Platform(Entity):
self.movement_speed = movement_speed
self.wait_time = wait_time
self.coeff = -1
self.acc = 0
self.width = width
self.height = height
self.clockwise = clockwise
self.center = center
self.radius = radius
self.angular_speed = angular_speed
self.angle = 0
def move_linear(self, dir, movement_points, movement_speed, wait_time, coeff):
if not dir:
@@ -43,3 +57,25 @@ class Platform(Entity):
a = -1
self.rect.y += a * movement_speed
self.coeff = a
else:
if (self.rect.x <= movement_points[0]["x"]) or (
self.coeff == 1 and not self.rect.x >= movement_points[1]["x"]
):
a = 1
if (self.rect.x >= movement_points[1]["x"]) or (
self.coeff == -1 and not self.rect.x <= movement_points[0]["x"]
):
a = -1
self.rect.x += a * movement_speed
self.coeff = a
def move_circular(self, center, angular_speed, radius, clockwise):
self.angle += angular_speed
if clockwise:
self.rect.x = self.rect.x + radius * np.cos(self.angle)
self.rect.y = self.rect.y + radius * np.sin(self.angle)
else:
self.rect.x = self.rect.x + radius * np.cos(self.angle)
self.rect.y = self.rect.y + radius * np.sin(-self.angle)

View File

@@ -2,6 +2,8 @@ from src.Entity.Entity import Entity
from pygame import *
import pygame
import os
from pygame.math import Vector2 as vec
from src.Entity.Projectile import Projectile
class Player(Entity):
@@ -36,6 +38,7 @@ class Player(Entity):
self.moving = False
self.dashing = False
self.jumping = False
self.highest_position = self.pos.y
# Dash mechanics
self.last_dash_time = 0
@@ -51,6 +54,8 @@ class Player(Entity):
self.invulnerable_duration = 1.5
self.life_icon = None
self.rect = self.surf.get_rect()
# Load images
self.load_images()
@@ -63,6 +68,11 @@ class Player(Entity):
elif self.animation_frames:
self.surf = self.animation_frames[0]
# Attacking
self.last_attack_time = 0
self.attack_start_time = 0
self.attack_cooldown = 2000
def load_images(self):
try:
# Load static image
@@ -271,12 +281,54 @@ class Player(Entity):
)
def update(self):
hits = pygame.sprite.spritecollide(self, self.game_resources.platforms, False)
feet_rect = pygame.Rect(0, 0, self.rect.width * 0.8, 10)
feet_rect.midbottom = self.rect.midbottom
left_side_rect = pygame.Rect(0, 0, 10, self.rect.height * 0.7)
left_side_rect.midleft = self.rect.midleft
right_side_rect = pygame.Rect(0, 0, 10, self.rect.height * 0.7)
right_side_rect.midright = self.rect.midright
hits = []
for platform in self.game_resources.platforms:
platform_top_rect = pygame.Rect(
platform.rect.x, platform.rect.y, platform.rect.width, 5
)
if feet_rect.colliderect(platform_top_rect):
hits.append(platform)
if hits:
if self.vel.y > 0:
self.pos.y = hits[0].rect.top
self.vel.y = 0
self.jumping = False
self.highest_position = self.pos.y
side_hits = []
for platform in self.game_resources.platforms:
platform_left_rect = pygame.Rect(
platform.rect.x, platform.rect.y + 5, 5, platform.rect.height - 5
)
platform_right_rect = pygame.Rect(
platform.rect.right - 5,
platform.rect.y + 5,
5,
platform.rect.height - 5,
)
if right_side_rect.colliderect(platform_left_rect):
side_hits.append(("right", platform))
if left_side_rect.colliderect(platform_right_rect):
side_hits.append(("left", platform))
for side, platform in side_hits:
if side == "right" and self.vel.x > 0:
self.pos.x = platform.rect.left - self.rect.width / 2
self.vel.x = 0
elif side == "left" and self.vel.x < 0:
self.pos.x = platform.rect.right + self.rect.width / 2
self.vel.x = 0
if self.invulnerable:
self.invulnerable_timer += 1 / self.game_resources.FPS
@@ -284,6 +336,14 @@ class Player(Entity):
self.invulnerable = False
self.invulnerable_timer = 0
if self.vel.y <= 0:
self.highest_position = self.pos.y
if self.vel.y > 0:
fall_distance = self.pos.y - self.highest_position
if fall_distance > 500:
self.death()
def take_damage(self, amount=1):
"""Reduce life number if not invulnerable"""
if not self.invulnerable:
@@ -369,3 +429,101 @@ class Player(Entity):
def collect_coin(self, surface):
"""Increment coin counter when collecting a coin"""
self.coins += 1
def attack(self):
"""Do an attack action on the player"""
self.is_attacking = False
current_time = pygame.time.get_ticks()
pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_q] and pressed_keys[K_c]:
if current_time - self.last_attack_time >= self.attack_cooldown:
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
# Calculate direction to player
direction = vec(self.pos.x, self.pos.y)
projectile = Projectile(
pos=vec(self.pos.x, self.pos.y),
direction=direction,
speed=2,
damage=1,
color=(165, 42, 42),
enemy_proj=False,
)
# Add projectile to the sprite group (to be placed in main.py)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)
if pressed_keys[K_d] and pressed_keys[K_c]:
if current_time - self.last_attack_time >= self.attack_cooldown:
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
# Calculate direction to player
direction = vec(self.pos.x, self.pos.y)
projectile = Projectile(
pos=vec(self.pos.x, self.pos.y),
direction=direction,
speed=2,
damage=1,
color=(165, 42, 42),
enemy_proj=False,
)
# Add projectile to the sprite group (to be placed in main.py)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)
if pressed_keys[K_q] and pressed_keys[K_v]:
if current_time - self.last_attack_time >= self.attack_cooldown:
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
# Calculate direction to player
direction = vec(-self.pos.x, 0)
projectile = Projectile(
pos=vec(self.pos.x - 50, self.pos.y - 50),
direction=direction,
speed=2,
damage=1,
color=(165, 42, 42),
enemy_proj=False,
)
# Add projectile to the sprite group (to be placed in main.py)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)
if pressed_keys[K_d] and pressed_keys[K_v]:
if current_time - self.last_attack_time >= self.attack_cooldown:
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
# Calculate direction to player
direction = vec(self.pos.x, 0)
projectile = Projectile(
pos=vec(self.pos.x + 50, self.pos.y - 50),
direction=direction,
speed=2,
damage=1,
color=(165, 42, 42),
enemy_proj=False,
)
# Add projectile to the sprite group (to be placed in main.py)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)

View File

@@ -3,7 +3,9 @@ from pygame.math import Vector2 as vec
class Projectile(pygame.sprite.Sprite):
def __init__(self, pos, direction, speed, damage, color=(0, 0, 255)):
def __init__(
self, pos, direction, speed, damage, color=(0, 0, 255), enemy_proj=False
):
super().__init__()
# Base attributes
@@ -16,8 +18,9 @@ class Projectile(pygame.sprite.Sprite):
self.surf = pygame.Surface((10, 10))
self.surf.fill(color)
self.rect = self.surf.get_rect(center=(pos.x, pos.y))
self.enemy_proj = enemy_proj
def update(self, screen_width, screen_height, player=None, camera=None):
def update(self, screen_width, screen_height, player=None, camera=None, enemy=None):
"""Move the projectile and check for collisions"""
# Movement of the projectile
self.pos += self.direction * self.speed
@@ -41,6 +44,10 @@ class Projectile(pygame.sprite.Sprite):
self.kill()
# Check for collision with player
if player and self.rect.colliderect(player.rect):
if player and self.rect.colliderect(player.rect) and self.enemy_proj:
player.take_damage(self.damage)
self.kill()
if enemy and self.rect.colliderect(enemy.rect) and not self.enemy_proj:
enemy.take_damage(self.damage)
self.kill()

View File

@@ -75,6 +75,10 @@ class LevelEditor:
if level_file:
self._load_level(level_file)
self.panning = False
self.pan_start_pos = None
self.camera_offset = [0, 0]
def _create_toolbar(self):
"""Create buttons for the editor toolbar"""
# Tool selection buttons
@@ -187,7 +191,8 @@ class LevelEditor:
def _snap_to_grid(self, pos):
"""Snap a position to the grid"""
x, y = pos
world_pos = self.screen_to_world(pos)
x, y = world_pos
return (
round(x / self.grid_size) * self.grid_size,
round(y / self.grid_size) * self.grid_size,
@@ -216,16 +221,6 @@ class LevelEditor:
"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": [],
@@ -491,27 +486,13 @@ class LevelEditor:
# 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):
@@ -521,7 +502,6 @@ class LevelEditor:
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
@@ -606,7 +586,7 @@ class LevelEditor:
elif self.selected_object and isinstance(self.selected_object, EditorExit):
if event.key == K_n:
# Cycle de navigation entre les niveaux
# Navigation between levels
level_dir = "map/levels/"
levels = [f for f in os.listdir(level_dir) if f.endswith(".json")]
@@ -640,6 +620,25 @@ class LevelEditor:
self.selected_object.update_appearance()
print(f"Enemy type set to: {self.selected_object.enemy_type}")
if event.type == MOUSEBUTTONDOWN and event.button == 3:
self.panning = True
self.pan_start_pos = event.pos
return None
elif event.type == MOUSEMOTION and self.panning:
dx = event.pos[0] - self.pan_start_pos[0]
dy = event.pos[1] - self.pan_start_pos[1]
self.camera_offset[0] += dx
self.camera_offset[1] += dy
self.pan_start_pos = event.pos
return None
elif event.type == MOUSEBUTTONUP and event.button == 3:
self.panning = False
return None
return None
def draw(self, surface):
@@ -652,25 +651,40 @@ class LevelEditor:
# Clear the screen
surface.fill((40, 40, 40))
# Draw grid
for x in range(0, self.game_resources.WIDTH, self.grid_size):
# Draw grid with camera offset
for x in range(
-self.camera_offset[0] % self.grid_size,
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):
for y in range(
-self.camera_offset[1] % self.grid_size,
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)
for sprite in self.all_sprites:
screen_pos = self.world_to_screen((sprite.rect.x, sprite.rect.y))
temp_rect = sprite.rect.copy()
temp_rect.x, temp_rect.y = screen_pos
surface.blit(sprite.image, temp_rect)
# Draw player start position
if self.player_start:
screen_pos = self.world_to_screen(
(self.player_start.x, self.player_start.y)
)
pygame.draw.circle(
surface,
(0, 255, 0),
(int(self.player_start.x), int(self.player_start.y)),
(int(screen_pos[0]), int(screen_pos[1])),
10,
)
@@ -700,12 +714,13 @@ class LevelEditor:
else:
info_text.append("Moving: No")
y_offset = 120
y_offset = 20
for text in info_text:
text_surf = self.game_resources.font.render(
text, True, (255, 255, 255)
)
surface.blit(text_surf, (10, y_offset))
x_pos = self.game_resources.WIDTH - text_surf.get_width() - 10
surface.blit(text_surf, (x_pos, y_offset))
y_offset += 25
# Draw platform being created
@@ -751,3 +766,11 @@ class LevelEditor:
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
def world_to_screen(self, pos):
"""Convert world coordinates to screen coordinates."""
return pos[0] + self.camera_offset[0], pos[1] + self.camera_offset[1]
def screen_to_world(self, pos):
"""Convert screen coordinates to world coordinates."""
return pos[0] - self.camera_offset[0], pos[1] - self.camera_offset[1]

View File

@@ -0,0 +1,157 @@
import random
import json
import os
import uuid
class InfiniteMapGenerator:
"""Procedural map generator for infinite levels."""
def __init__(self, game_resources):
self.game_resources = game_resources
self.width = 2400
self.height = 800
self.backgrounds = [
"assets/map/background/forest_bg.jpg",
"assets/map/background/desert_bg.jpg",
"assets/map/background/mountain_bg.jpg",
]
self.platform_textures = [
"assets/map/platform/grass_texture.jpg",
"assets/map/platform/stone_texture.jpg",
"assets/map/platform/wood_texture.jpg",
]
# Create the directory for infinite maps if it doesn't exist
os.makedirs("map/infinite", exist_ok=True)
def generate_map(self, difficulty=1):
"""Generate a new infinite map with the specified difficulty level."""
map_data = {
"name": f"Niveau Infini {difficulty}",
"width": self.width,
"height": self.height,
"background": random.choice(self.backgrounds),
"gravity": 1.0,
"platforms": self._generate_platforms(difficulty),
"enemies": self._generate_enemies(difficulty),
"checkpoints": [],
"exits": [self._generate_exit()],
"collectibles": self._generate_collectibles(difficulty),
"spawn_point": {"x": 260.0, "y": 200.0},
}
# Save the map data to a JSON file
map_id = str(uuid.uuid4())[:8]
map_path = f"map/infinite/{map_id}.json"
with open(map_path, "w") as f:
json.dump(map_data, f, indent=2)
return map_path
def _generate_platforms(self, difficulty):
platforms = []
# Starting platform
platforms.append(
{
"id": "platform_start",
"x": 180,
"y": 260,
"width": 540,
"height": 60,
"texture": random.choice(self.platform_textures),
"is_moving": False,
}
)
# Generate additional platforms
num_platforms = 10 + difficulty * 2
last_x = 600
for i in range(num_platforms):
width = random.randint(
max(40, 100 - difficulty * 5), max(120, 300 - difficulty * 10)
)
gap = random.randint(80, 200)
x = last_x + gap
y = random.randint(150, 400)
is_moving = random.random() < min(0.1 + difficulty * 0.05, 0.5)
platform = {
"id": f"platform{i+2}",
"x": x,
"y": y,
"width": width,
"height": random.choice([20, 40, 60]),
"texture": random.choice(self.platform_textures),
"is_moving": is_moving,
}
if is_moving:
move_direction = random.choice(["horizontal", "vertical"])
distance = random.randint(100, 200)
if move_direction == "horizontal":
platform["movement"] = {
"type": "linear",
"points": [{"x": x, "y": y}, {"x": x + distance, "y": y}],
"speed": random.randint(1, 3),
"wait_time": 0.5,
}
else:
platform["movement"] = {
"type": "linear",
"points": [{"x": x, "y": y}, {"x": x, "y": y + distance}],
"speed": random.randint(1, 3),
"wait_time": 0.5,
}
platforms.append(platform)
last_x = x + width
return platforms
def _generate_enemies(self, difficulty):
enemies = []
num_enemies = difficulty * 2
enemy_types = ["walker", "flyer", "turret"]
for i in range(num_enemies):
enemy = {
"id": f"enemy{i+1}",
"type": random.choice(enemy_types),
"x": random.randint(600, self.width - 200),
"y": random.randint(100, 400),
"patrol_distance": random.randint(100, 300),
}
enemies.append(enemy)
return enemies
def _generate_collectibles(self, difficulty):
collectibles = []
num_collectibles = 5 + difficulty
collectible_types = ["coin", "health", "shield"]
for i in range(num_collectibles):
collectible = {
"id": f"collectible{i+1}",
"type": random.choice(collectible_types),
"x": random.randint(400, self.width - 100),
"y": random.randint(100, 400),
}
collectibles.append(collectible)
return collectibles
def _generate_exit(self):
return {
"x": self.width - 100,
"y": 200,
"width": 50,
"height": 80,
"next_level": "NEXT_INFINITE_LEVEL",
"sprite": "assets/map/exit/door.png",
}

View File

@@ -0,0 +1,81 @@
import os
import json
import glob
from src.Map.Infinite.InfiniteMapGenerator import InfiniteMapGenerator
class InfiniteMapManager:
"""Handle infinite map generation and management."""
def __init__(self, game_resources):
self.game_resources = game_resources
self.map_generator = InfiniteMapGenerator(game_resources)
self.current_level = 0
self.active_maps = []
self.difficulty = 1
def start_infinite_mode(self):
"""Start the infinite mode by generating the first two maps."""
self._clean_old_maps()
# Generate the first two maps
first_map = self.map_generator.generate_map(difficulty=self.difficulty)
second_map = self.map_generator.generate_map(difficulty=self.difficulty)
# Configure the first map to point to the second map
self._update_exit_target(first_map, second_map)
self.active_maps = [first_map, second_map]
self.current_level = 1
return first_map
def advance_to_next_level(self):
"""Progress to the next level in infinite mode and delete the previous one."""
# Delete the oldest map
if self.active_maps:
old_map = self.active_maps.pop(0)
try:
os.remove(old_map)
except:
print(f"Error: Unable to delete {old_map}")
# Up the difficulty every 3 levels
self.current_level += 1
if self.current_level % 3 == 0:
self.difficulty = min(10, self.difficulty + 1)
# Generate a new map
new_map = self.map_generator.generate_map(difficulty=self.difficulty)
# Update the exit target of the last map to point to the new one
if self.active_maps:
self._update_exit_target(self.active_maps[0], new_map)
self.active_maps.append(new_map)
return self.active_maps[0]
def _update_exit_target(self, map_path, next_map_path):
"""Update the exit of the current map to point to the next map."""
try:
with open(map_path, "r") as f:
map_data = json.load(f)
if "exits" in map_data and map_data["exits"]:
for exit_obj in map_data["exits"]:
exit_obj["next_level"] = next_map_path
with open(map_path, "w") as f:
json.dump(map_data, f, indent=2)
except Exception as e:
print(f"Error while updating exit: {e}")
def _clean_old_maps(self):
"""Delete all old infinite maps."""
map_files = glob.glob("map/infinite/*.json")
for file in map_files:
try:
os.remove(file)
except:
print(f"Error: Unable to delete {file}")

View File

@@ -56,21 +56,6 @@ class MapParser:
self.checkpoints.empty()
self.exits.empty()
# Create ground elements
if "ground" in map_data:
for ground in map_data["ground"]:
if not ground.get("is_hole", False):
platform = Platform(
ground["width"],
ground["height"],
ground["x"] + ground["width"] / 2,
ground["y"],
(255, 0, 0),
ground["texture"],
)
self.platforms.add(platform)
self.all_sprites.add(platform)
# Create enemies
if "enemies" in map_data:
# Create enemies

View File

@@ -27,6 +27,9 @@ class GameResources:
pygame.display.set_caption("Project Sanic")
self.FramePerSec = pygame.time.Clock()
self.infinite_manager = None
self.infinite_mode = False
# Font
try:
self.font = pygame.font.SysFont("Arial", 20)

View File

@@ -9,6 +9,7 @@ from src.Entity.Platform import Platform
from src.Entity.Player import Player
from src.Map.parser import MapParser
from src.Database.CheckpointDB import CheckpointDB
from src.Map.Infinite.InfiniteMapManager import InfiniteMapManager
def initialize_game(game_resources, map_file="map/levels/1.json"):
@@ -53,20 +54,17 @@ 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")
background = map_objects.get("background", None)
if background is None:
background = pygame.Surface((game_resources.WIDTH, game_resources.HEIGHT))
background.fill((0, 0, 0))
return (
map_objects["player"],
None,
map_objects["platforms"],
map_objects["all_sprites"],
parser.background,
background,
map_objects["checkpoints"],
exits,
map_objects["collectibles"],
@@ -126,5 +124,31 @@ def clear_level_progress():
print(f"Error clearing level progress: {e}")
def start_infinite_mode(game_resources):
"""Start the infinite mode of the game"""
# Create a new InfiniteMapManager
infinite_manager = InfiniteMapManager(game_resources)
game_resources.infinite_manager = infinite_manager
game_resources.infinite_mode = True
# Generate the first level
first_level = infinite_manager.start_infinite_mode()
# Initialize the game with the generated level
return initialize_game(game_resources, first_level)
def handle_exit_collision(exit_obj, game_resources, level_file):
"""Handle exit collision and transition to next level, including infinite mode"""
next_level = exit_obj.next_level
# Mod infinite: if the next level is "NEXT_INFINITE_LEVEL", generate a new level
if hasattr(game_resources, "infinite_mode") and game_resources.infinite_mode:
if next_level == "NEXT_INFINITE_LEVEL":
next_level = game_resources.infinite_manager.advance_to_next_level()
return initialize_game(game_resources, next_level)
if __name__ == "__main__":
print("Please run the game using main.py")

409
src/handler.py Normal file
View File

@@ -0,0 +1,409 @@
import re
import pygame
import sys
from pygame.locals import *
import numpy as np
from src.Database.LevelDB import LevelDB
from src.Entity.Enemy import Enemy
from src.Menu.LevelSelectMenu import LevelSelectMenu
from src.game import (
initialize_game,
reset_game_with_checkpoint,
clear_checkpoint_database,
start_infinite_mode,
handle_exit_collision,
)
from src.constant import GameResources
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 handler():
# Initialize Pygame and game resources
game_resources = GameResources()
displaysurface = game_resources.displaysurface
FramePerSec = game_resources.FramePerSec
font = game_resources.font
FPS = game_resources.FPS
WIDTH = game_resources.WIDTH
HEIGHT = game_resources.HEIGHT
ORIGINAL_WIDTH = game_resources.ORIGINAL_WIDTH
ORIGINAL_HEIGHT = game_resources.ORIGINAL_HEIGHT
fullscreen = game_resources.fullscreen
# Add camera initialization
camera = Camera(WIDTH, HEIGHT, game_resources)
# Game states
MENU = 0
PLAYING = 1
INFINITE = 2
LEADERBOARD = 3
DEATH_SCREEN = 4
# Initialize death screen
death_timer = 0
death_display_time = 2
checkpoint_data = None
try:
death_image = pygame.image.load("assets/player/dead.jpg")
print("Image dead.jpg chargée avec succès")
except Exception as e:
print(f"Erreur de chargement de l'image: {e}")
death_image = None
try:
death_sound = pygame.mixer.Sound("assets/sound/Death.mp3")
death_display_time = death_sound.get_length()
print(f"Son Death.mp3 chargé avec succès, durée: {death_display_time} secondes")
except Exception as e:
print(f"Erreur de chargement du son Death.mp3: {e}")
death_sound = None
death_display_time = 2
# Initialize game state and objects
current_state = MENU
main_menu = Menu(game_resources)
level_select_menu = None
level_file = "map/levels/1.json"
current_menu = "main"
leaderboard = Leaderboard(WIDTH, HEIGHT, font)
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)
except pygame.error:
print("Error while initializing joysticks")
# Main game loop
running = True
while running:
try:
events = []
dt = FramePerSec.get_time() / 1000.0
try:
events = pygame.event.get()
except Exception as e:
print(f"Error while getting events: {e}")
pygame.joystick.quit()
pygame.joystick.init()
continue
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
)
# Update window dimensions
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
elif event.type == USEREVENT:
if event.dict.get("action") == "player_death":
current_state = DEATH_SCREEN
death_timer = 0
if death_sound:
death_sound.play()
db = CheckpointDB()
checkpoint_data = db.get_checkpoint(level_file)
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 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"
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"Error while processing events: {e}")
continue
# Clear screen
displaysurface.fill((0, 0, 0))
# Draw appropriate screen based on state
if current_state == MENU:
if current_menu == "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
P1.move()
P1.update()
P1.attack()
# Update camera to follow player
camera.update(P1)
# Clear screen
displaysurface.fill((0, 0, 0))
for platform in platforms:
if platform.is_moving and platform.movement_type == "linear":
if (
platform.movement_points[0]["x"]
- platform.movement_points[1]["x"]
== 0
):
dir = 0
else:
dir = 1
if (
P1.rect.colliderect(platform.rect)
and P1.pos.y == platform.rect.y
):
P1.pos.x += platform.movement_speed * platform.coeff
platform.move_linear(
dir,
platform.movement_points,
platform.movement_speed,
platform.wait_time,
platform.coeff,
)
if platform.is_moving and platform.movement_type == "circular":
if (
P1.rect.colliderect(platform.rect)
and P1.pos.y == platform.rect.y
and platform.clockwise
):
P1.pos.x = P1.pos.x + platform.radius * np.cos(platform.angle)
P1.pos.y = P1.pos.y + platform.radius * np.sin(platform.angle)
if (
P1.rect.colliderect(platform.rect)
and P1.pos.y == platform.rect.y
and not platform.clockwise
):
P1.pos.x = P1.pos.x + platform.radius * np.cos(platform.angle)
P1.pos.y = P1.pos.y + platform.radius * np.sin(-platform.angle)
platform.move_circular(
platform.center,
platform.angular_speed,
platform.radius,
platform.clockwise,
)
if background:
parallax_factor = 0.3
bg_x = camera.camera.x * parallax_factor
bg_y = camera.camera.y * parallax_factor
displaysurface.blit(background, (bg_x, bg_y))
# Draw all sprites with camera offset applied
for entity in all_sprites:
# Calculate position adjusted for camera
camera_adjusted_rect = entity.rect.copy()
camera_adjusted_rect.x += camera.camera.x
camera_adjusted_rect.y += camera.camera.y
displaysurface.blit(entity.surf, camera_adjusted_rect)
for sprite in all_sprites:
if isinstance(sprite, Enemy):
sprite.update(P1)
projectiles.update(WIDTH, HEIGHT, P1, camera, sprite)
else:
sprite.update()
for projectile in projectiles:
# Calculate position adjusted for camera (comme pour les autres sprites)
camera_adjusted_rect = projectile.rect.copy()
camera_adjusted_rect.x += camera.camera.x
camera_adjusted_rect.y += camera.camera.y
displaysurface.blit(projectile.surf, camera_adjusted_rect)
if checkpoints is not None:
checkpoints_hit = pygame.sprite.spritecollide(P1, checkpoints, False)
else:
checkpoints_hit = []
for checkpoint in checkpoints_hit:
checkpoint.activate()
exits_hit = pygame.sprite.spritecollide(P1, exits, False) if exits else []
for exit in exits_hit:
if (
hasattr(game_resources, "infinite_mode")
and game_resources.infinite_mode
):
# Mod infinit : load the next level without the menu
P1, P1T, platforms, all_sprites, background, checkpoints, exits = (
handle_exit_collision(exit, game_resources, level_file)
)
else:
# Mod normal : unlock the next level and return to the menu
current_level_match = re.search(r"(\d+)\.json$", level_file)
if current_level_match:
current_level = int(current_level_match.group(1))
next_level = current_level + 1
# Unlock next level
db = LevelDB()
db.unlock_level(next_level)
db.close()
# Return to level select menu
current_state = MENU
current_menu = "level_select"
level_select_menu = LevelSelectMenu(game_resources)
# Display FPS and coordinates (fixed position UI elements)
fps = int(FramePerSec.get_fps())
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
displaysurface.blit(fps_text, (10, 10))
P1.draw_dash_cooldown_bar(displaysurface)
pos_text = font.render(
f"X: {int(P1.pos.x)}, Y: {int(P1.pos.y)}", True, (255, 255, 255)
)
displaysurface.blit(pos_text, (10, 40))
P1.draw_dash_cooldown_bar(displaysurface)
P1.draw_lives(displaysurface)
elif current_state == INFINITE:
P1, P1T, platforms, all_sprites, background, checkpoints, exits = (
start_infinite_mode(game_resources)
)
current_state = PLAYING
elif current_state == LEADERBOARD:
leaderboard.draw(displaysurface)
elif current_state == DEATH_SCREEN:
displaysurface.fill((0, 0, 0))
if death_image:
scaled_image = pygame.transform.scale(death_image, (WIDTH, HEIGHT))
image_rect = scaled_image.get_rect(center=(WIDTH // 2, HEIGHT // 2))
displaysurface.blit(scaled_image, image_rect)
# Timer for death screen
death_timer += dt
if death_timer >= death_display_time:
if checkpoint_data:
P1, platforms, all_sprites, background, checkpoints = (
reset_game_with_checkpoint(level_file, game_resources)
)
projectiles.empty()
current_state = PLAYING
else:
current_state = MENU
pygame.display.update()
FramePerSec.tick(FPS)