Feat(Instructions) - Add instructions screen to display game controls; implement event handling for navigation and drawing functionality.

This commit is contained in:
2025-04-10 22:32:51 +02:00
parent bb6eb2d77f
commit 654e9c66e7
3 changed files with 288 additions and 142 deletions

View File

@@ -21,6 +21,8 @@ class Player(Entity):
self.jump_button = 0
self.dash_button = 1
self.attack_button = 2
self.menu_button = 3
try:
if pygame.joystick.get_count() > 0:
@@ -540,152 +542,170 @@ class Player(Entity):
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},
)
)
joystick_attack = False
if self.has_joystick and self.joystick:
try:
if self.joystick.get_numbuttons() > self.attack_button:
joystick_attack = self.joystick.get_button(self.attack_button)
except pygame.error:
pass
if pressed_keys[K_q] and pressed_keys[K_v]:
if current_time - self.last_attack_time >= self.attack_cooldown:
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
attack_sound.set_volume(0.4)
attack_sound.play()
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,
texturePath="assets/player/Boule de feu.png",
size=(50, 50),
)
# 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 self.projectiles > 0:
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,
texturePath="assets/player/Boule de feu.png",
)
# 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},
)
)
self.projectiles -= 1
if (
joystick_attack
and current_time - self.last_attack_time >= self.attack_cooldown
and self.projectiles > 0
):
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
attack_sound.set_volume(0.4)
attack_sound.play()
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
if pressed_keys[K_d] and pressed_keys[K_v]:
if current_time - self.last_attack_time >= self.attack_cooldown:
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
attack_sound.set_volume(0.4)
attack_sound.play()
self.is_attacking = True
self.attack_start_time = current_time
self.last_attack_time = current_time
# Calculate direction to player
# Direction en fonction de où le personnage est tourné
if self.facing_right:
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,
texturePath="assets/player/Boule de feu.png",
size=(50, 50),
position = vec(self.pos.x + 50, self.pos.y - 50)
else:
direction = vec(-self.pos.x, 0)
position = vec(self.pos.x - 50, self.pos.y - 50)
projectile = Projectile(
pos=position,
direction=direction,
speed=2,
damage=1,
color=(165, 42, 42),
enemy_proj=False,
texturePath="assets/player/Boule de feu.png",
size=(50, 50),
)
# Ajouter le projectile au groupe de sprites
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
# 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},
)
)
self.projectiles -= 1
if (
pressed_keys[K_q]
and pressed_keys[K_c]
and 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 self.projectiles > 0:
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,
texturePath="assets/player/Boule de feu.png",
)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)
self.projectiles -= 1
)
if (
pressed_keys[K_d]
and pressed_keys[K_c]
and 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]
and current_time - self.last_attack_time >= self.attack_cooldown
and self.projectiles > 0
):
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
attack_sound.set_volume(0.4)
attack_sound.play()
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,
texturePath="assets/player/Boule de feu.png",
)
# 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},
)
)
self.projectiles -= 1
if (
pressed_keys[K_d]
and pressed_keys[K_v]
and current_time - self.last_attack_time >= self.attack_cooldown
and self.projectiles > 0
):
attack_sound = pygame.mixer.Sound("assets/sound/Boule de feu.mp3")
attack_sound.set_volume(0.4)
attack_sound.play()
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,
texturePath="assets/player/Boule de feu.png",
)
pygame.event.post(
pygame.event.Event(
pygame.USEREVENT,
{"action": "create_projectile", "projectile": projectile},
)
)
self.projectiles -= 1
def add_projectiles(self):
"""Set player projectiles to 3 and show floating text"""

View File

