mirror of
https://github.com/BreizhHardware/project_sanic.git
synced 2026-03-18 21:50:33 +01:00
Merge remote-tracking branch 'origin/dev' into dev_clement
# Conflicts: # main.py # src/Entity/Player.py
This commit is contained in:
223
Test.py
223
Test.py
@@ -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
BIN
assets/player/dead.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/sound/Death.mp3
Normal file
BIN
assets/sound/Death.mp3
Normal file
Binary file not shown.
335
main.py
335
main.py
@@ -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
285
map/infinite/2e9b8d03.json
Normal 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
225
map/infinite/822377c7.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]
|
||||
|
||||
157
src/Map/Infinite/InfiniteMapGenerator.py
Normal file
157
src/Map/Infinite/InfiniteMapGenerator.py
Normal 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",
|
||||
}
|
||||
81
src/Map/Infinite/InfiniteMapManager.py
Normal file
81
src/Map/Infinite/InfiniteMapManager.py
Normal 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}")
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
40
src/game.py
40
src/game.py
@@ -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
409
src/handler.py
Normal 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)
|
||||
Reference in New Issue
Block a user