diff --git a/assets/player/Sanic Annimate.png b/assets/player/Sanic Annimate.png new file mode 100644 index 0000000..0b3c10f Binary files /dev/null and b/assets/player/Sanic Annimate.png differ diff --git a/assets/player/Sanic Base.png b/assets/player/Sanic Base.png new file mode 100644 index 0000000..7c7f5e0 Binary files /dev/null and b/assets/player/Sanic Base.png differ diff --git a/assets/player/Sanic Boule Annimate.png b/assets/player/Sanic Boule Annimate.png new file mode 100644 index 0000000..5430368 Binary files /dev/null and b/assets/player/Sanic Boule Annimate.png differ diff --git a/assets/player/Sanic Boule.png b/assets/player/Sanic Boule.png new file mode 100644 index 0000000..993f857 Binary files /dev/null and b/assets/player/Sanic Boule.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..6c88ef8 --- /dev/null +++ b/main.py @@ -0,0 +1,102 @@ +import pygame +import sys +from pygame.locals import * + +# Import from pygame_basics +from src.game import ( + initialize_game, +) + +from src.constant import displaysurface, FramePerSec, font, FPS, WIDTH, HEIGHT + +# Import from menu +from src.Menu.Menu import Menu +from src.Menu.Leaderboard import Leaderboard + + +def main(): + # Game states + MENU = 0 + PLAYING = 1 + INFINITE = 2 + LEADERBOARD = 3 + + # Initialize game state and objects + current_state = MENU + menu = Menu() + leaderboard = Leaderboard() + + # Initialize game components + P1, PT1, platforms, all_sprites = initialize_game() + + # Main game loop + while True: + for event in pygame.event.get(): + if event.type == QUIT: + pygame.quit() + sys.exit() + elif event.type == KEYDOWN: + if event.key == K_ESCAPE: + if current_state in [PLAYING, INFINITE]: + current_state = MENU + else: + pygame.quit() + sys.exit() + + # Handle menu events + if current_state == MENU: + action = menu.handle_event(event) + if action == "play": + current_state = PLAYING + elif action == "infinite": + current_state = INFINITE + elif action == "leaderboard": + current_state = LEADERBOARD + elif action == "quit": + pygame.quit() + sys.exit() + + # Handle leaderboard events + elif current_state == LEADERBOARD: + action = leaderboard.handle_event(event) + if action == "menu": + current_state = MENU + + # Clear screen + displaysurface.fill((0, 0, 0)) + + # Draw appropriate screen based on state + if current_state == MENU: + menu.draw(displaysurface) + + elif current_state == PLAYING: + # Regular game code + P1.move() + P1.update() + for entity in all_sprites: + displaysurface.blit(entity.surf, entity.rect) + + # Display FPS and coordinates + fps = int(FramePerSec.get_fps()) + fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255)) + displaysurface.blit(fps_text, (10, 10)) + + 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)) + + 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) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index fa37066..8a97ad8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,7 @@ +black==25.1.0 +click==8.1.8 +mypy-extensions==1.0.0 +packaging==24.2 +pathspec==0.12.1 +platformdirs==4.3.7 pygame==2.6.1 \ No newline at end of file diff --git a/src/Entity/Entity.py b/src/Entity/Entity.py new file mode 100644 index 0000000..b764b1a --- /dev/null +++ b/src/Entity/Entity.py @@ -0,0 +1,28 @@ +import pygame +from pygame.math import Vector2 as vec + + +class Entity(pygame.sprite.Sprite): + def __init__(self, pos=(0, 0), size=(30, 30), color=(255, 255, 255)): + super().__init__() + self.pos = vec(pos) + self.vel = vec(0, 0) + self.acc = vec(0, 0) + + # Default surface + self.surf = pygame.Surface(size) + self.surf.fill(color) + self.rect = self.surf.get_rect() + self.update_rect() + + def update_rect(self): + """Update rect position based on entity position""" + self.rect.midbottom = self.pos + + def update(self): + """Update entity state - to be overridden by child classes""" + pass + + def move(self): + """Handle movement - to be overridden by child classes""" + pass diff --git a/src/Entity/Platform.py b/src/Entity/Platform.py new file mode 100644 index 0000000..3feb51e --- /dev/null +++ b/src/Entity/Platform.py @@ -0,0 +1,10 @@ +import pygame +from src.Entity.Entity import Entity +from src.constant import WIDTH, HEIGHT + + +class Platform(Entity): + def __init__(self, width, height, x, y, color=(255, 0, 0)): + super().__init__(pos=(x, y), size=(width, height), color=color) + # Override rect setting for platforms if needed + self.rect = self.surf.get_rect(center=(x, y)) diff --git a/src/Entity/Player.py b/src/Entity/Player.py new file mode 100644 index 0000000..b822abc --- /dev/null +++ b/src/Entity/Player.py @@ -0,0 +1,171 @@ +from src.Entity.Entity import Entity +from src.constant import WIDTH, vec, ACC, FRIC, platforms +from pygame import * +import pygame +import os +import time + + +class Player(Entity): + def __init__(self, width=120, height=120, x=10, y=385): + super().__init__(pos=(x, y), size=(width, height), color=(128, 255, 40)) + + # Animation variables + self.animation_frames = [] + self.jump_frames = [] + self.dash_frames = [] + self.current_frame = 0 + self.animation_speed = 0.1 + self.last_update = time.time() + self.static_image = None + self.moving = False + self.dashing = False + self.jumping = False + + # Load images + self.load_images() + + # Override initial surface if images are loaded + if self.static_image: + self.surf = self.static_image + elif self.animation_frames: + self.surf = self.animation_frames[0] + + 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, (120, 120) + ) + + # Load regular 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, (120, 120)) + self.animation_frames.append(frame) + + # Load jump animation sprite sheet + if os.path.isfile("assets/player/Sanic Boule.png"): + self.jump_frames.append( + pygame.transform.scale( + pygame.image.load( + "assets/player/Sanic Boule.png" + ).convert_alpha(), + (120, 120), + ) + ) + + # Load dash animation sprite sheet + if os.path.isfile("assets/player/Sanic Boule Annimate.png"): + dash_sheet = pygame.image.load( + "assets/player/Sanic Boule Annimate.png" + ).convert_alpha() + + # Extract the frames with 2000px gap + dash_frame_height = dash_sheet.get_height() + + for i in range(4): # Assuming 4 frames + frame = dash_sheet.subsurface( + (i * 2000, 0, dash_frame_height, dash_frame_height) + ) + frame = pygame.transform.scale(frame, (120, 120)) + self.dash_frames.append(frame) + + except Exception as e: + print(f"Error loading player images: {e}") + + def update_animation(self): + current_time = time.time() + + # Priority: Dashing > Jumping > Moving > Static + if self.dashing and self.dash_frames: + if current_time - self.last_update > self.animation_speed: + self.current_frame = (self.current_frame + 1) % len(self.dash_frames) + self.surf = self.dash_frames[self.current_frame] + self.last_update = current_time + elif self.jumping and self.jump_frames: + self.surf = self.jump_frames[0] # Use jump frame + elif self.moving and self.animation_frames: + if current_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 = current_time + elif self.static_image: + self.surf = self.static_image + + def dash(self, acc): + self.acc.x = 5 * acc + self.dashing = True # Set dashing flag + + def move(self): + self.acc = vec(0, 1) # Gravity + + # Reset flags + self.moving = False + self.dashing = False + + pressed_keys = pygame.key.get_pressed() + if pressed_keys[K_q]: + self.acc.x = -ACC + self.moving = True + if pressed_keys[K_a]: + self.dash(-ACC) + if pressed_keys[K_d]: + self.acc.x = ACC + self.moving = True + 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 diff --git a/src/Menu/Button.py b/src/Menu/Button.py new file mode 100644 index 0000000..3d4c2dd --- /dev/null +++ b/src/Menu/Button.py @@ -0,0 +1,46 @@ +from pygame.locals import * +import pygame +from src.constant import font + + +class Button: + def __init__(self, text, x, y, width, height, action=None): + self.text = text + self.x = x + self.y = y + self.width = width + self.height = height + self.action = action + self.hover = False + + def draw(self, surface): + # Button colors + color = (100, 149, 237) if self.hover else (65, 105, 225) + border_color = (255, 255, 255) + + # Draw button with border + pygame.draw.rect(surface, color, (self.x, self.y, self.width, self.height)) + pygame.draw.rect( + surface, border_color, (self.x, self.y, self.width, self.height), 2 + ) + + # Draw text + text_surf = font.render(self.text, True, (255, 255, 255)) + text_rect = text_surf.get_rect( + center=(self.x + self.width / 2, self.y + self.height / 2) + ) + surface.blit(text_surf, text_rect) + + def is_hover(self, pos): + return ( + self.x <= pos[0] <= self.x + self.width + and self.y <= pos[1] <= self.y + self.height + ) + + def handle_event(self, event): + if event.type == MOUSEMOTION: + self.hover = self.is_hover(event.pos) + elif event.type == MOUSEBUTTONDOWN: + if self.hover and self.action: + return self.action + return None diff --git a/src/Menu/Leaderboard.py b/src/Menu/Leaderboard.py new file mode 100644 index 0000000..8dc69da --- /dev/null +++ b/src/Menu/Leaderboard.py @@ -0,0 +1,57 @@ +import pygame +from src.constant import WIDTH, HEIGHT, font +from src.Menu.Button import Button + + +class Leaderboard: + def __init__(self): + self.tabs = ["Mode Normal", "Mode Infini"] + self.current_tab = 0 + self.scores = { + 0: [("Player1", 1000), ("Player2", 800), ("Player3", 600)], + 1: [("Player1", 2000), ("Player2", 1500), ("Player3", 1200)], + } + self.back_button = Button("Retour", 20, HEIGHT - 70, 120, 50, "menu") + tab_width = 150 + self.tab_buttons = [ + Button(self.tabs[0], WIDTH // 2 - tab_width, 80, tab_width, 40, "tab_0"), + Button(self.tabs[1], WIDTH // 2, 80, tab_width, 40, "tab_1"), + ] + + def draw(self, surface): + # Draw title + title = pygame.font.SysFont("Arial", 48).render( + "Classement", True, (0, 191, 255) + ) + title_rect = title.get_rect(center=(WIDTH // 2, 40)) + surface.blit(title, title_rect) + + # Draw tabs + for i, button in enumerate(self.tab_buttons): + if i == self.current_tab: + pygame.draw.rect( + surface, + (100, 149, 237), + (button.x, button.y, button.width, button.height), + ) + button.draw(surface) + + # Draw scores + y_pos = 150 + for i, (name, score) in enumerate(self.scores[self.current_tab]): + rank_text = font.render(f"{i+1}. {name}: {score}", True, (255, 255, 255)) + surface.blit(rank_text, (WIDTH // 2 - rank_text.get_width() // 2, y_pos)) + y_pos += 40 + + self.back_button.draw(surface) + + def handle_event(self, event): + action = self.back_button.handle_event(event) + if action: + return action + + for i, button in enumerate(self.tab_buttons): + action = button.handle_event(event) + if action and action.startswith("tab_"): + self.current_tab = int(action.split("_")[1]) + return None diff --git a/src/Menu/Menu.py b/src/Menu/Menu.py new file mode 100644 index 0000000..52a02e8 --- /dev/null +++ b/src/Menu/Menu.py @@ -0,0 +1,79 @@ +import pygame +from src.constant import HEIGHT, WIDTH +from src.Menu.Button import Button + + +class Menu: + def __init__(self): + self.buttons = [] + button_width = 250 + button_height = 60 + button_spacing = 20 + start_y = HEIGHT // 2 - 100 + + # Create buttons centered horizontally + self.buttons.append( + Button( + "Jouer", + WIDTH // 2 - button_width // 2, + start_y, + button_width, + button_height, + "play", + ) + ) + + start_y += button_height + button_spacing + self.buttons.append( + Button( + "Jouer en mode infini", + WIDTH // 2 - button_width // 2, + start_y, + button_width, + button_height, + "infinite", + ) + ) + + start_y += button_height + button_spacing + self.buttons.append( + Button( + "Classement", + WIDTH // 2 - button_width // 2, + start_y, + button_width, + button_height, + "leaderboard", + ) + ) + + start_y += button_height + button_spacing + self.buttons.append( + Button( + "Quitter", + WIDTH // 2 - button_width // 2, + start_y, + button_width, + button_height, + "quit", + ) + ) + + def draw(self, surface): + # Draw title + title = pygame.font.SysFont("Arial", 72).render( + "Project Sanic", True, (0, 191, 255) + ) + title_rect = title.get_rect(center=(WIDTH // 2, HEIGHT // 4)) + surface.blit(title, title_rect) + + # Draw buttons + for button in self.buttons: + button.draw(surface) + + def handle_event(self, event): + for button in self.buttons: + action = button.handle_event(event) + if action: + return action + return None diff --git a/src/constant.py b/src/constant.py new file mode 100644 index 0000000..ccce002 --- /dev/null +++ b/src/constant.py @@ -0,0 +1,20 @@ +import pygame + +pygame.init() + +FPS = 60 +ACC = 0.5 +FRIC = -0.12 +WIDTH = 800 +HEIGHT = 600 +platforms = pygame.sprite.Group() +vec = pygame.math.Vector2 +displaysurface = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Game") +FramePerSec = pygame.time.Clock() +all_sprites = pygame.sprite.Group() + +try: + font = pygame.font.SysFont("Arial", 20) +except: + font = pygame.font.Font(None, 20) diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..d2ed3fa --- /dev/null +++ b/src/game.py @@ -0,0 +1,62 @@ +import pygame +import sys +from pygame.locals import * +from src.Entity.Platform import Platform +from src.Entity.Player import Player +from src.constant import displaysurface, FramePerSec, font, FPS, platforms, all_sprites + + +def initialize_game(): + # Clear previous sprites if any + platforms.empty() + all_sprites.empty() + + # Create new game objects + PT1 = Platform(1200, 20, 200, 400) + P1 = Player() + + # Add them to the groups + platforms.add(PT1) + all_sprites.add(PT1) + all_sprites.add(P1) + + return P1, PT1, platforms, all_sprites + + +def run_game(P1, all_sprites): + """Run the main game loop without menu system""" + 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) + + # 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) + + +if __name__ == "__main__": + P1, PT1, platforms, all_sprites = initialize_game() + run_game(P1, all_sprites)