@@ -0,0 +1,97 @@
import pygame
import math
from src.Menu.BackgroundManager import BackgroundManager
class InstructionsScreen:
def __init__(self, game_resources):
self.game_resources = game_resources
self.bg_manager = BackgroundManager(game_resources.WIDTH, game_resources.HEIGHT)
self.title_font = pygame.font.SysFont("Arial", 72)
self.text_font = pygame.font.SysFont("Arial", 32)
self.blink_timer = 0
self.blink_speed = 0.5
def draw(self, surface):
self.bg_manager.draw(surface)
def render_text_with_outline(text, font, text_color, outline_color):
text_surface = font.render(text, True, text_color)
outline_surface = font.render(text, True, outline_color)
w, h = text_surface.get_size()
outline_surf = pygame.Surface((w + 2, h + 2), pygame.SRCALPHA)
# Dessiner le contour en décalant le texte
offsets = [
(1, 1),
(1, -1),
(-1, 1),
(-1, -1),
(1, 0),
(-1, 0),
(0, 1),
(0, -1),
]
for dx, dy in offsets:
outline_surf.blit(outline_surface, (dx + 1, dy + 1))
# Dessiner le texte principal au centre
outline_surf.blit(text_surface, (1, 1))
return outline_surf
title_surf = render_text_with_outline(
"Game control", self.title_font, (255, 255, 255), (0, 0, 0)
)
title_rect = title_surf.get_rect(center=(self.game_resources.WIDTH // 2, 100))
surface.blit(title_surf, title_rect)
instructions = [
"Q : Move left",
"D : Move right",
"A : Dash",
"Espace : Jump",
"V: Attack",
"Escape : Pause / Menu",
"Controller : Use the left joystick to move",
"B: Dash",
"A: Jump",
"X: Attack",
"Y: Pause / Menu",
]
y_offset = 180
line_spacing = 40
for line in instructions:
text_surf = render_text_with_outline(
line, self.text_font, (255, 255, 255), (0, 0, 0)
)
text_rect = text_surf.get_rect(
center=(self.game_resources.WIDTH // 2, y_offset)
)
surface.blit(text_surf, text_rect)
y_offset += line_spacing
self.blink_timer += 0.01
alpha = abs(math.sin(self.blink_timer * self.blink_speed)) * 255
skip_text = render_text_with_outline(
"Press any key to continue", self.text_font, (255, 220, 0), (0, 0, 0)
)
skip_text.set_alpha(int(alpha))
skip_rect = skip_text.get_rect(
center=(self.game_resources.WIDTH // 2, self.game_resources.HEIGHT - 100)
)
surface.blit(skip_text, skip_rect)
def handle_event(self, event):
if (
event.type == pygame.KEYDOWN
or event.type == pygame.MOUSEBUTTONDOWN
or event.type == pygame.JOYBUTTONDOWN
):
return "menu"
return None

View File

@@ -23,6 +23,7 @@ from src.Database.CheckpointDB import CheckpointDB
from src.Map.Editor.LevelEditor import LevelEditor
from src.Menu.LevelEditorSelectionMenu import LevelEditorSelectionMenu
from src.Map.Speedrun.SpeedrunTimer import SpeedrunTimer
from src.Menu.InstructionsScreen import InstructionsScreen
def initialize_game_resources():
@@ -63,8 +64,9 @@ def initialize_game_resources():
projectiles = pygame.sprite.Group()
# Game states initialization
current_state = 0 # MENU
current_state = 5 # INSTRUCTIONS
current_menu = "main"
instructions_screen = InstructionsScreen(game_resources)
main_menu = Menu(game_resources)
level_select_menu = None
editor_select_menu = None
@@ -99,6 +101,7 @@ def initialize_game_resources():
joysticks,
editor_select_menu,
leaderboard_db,
instructions_screen,
)
@@ -134,6 +137,16 @@ def handle_system_events(
)
# Update window dimensions
ORIGINAL_WIDTH, ORIGINAL_HEIGHT = event.w, event.h
elif event.type == pygame.JOYBUTTONDOWN:
try:
if event.button == 4: # Triangle sur la plupart des manettes
if current_state in [1, 2]: # PLAYING, INFINITE
current_state = 0 # MENU
else:
pygame.quit()
sys.exit()
except Exception as e:
print(f"Error while handling joystick button: {e}")
return current_state, fullscreen, displaysurface, ORIGINAL_WIDTH, ORIGINAL_HEIGHT
@@ -645,7 +658,7 @@ def handle_death_screen(
def handler():
"""Main function that handles the game flow"""
# Game state constants
MENU, PLAYING, INFINITE, LEADERBOARD, DEATH_SCREEN = 0, 1, 2, 3, 4
MENU, PLAYING, INFINITE, LEADERBOARD, DEATH_SCREEN, INSTRUCTIONS = 0, 1, 2, 3, 4, 5
previous_state = None
# Initialize game resources and states
@@ -667,6 +680,7 @@ def handler():
joysticks,
editor_select_menu,
leaderboard_db,
instructions_screen,
) = initialize_game_resources()
# Initialize editor variables
@@ -687,6 +701,7 @@ def handler():
print(f"Error while getting events: {e}")
pygame.joystick.quit()
pygame.joystick.init()
events = []
continue
# Process events
@@ -766,6 +781,13 @@ def handler():
game_resources
)
elif current_state == INSTRUCTIONS:
for event in events:
result = instructions_screen.handle_event(event)
if result == "menu":
current_state = MENU
instructions_screen.draw(displaysurface)
# Process general game events (player death, projectiles, etc.)
if event.type == USEREVENT:
current_state, death_timer, checkpoint_data, projectiles = (
@@ -929,6 +951,13 @@ def handler():
elif death_result["action"] == "return_to_menu":
current_state = death_result["current_state"]
elif current_state == INSTRUCTIONS:
for event in events:
result = instructions_screen.handle_event(event)
if result == "menu":
current_state = MENU
instructions_screen.draw(displaysurface)
# Update display
pygame.display.update()
game_resources.FramePerSec.tick(game_resources.FPS)