Merge remote-tracking branch 'origin/main' into SoundManager

# Conflicts:
#	src/engine/engine.py
#	src/engine/renderer.py
This commit is contained in:
Yannis 2024-01-08 16:05:06 +01:00
commit c926a75a50
11 changed files with 513 additions and 36 deletions

5
assets/dialogs.json Normal file
View file

@ -0,0 +1,5 @@
{
"test": ["test1111", "test2", "test très long permettant de tester le retour à la ligne dans le renderer du jeu dans la fonction qui rend les dialogues et il faut éviter d'en faire des si long car ça pourrait dépacer en bas de l'écran ! Bonne journée !"],
"test2": ["salut", "aurevoir"],
"test3": ["test très long permettant de tester le retour à la ligne dans le renderer du jeu dans la fonction qui rend les dialogues et il faut éviter d'en faire des si long car ça pourrait dépacer en bas de l'écran ! Bonne journée !"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,13 +1,87 @@
import json import json
from types import FunctionType
from src.engine.event_handler import EventHandler
class DialogsManager: class DialogsManager:
"""Classe qui gère la lecture des dialogues.""" """Classe qui gère la lecture des dialogues."""
def __init__(self): def __init__(self, event_handler: EventHandler):
self.current_dialog = [] self.event_handler = event_handler
self.current_dialogs = []
self.current_dialog_id = -1
self.dialogs = {} self.dialogs = {}
self.reading_dialog = False
self.current_dialogue_letter_id = 0
self.writing_dialog = False
self.LETTER_WRITING_DELAY = 0.02
self.letter_timer = 0
self.dialogue_finished_callback = None
def next_signal(self):
"""Fonction exécutée lorsque l'utilisateur demande de passer au prochain dialogue. Si un dialogue est en
train d'être écrit, il écrit tout d'un coup."""
if self.reading_dialog:
if self.writing_dialog:
self.current_dialogue_letter_id = len(self.current_dialogs[self.current_dialog_id])
else:
self.next_dialog()
print("next")
def next_dialog(self):
"""Passe au dialogue suivant. Appelle le callback si le dialogue est fini."""
self.current_dialog_id += 1
self.current_dialogue_letter_id = 0
self.writing_dialog = True
if self.current_dialog_id >= len(self.current_dialogs): # Le dialogue est fini.
self.current_dialogs = []
self.current_dialog_id = -1
self.writing_dialog = False
self.reading_dialog = False
self.event_handler.remove_button_area("next_dialog")
if self.dialogue_finished_callback is not None:
self.dialogue_finished_callback()
def start_dialog(self, name: str, dialogue_finished_callback: FunctionType | classmethod | staticmethod = None):
"""Lance le dialogue au nom donné."""
# Si un dialogue n'est pas déja lancé, on lance le dialogue au nom donné
if not self.reading_dialog:
self.event_handler.register_button_area((0, 0, 1, 1), self.next_signal, "next_dialog", 2)
self.current_dialogs = self.dialogs[name]
self.current_dialog_id = 0
self.current_dialogue_letter_id = 0
self.reading_dialog = True
self.dialogue_finished_callback = dialogue_finished_callback
def get_current_dialog_sentence(self, progressive=True) -> str:
"""Renvoie la phrase actuelle du dialogue."""
if progressive:
return self.current_dialogs[self.current_dialog_id][:self.current_dialogue_letter_id]
else:
return self.current_dialogs[self.current_dialog_id]
def load_dialogs(self, file_path: str): def load_dialogs(self, file_path: str):
"""Charge les dialogues du jeu grave au fichier json donné.""" """Charge les dialogues du jeu grave au fichier json donné."""
with open(file_path, "r") as file: with open(file_path, "r", encoding="utf-8") as file:
self.dialogs = json.loads(file.read()) self.dialogs = json.loads(file.read())
def update(self, delta: float):
"""Met à jour e gestionnaire de dialogues."""
if self.reading_dialog:
self.letter_timer -= delta
if self.letter_timer <= 0:
self.letter_timer = self.LETTER_WRITING_DELAY
self.current_dialogue_letter_id += 1
if self.current_dialogue_letter_id > len(self.current_dialogs[self.current_dialog_id]):
self.current_dialogue_letter_id -= 1
self.writing_dialog = False

View file

@ -1,8 +1,11 @@
from src.engine.boss_fight_manager import BossFightManager from src.engine.boss_fight_manager import BossFightManager
from src.engine.camera import Camera from src.engine.camera import Camera
from src.engine.dialogs_manager import DialogsManager
from src.engine.entity_manager import EntityManager from src.engine.entity_manager import EntityManager
from src.engine.event_handler import EventHandler from src.engine.event_handler import EventHandler
from src.engine.event_sheduler import EventSheduler
from src.engine.map_manager import MapManager from src.engine.map_manager import MapManager
from src.engine.menu_manager import MenuManager
from src.engine.renderer import Renderer from src.engine.renderer import Renderer
from src.engine.enums import GameState from src.engine.enums import GameState
from src.engine.sound_manager import SoundManager from src.engine.sound_manager import SoundManager
@ -32,6 +35,9 @@ class Engine:
self.camera = Camera() self.camera = Camera()
self.entity_manager = EntityManager(self.map_manager) self.entity_manager = EntityManager(self.map_manager)
self.boss_fight_manager = BossFightManager(self) self.boss_fight_manager = BossFightManager(self)
self.event_sheduler = EventSheduler(self)
self.dialogs_manager = DialogsManager(self.event_handler)
self.menu_manager = MenuManager(self)
self.sound_manager = SoundManager(60) self.sound_manager = SoundManager(60)
def loop(self): def loop(self):
@ -49,6 +55,8 @@ class Engine:
self.entity_manager.update(0.016666666) self.entity_manager.update(0.016666666)
self.renderer.update(0.016666666) self.renderer.update(0.016666666)
self.event_handler.update() self.event_handler.update()
self.event_sheduler.update()
self.dialogs_manager.update(0.016666666)
self.sound_manager.update(1/60) self.sound_manager.update(1/60)
def stop(self): def stop(self):

View file

@ -1,6 +1,7 @@
import math import math
from types import FunctionType
from pygame import event from pygame import event, display
from pygame.locals import * from pygame.locals import *
import src.engine.engine as engine import src.engine.engine as engine
@ -8,9 +9,52 @@ import src.engine.engine as engine
class EventHandler: class EventHandler:
"""Classe utilisée pour traiter les pygame.event.get() et gérer les interactions avec le reste du programme.""" """Classe utilisée pour traiter les pygame.event.get() et gérer les interactions avec le reste du programme."""
def __init__(self, core: 'engine.Engine'): def __init__(self, core: 'engine.Engine'):
self.engine = core self.engine = core
self.key_pressed = [] self.key_pressed = []
self.buttons_area = []
self.hovered_area = []
@staticmethod
def get_click_collision(rect: tuple[float | int, float | int, float | int, float | int], point: tuple[int, int],
is_window_relative: int):
"""Vérifie si le point et le rectangle donné sont en collision."""
window_size = display.get_window_size()
if is_window_relative == 0:
return (rect[0]*window_size[0] < point[0] < rect[0]*window_size[0] + rect[2]*window_size[0]
and rect[1]*window_size[0] < point[1] < rect[1]*window_size[0] + rect[3]*window_size[0])
elif is_window_relative == 1:
return (rect[0]*window_size[1] < point[0] < rect[0]*window_size[1] + rect[2]*window_size[1] and
rect[1]*window_size[1] < point[1] < rect[1]*window_size[1] + rect[3]*window_size[1])
elif is_window_relative == 2:
return (rect[0]*window_size[0] < point[0] < rect[0]*window_size[0] + rect[2]*window_size[0] and
rect[1]*window_size[1] < point[1] < rect[1]*window_size[1] + rect[3]*window_size[1])
return rect[0] < point[0] < rect[0] + rect[2] and rect[1] < point[1] < rect[1] + rect[3]
def register_button_area(self, rect: tuple[float | int, float | int, float | int, float | int],
callback: FunctionType | classmethod | staticmethod, name: str,
is_window_relative: int = -1,
hover_callback: FunctionType | classmethod | staticmethod = None):
"""Enregistre une zone comme bouton. La fonction donnée sera donc executé lorsque la zone sur la fenêtre
sera cliqué. is_window_relative doit être 0 pour que le rect soit multipliée par la largeur de la fenêtre et 1
pour qu'elle soit multipliée par la hauteur"""
self.buttons_area.append((rect, callback, is_window_relative, name, hover_callback))
def remove_button_area(self, name: str):
"""Supprime les boutons aux noms donnés."""
# On itère dans toute la liste et on ne garde que les éléments ne portant pas le nom cherché
cleared_list = []
for area in self.buttons_area:
if area[3] != name:
cleared_list.append(area)
self.buttons_area = cleared_list
def update(self): def update(self):
"""Vérifie s'il y a de nouvelles interactions et les traites.""" """Vérifie s'il y a de nouvelles interactions et les traites."""
@ -22,7 +66,25 @@ class EventHandler:
elif e.type == KEYDOWN: elif e.type == KEYDOWN:
self.key_pressed.append(e.key) self.key_pressed.append(e.key)
elif e.type == KEYUP: elif e.type == KEYUP:
self.key_pressed.remove(e.key) if e.key in self.key_pressed:
self.key_pressed.remove(e.key)
elif e.type == MOUSEBUTTONDOWN:
# Vérifie si une des zones enregistrées comme bouton n'a pas été cliqué
if e.button == 1:
for area in self.buttons_area:
if self.get_click_collision(area[0], e.pos, area[2]):
area[1]()
elif e.type == MOUSEMOTION:
for area in self.buttons_area:
if area[4] is not None:
if self.get_click_collision(area[0], e.pos, area[2]):
if area not in self.hovered_area:
area[4](True)
self.hovered_area.append(area)
else:
if area in self.hovered_area:
area[4](False)
self.hovered_area.remove(area)
if self.engine.entity_manager.player_entity_name: if self.engine.entity_manager.player_entity_name:
if K_RIGHT in self.key_pressed: if K_RIGHT in self.key_pressed:
@ -34,13 +96,17 @@ class EventHandler:
if K_DOWN in self.key_pressed: if K_DOWN in self.key_pressed:
self.engine.entity_manager.move_player_controls(0, 1) self.engine.entity_manager.move_player_controls(0, 1)
if K_SPACE in self.key_pressed:
self.engine.dialogs_manager.next_signal()
self.key_pressed.remove(K_SPACE)
if self.engine.DEBUG_MODE: if self.engine.DEBUG_MODE:
if K_l in self.key_pressed: if K_l in self.key_pressed:
self.engine.entity_manager.get_by_name("player").take_damages(1) self.engine.entity_manager.get_by_name("player").take_damages(1)
if K_p in self.key_pressed: if K_p in self.key_pressed:
self.engine.renderer.emit_particles(math.floor(self.engine.entity_manager.get_by_name("player").x), self.engine.renderer.emit_particles(math.floor(self.engine.entity_manager.get_by_name("player").x),
math.floor(self.engine.entity_manager.get_by_name("player").y), math.floor(self.engine.entity_manager.get_by_name("player").y),
16, 16, 16, 1, 8, 0, 1, 0.2, 1., (0, 200, 200)) 16, 16, 16, 1, 8, 0, 1, 0.2, 1., (0, 200, 200))
if K_o in self.key_pressed: if K_o in self.key_pressed:
print(f"Player pos: X = {self.engine.entity_manager.get_by_name('player').x} " print(f"Player pos: X = {self.engine.entity_manager.get_by_name('player').x} "
f"Y = {self.engine.entity_manager.get_by_name('player').y}") f"Y = {self.engine.entity_manager.get_by_name('player').y}")
@ -49,4 +115,3 @@ class EventHandler:
self.engine.camera.target_zoom *= 1.01 self.engine.camera.target_zoom *= 1.01
if K_c in self.key_pressed: if K_c in self.key_pressed:
self.engine.camera.target_zoom *= 0.99 self.engine.camera.target_zoom *= 0.99

View file

@ -1,14 +1,41 @@
from types import FunctionType from types import FunctionType
import src.engine.engine
from src.engine.entity import Entity
class EventSheduler: class EventSheduler:
"""Gère le lancement d'évenements avec des conditions.""" """Gère le lancement d'évenements avec des conditions."""
def __init__(self): def __init__(self, engine: 'src.engine.engine.Engine'):
self.area_callbacks = [] self.area_callbacks = []
self.engine = engine
def register_area(self, area_rect: tuple[int, int, int, int], callback: FunctionType | classmethod | staticmethod): def register_area(self, area_rect: tuple[int, int, int, int], callback: FunctionType | classmethod | staticmethod,
self.area_callbacks.append((area_rect, callback)) linked_entities_name: list[Entity], single_use: bool = True, no_spam: bool = False):
self.area_callbacks.append((area_rect, callback, linked_entities_name, single_use, no_spam, []))
# La liste vide en dernier argument correspond aux entités actuellement dans la zone
@staticmethod
def get_collisions_with_entity(rect: tuple[int, int, int, int], entity: 'Entity'):
"""Retourne True si l'entité donnée touche le rectangle donné."""
return (rect[0] <= entity.x+entity.collision_rect[2] and
rect[0] + rect[2] >= entity.x+entity.collision_rect[0] and
rect[1] + rect[3] >= entity.y+entity.collision_rect[1] and
rect[1] <= entity.y+entity.collision_rect[3])
def update(self): def update(self):
for area in self.area_callbacks: """Met à jour l'event sheduler et execute les actions si les conditions à son execution sont respéctées."""
area_rect = area[0]
# On itère dans la liste des zones de détection
for area in self.area_callbacks.copy():
# On itère dans toutes les entités enregistrées
for entity in area[2]:
entity_in_area = self.get_collisions_with_entity(area[0], self.engine.entity_manager.get_by_name(entity))
if entity_in_area and not (area[4] and entity in area[5]):
area[1](entity)
if area[3]:
self.area_callbacks.remove(area)
if area[4]:
area[5].append(entity)
elif (not entity_in_area) and (area[4] and entity in area[5]):
area[5].remove(entity)

View file

@ -0,0 +1,95 @@
from types import FunctionType
import pygame
import src.engine.engine
class Widget:
"""Classe parente des widgets de menu."""
def __init__(self, x, y, is_window_relative):
self.x = x
self.y = y
self.is_window_relative = is_window_relative
class Label(Widget):
"""Un widget de texte."""
def __init__(self, x: int | float, y: int | float, text: str, size: int | float, color: tuple[int, int, int],
centered: bool = False, is_window_relative: int = -1):
super().__init__(x, y, is_window_relative)
self.text = text
self.size = size
self.centered = centered
self.color = color
class Button(Widget):
"""Un widget de bouton."""
def __init__(self, x: int | float, y: int | float, text: str, size: int | float, color: tuple[int, int, int],
callback: FunctionType | classmethod | staticmethod, base_image: pygame.Surface,
hover_image: pygame.Surface, centered: bool = False, is_window_relative: int = -1,
area_name: str = "menu_button"):
super().__init__(x, y, is_window_relative)
self.text = text
self.size = size
self.color = color
self.callback = callback
self.base_image = base_image
self.hover_image = hover_image
self.centered = centered
self.area_name = area_name
self.hovered = False
def set_hover_state(self, state: bool):
"""Modifie la valeur du hover."""
self.hovered = state
class Menu:
"""Un menu contenant des widgets."""
def __init__(self):
self.widgets: list[Widget] = []
def add_widget(self, widget: Widget):
"""Ajoute le widget donné au menu."""
self.widgets.append(widget)
class MenuManager:
"""Classe qui gère les menus."""
def __init__(self, engine: 'src.engine.engine.Engine'):
self.menus = {}
self.active_menu: Menu | None = None
self.engine = engine
def register_menu(self, menu: Menu, name: str):
"""Ajoute le menu donné au manager de menu avec le nom donné."""
self.menus[name] = menu
def show(self, name: str):
"""Affiche le menu au nom donné."""
self.active_menu = self.menus[name]
# On itère dans tous les bouttons pour leur ajouter une interaction
for btn in self.active_menu.widgets:
if isinstance(btn, Button):
width = btn.base_image.get_width() / self.engine.renderer.window_size[0]
height = btn.base_image.get_height() / self.engine.renderer.window_size[1]
area_x = btn.x
area_y = btn.y
if btn.centered:
area_x -= width / 2
area_y -= height / 2
self.engine.event_handler.register_button_area((area_x, area_y, width, height), btn.callback,
btn.area_name,
btn.is_window_relative, btn.set_hover_state)
def hide(self):
"""Affiche le menu actuelement à l'écran."""
# On itère dans tous les bouttons pour retirer l'interaction
for btn in self.active_menu.widgets:
if isinstance(btn, Button):
self.engine.event_handler.remove_button_area(btn.area_name)
self.active_menu = None

View file

@ -7,6 +7,7 @@ from pygame.locals import RESIZABLE, SRCALPHA, FULLSCREEN
import src.engine.engine as engine import src.engine.engine as engine
from src.engine.animation import Anim from src.engine.animation import Anim
from src.engine.enums import GameState from src.engine.enums import GameState
from src.engine.menu_manager import Label, Button
class Renderer: class Renderer:
@ -15,7 +16,8 @@ class Renderer:
def __init__(self, core: 'engine.Engine'): def __init__(self, core: 'engine.Engine'):
self.engine = core self.engine = core
self.window_type = RESIZABLE self.window_type = RESIZABLE
self.window_size = (display.Info().current_w, display.Info().current_h) if self.window_type == FULLSCREEN else (600, 600) self.window_size = (display.Info().current_w, display.Info().current_h) if self.window_type == FULLSCREEN else (
600, 600)
self.window = display.set_mode(self.window_size, self.window_type) self.window = display.set_mode(self.window_size, self.window_type)
self.tiles = [] self.tiles = []
self.tile_size = 0 self.tile_size = 0
@ -26,8 +28,8 @@ class Renderer:
self.boss_fight_player_animations: dict[str: Anim] = {} self.boss_fight_player_animations: dict[str: Anim] = {}
self.boss_fight_GUI_container = None self.boss_fight_GUI_container = None
# Variables utilisées par le menu principal # Boite de dialogue
self.main_menu_assets: dict[str: Anim] = {} self.dialogs_box = None
# Ombres d'entités # Ombres d'entités
self.shadows = {} self.shadows = {}
@ -54,8 +56,8 @@ class Renderer:
part_speed_y = - part_speed_y part_speed_y = - part_speed_y
# On choisit sa position dans le rectangle # On choisit sa position dans le rectangle
part_x = random.randint(x-w, x+w-part_size) part_x = random.randint(x - w, x + w - part_size)
part_y = random.randint(y-h, y+h-part_size) part_y = random.randint(y - h, y + h - part_size)
# On choisit la durée de vie # On choisit la durée de vie
part_life_time = random.uniform(min_life_time, max_life_time) part_life_time = random.uniform(min_life_time, max_life_time)
@ -64,9 +66,6 @@ class Renderer:
# Le 0 correspond au temps de vie depuis la création de la particule # Le 0 correspond au temps de vie depuis la création de la particule
self.particles.append([part_x, part_y, part_size, part_speed_x, part_speed_y, 0., part_life_time, color]) self.particles.append([part_x, part_y, part_size, part_speed_x, part_speed_y, 0., part_life_time, color])
def load_main_menu_assets(self, path: str):
"""Charge les assets du menu principal depuis le dossier donné."""
def load_tile_set(self, file_path: str, tile_size: int): def load_tile_set(self, file_path: str, tile_size: int):
"""Charge le jeu de tuiles en utilisant le fichier donné et la taille donnée.""" """Charge le jeu de tuiles en utilisant le fichier donné et la taille donnée."""
tile_set = image.load(file_path).convert_alpha() tile_set = image.load(file_path).convert_alpha()
@ -98,6 +97,7 @@ class Renderer:
self.render_entities(rendered_surface, gui_surface, delta) self.render_entities(rendered_surface, gui_surface, delta)
self.render_particles(rendered_surface, delta) self.render_particles(rendered_surface, delta)
self.render_layer(2, rendered_surface) self.render_layer(2, rendered_surface)
self.render_debug_area(rendered_surface)
# Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale # Enfin, on redimensionne notre surface et on la colle sur la fenêtre principale
self.window.blit( self.window.blit(
@ -112,7 +112,10 @@ class Renderer:
self.render_boss_fight_scene(delta) self.render_boss_fight_scene(delta)
self.render_boss_fight_gui() self.render_boss_fight_gui()
# Conteur de données (FPS, Coords, Volume) en mode DEBUG # Rend les menus
self.render_menus()
# Conteur de FPS en mode DEBUG
if self.engine.DEBUG_MODE: if self.engine.DEBUG_MODE:
self.window.blit(font.SysFont("Arial", 20).render(f"FPS: {round(self.engine.clock.get_fps())}", True, (255, 0, 0)), self.window.blit(font.SysFont("Arial", 20).render(f"FPS: {round(self.engine.clock.get_fps())}", True, (255, 0, 0)),
(0, 0)) (0, 0))
@ -126,9 +129,181 @@ class Renderer:
self.window.blit(font.SysFont("Arial", 20).render(f"Track: {self.engine.sound_manager.music_current_song}", self.window.blit(font.SysFont("Arial", 20).render(f"Track: {self.engine.sound_manager.music_current_song}",
True, (255, 0, 0)), (0, 120)) True, (255, 0, 0)), (0, 120))
# On rend maintenant toutes les zones de détection de la fenêtre
for area in self.engine.event_handler.buttons_area:
window_size = display.get_window_size()
if area[2] == 0:
draw.rect(self.window, (255, 255, 0),
(area[0][0] * window_size[0], area[0][1] * window_size[0],
area[0][2] * window_size[0], area[0][3] * window_size[0]), width=1)
elif area[2] == 1:
draw.rect(self.window, (255, 255, 0),
(area[0][0] * window_size[1], area[0][1] * window_size[1],
area[0][2] * window_size[1], area[0][3] * window_size[1]), width=1)
elif area[2] == 2:
draw.rect(self.window, (255, 255, 0),
(area[0][0] * window_size[0], area[0][1] * window_size[1],
area[0][2] * window_size[0], area[0][3] * window_size[1]), width=1)
else:
draw.rect(self.window, (255, 255, 0),
area[0], width=1)
# Rendu présent dans tous les types de jeu
self.render_dialogs_box()
# Apres avoir tout rendu, on met à jour l'écran # Apres avoir tout rendu, on met à jour l'écran
display.update() display.update()
def render_menus(self):
"""Rend le menu enregistré comme visible."""
window_size = display.get_window_size()
# Si un menu est affiché, on itère dans tous ses widgets
if self.engine.menu_manager.active_menu is not None:
for widget in self.engine.menu_manager.active_menu.widgets:
# On multiplie les coordonnées par la taille de la fenetre si besoin
if widget.is_window_relative == 0:
x = widget.x * window_size[0]
y = widget.y * window_size[0]
elif widget.is_window_relative == 1:
x = widget.x * window_size[1]
y = widget.y * window_size[1]
elif widget.is_window_relative == 2:
x = widget.x * window_size[0]
y = widget.y * window_size[1]
else:
x = widget.x
y = widget.y
# On vérifie quel est le widget
if isinstance(widget, Label):
# On multiplie la taille du texte si besoin
if widget.is_window_relative == 0:
size = widget.size*window_size[0]
elif widget.is_window_relative == 1:
size = widget.size*window_size[1]
elif widget.is_window_relative == 2:
size = widget.size*min(window_size[0], window_size[1])
else:
size = widget.size
text_font = font.SysFont("Arial", round(size))
rendered_text = text_font.render(widget.text, True, widget.color)
if widget.centered:
self.window.blit(rendered_text, (x-rendered_text.get_width()//2,
y-rendered_text.get_height()//2))
else:
self.window.blit(rendered_text, (x, y))
elif isinstance(widget, Button):
# On multiplie la taille du texte si besoin
if widget.is_window_relative == 0:
size = widget.size*window_size[0]
elif widget.is_window_relative == 1:
size = widget.size*window_size[1]
elif widget.is_window_relative == 2:
size = widget.size*min(window_size[0], window_size[1])
else:
size = widget.size
text_font = font.SysFont("Arial", round(size))
rendered_text = text_font.render(widget.text, True, widget.color)
if widget.hovered:
btn_image = widget.hover_image
else:
btn_image = widget.base_image
if widget.is_window_relative == 0:
btn_image = transform.scale(btn_image, (btn_image.get_width()*window_size[0]/self.window_size[0],
btn_image.get_height()*window_size[0]/self.window_size[0]))
elif widget.is_window_relative == 1:
btn_image = transform.scale(btn_image, (btn_image.get_width()*window_size[1]/self.window_size[1],
btn_image.get_height()*window_size[1]/self.window_size[1]))
elif widget.is_window_relative == 2:
btn_image = transform.scale(btn_image, (btn_image.get_width()*window_size[0]/self.window_size[0],
btn_image.get_height()*window_size[1]/self.window_size[1]))
# On affiche l'image du boutton
if widget.centered:
self.window.blit(btn_image, (x-btn_image.get_width()//2,
y-btn_image.get_height()//2))
self.window.blit(rendered_text, (x-rendered_text.get_width()//2,
y-rendered_text.get_height()//2))
else:
self.window.blit(btn_image, (x, y))
self.window.blit(rendered_text, (x, y))
def render_dialogs_box(self):
"""Rend la boite de dialogue lorsqu'un dialogue est lancé."""
# Rend le conteneur des dialogues
if self.engine.dialogs_manager.reading_dialog:
resized_box = transform.scale(self.dialogs_box,
(display.get_window_size()[0],
self.dialogs_box.get_height() / self.dialogs_box.get_width() *
display.get_window_size()[0]))
self.window.blit(resized_box, (0, display.get_window_size()[1] - resized_box.get_height()))
# Rend le texte
# On récupère le texte
sentence = self.engine.dialogs_manager.get_current_dialog_sentence()
# On crée la font qui permettra de faire le rendu du texte après
text_font = font.SysFont("Arial", display.get_window_size()[0]//30)
# On calcule la taille du décalage puis on calcule la largeur maximale que peut faire une ligne
x_border = display.get_window_size()[0]/30
max_width = display.get_window_size()[0]-2*x_border
# On passe le texte dans un algorithme qui coupe le texte entre les espaces pour empecher de dépacer la
# taille maximale de la ligne
lines = []
current_line = ""
for i in sentence:
current_line += i
# Si on déplace de la ligne, on ajoute la ligne jusqu'au dernier mot
if text_font.size(current_line)[0] > max_width:
lines.append(current_line[:current_line.rfind(" ")])
current_line = current_line[current_line.rfind(" "):]
# Si la ligne est incomplète, on ajoute la ligne
lines.append(current_line)
# On itère dans les lignes avec un enumerate pour avoir sont index
for i in enumerate(lines):
# On récupère le texte et s'il commence par un espace, on le retire
text = i[1]
if len(text) > 0 and text[0] == " ":
text = text[1:]
# On rend la ligne au bon endroit sur l'écran
rendered_text = text_font.render(text, True, (0, 0, 0))
self.window.blit(rendered_text,
(x_border,
display.get_window_size()[1] - resized_box.get_height() +
display.get_window_size()[0]/30 +
(text_font.get_height()+display.get_window_size()[0]/200)*i[0]))
def render_debug_area(self, rendered_surface: surface.Surface):
"""Rend les zones de collisions et de détections quand le mode DEBUG est activé."""
# On calcule le décalage pour centrer la caméra
x_middle_offset = display.get_window_size()[0] / 2 / self.engine.camera.zoom
y_middle_offset = display.get_window_size()[1] / 2 / self.engine.camera.zoom
# On itère et on rend toutes les zones de détection
for area in self.engine.event_sheduler.area_callbacks:
area_rect = area[0]
draw.rect(rendered_surface, (200, 100, 0),
(math.floor(x_middle_offset + area_rect[0] - self.engine.camera.x),
math.floor(y_middle_offset + area_rect[1] - self.engine.camera.y),
math.floor(area_rect[2]), math.floor(area_rect[3])), width=1)
def register_shadow(self, file_path: str, name: str): def register_shadow(self, file_path: str, name: str):
"""Enregistre une image d'ombre utilisée pour le rendu des entités.""" """Enregistre une image d'ombre utilisée pour le rendu des entités."""
shadow = image.load(file_path).convert_alpha() shadow = image.load(file_path).convert_alpha()
@ -173,8 +348,8 @@ class Renderer:
frame = transform.scale(frame, (display.get_window_size()[0] / 5, display.get_window_size()[0] / 5)) frame = transform.scale(frame, (display.get_window_size()[0] / 5, display.get_window_size()[0] / 5))
# On colle le boss à droite de la fenêtre # On colle le boss à droite de la fenêtre
self.window.blit(frame, (display.get_window_size()[0]-frame.get_width()-display.get_window_size()[0]/20, self.window.blit(frame, (display.get_window_size()[0] - frame.get_width() - display.get_window_size()[0] / 20,
display.get_window_size()[1]/4-frame.get_height()/2)) display.get_window_size()[1] / 4 - frame.get_height() / 2))
# On récupère l'image de l'animation du joueur # On récupère l'image de l'animation du joueur
player_animation = self.boss_fight_player_animations[self.engine.boss_fight_manager.current_player_animation] player_animation = self.boss_fight_player_animations[self.engine.boss_fight_manager.current_player_animation]
@ -184,14 +359,17 @@ class Renderer:
frame = transform.scale(frame, (display.get_window_size()[0] / 5, display.get_window_size()[0] / 5)) frame = transform.scale(frame, (display.get_window_size()[0] / 5, display.get_window_size()[0] / 5))
# On colle le joueur à gauche de la fenêtre # On colle le joueur à gauche de la fenêtre
self.window.blit(frame, (display.get_window_size()[0]/20, display.get_window_size()[1]/4-frame.get_height()/2)) self.window.blit(frame,
(display.get_window_size()[0] / 20, display.get_window_size()[1] / 4 - frame.get_height() / 2))
def render_boss_fight_gui(self): def render_boss_fight_gui(self):
"""Rend la barre d'action en bas de l'écran pendant le combat de boss.""" """Rend la barre d'action en bas de l'écran pendant le combat de boss."""
resized_container = transform.scale(self.boss_fight_GUI_container, resized_container = transform.scale(self.boss_fight_GUI_container,
(display.get_window_size()[0], self.boss_fight_GUI_container.get_height()/self.boss_fight_GUI_container.get_width()*display.get_window_size()[0])) (display.get_window_size()[0],
self.window.blit(resized_container, (0, display.get_window_size()[1]-resized_container.get_height())) self.boss_fight_GUI_container.get_height() / self.boss_fight_GUI_container.get_width() *
display.get_window_size()[0]))
self.window.blit(resized_container, (0, display.get_window_size()[1] - resized_container.get_height()))
def render_entities(self, rendered_surface: surface.Surface, gui_surface: surface.Surface, delta: float): def render_entities(self, rendered_surface: surface.Surface, gui_surface: surface.Surface, delta: float):
"""Rend toutes les entités.""" """Rend toutes les entités."""
@ -239,10 +417,11 @@ class Renderer:
cooldown_value = entity.damage_cooldown / entity.default_damage_cooldown cooldown_value = entity.damage_cooldown / entity.default_damage_cooldown
# On calcule où placer la barre de vei sur la surface des GUI # On calcule où placer la barre de vei sur la surface des GUI
life_bar_dest = (math.floor((entity.x - self.engine.camera.x + x_middle_offset) * self.engine.camera.zoom - life_bar_dest = (
life_bar_width / 2), math.floor((entity.x - self.engine.camera.x + x_middle_offset) * self.engine.camera.zoom -
math.floor((entity.y - self.engine.camera.y + y_middle_offset - frame.get_height() / 2) * life_bar_width / 2),
self.engine.camera.zoom - life_bar_height - life_bar_y_offset)) math.floor((entity.y - self.engine.camera.y + y_middle_offset - frame.get_height() / 2) *
self.engine.camera.zoom - life_bar_height - life_bar_y_offset))
# Contour de la barre de vie # Contour de la barre de vie
draw.rect(gui_surface, (20, 0, 0), (life_bar_dest[0] - life_bar_border, draw.rect(gui_surface, (20, 0, 0), (life_bar_dest[0] - life_bar_border,
@ -268,9 +447,6 @@ class Renderer:
entity.collision_rect[3] - entity.collision_rect[1]), entity.collision_rect[3] - entity.collision_rect[1]),
width=1) width=1)
def render_main_menu(self):
"""Rend le menu principal du jeu."""
def render_layer(self, layer_id: int, rendered_surface: surface.Surface): def render_layer(self, layer_id: int, rendered_surface: surface.Surface):
"""Rend la map.""" """Rend la map."""
# On calcule le nombre de tiles à mettre sur notre écran en prenant en compte le zoom # On calcule le nombre de tiles à mettre sur notre écran en prenant en compte le zoom

View file

@ -4,6 +4,7 @@ from src.custom_AI import WolfAI
from src.engine.animation import Anim from src.engine.animation import Anim
from src.engine.engine import Engine from src.engine.engine import Engine
from src.engine.enums import GameState from src.engine.enums import GameState
from src.engine.menu_manager import Menu, Label, Button
class Game(Engine): class Game(Engine):
@ -12,6 +13,7 @@ class Game(Engine):
self.map_manager.load_new("maps/map5.tmj") self.map_manager.load_new("maps/map5.tmj")
self.renderer.load_tile_set("assets/textures/tileset.png", 16) self.renderer.load_tile_set("assets/textures/tileset.png", 16)
self.dialogs_manager.load_dialogs("assets/dialogs.json")
self.create_player_entity() self.create_player_entity()
self.load_boss_fight_assets() self.load_boss_fight_assets()
@ -19,7 +21,32 @@ class Game(Engine):
self.DEBUG_MODE = True self.DEBUG_MODE = True
self.game_state = GameState.MAIN_MENU
self.event_sheduler.register_area((64, 64, 32, 32), lambda _: self.dialogs_manager.start_dialog("test"), ["player"], False, True)
self.renderer.dialogs_box = pygame.image.load("assets/textures/GUI/dialogs_box.png").convert_alpha()
self.event_handler.register_button_area((0, 0, 0.1, 0.1), lambda : print("salut"), 0)
self.setup_main_menu()
def start_game(self):
self.game_state = GameState.NORMAL self.game_state = GameState.NORMAL
self.menu_manager.hide()
def setup_main_menu(self):
"""Crée les éléments du menu principal."""
menu = Menu()
menu.add_widget(Label(0.5, 0.1, "The Forest's Secret", 0.1, (0, 0, 0), True, 0))
base_image = pygame.image.load("assets/textures/GUI/button_1.png").convert_alpha()
hover_image = pygame.image.load("assets/textures/GUI/button_2.png").convert_alpha()
menu.add_widget(Button(0.5, 0.3, "play", 0.08, (0, 0, 0), self.start_game, base_image, hover_image, True, 0))
self.menu_manager.register_menu(menu, "main")
self.menu_manager.show("main")
def create_player_entity(self): def create_player_entity(self):
"""Crée une entité joueur.""" """Crée une entité joueur."""
@ -61,7 +88,7 @@ class Game(Engine):
mob.set_default_life(5) mob.set_default_life(5)
mob.max_speed = 1. mob.max_speed = 1.
mob.x, mob.y = 160, 16 mob.x, mob.y = 1600, 16
def load_boss_fight_assets(self): def load_boss_fight_assets(self):
"""Charge les animations de combat des combats de boss.""" """Charge les animations de combat des combats de boss."""
@ -72,7 +99,7 @@ class Game(Engine):
boss_none.load_animation_from_directory("assets/textures/boss_fight/boss_sprite/test/none") boss_none.load_animation_from_directory("assets/textures/boss_fight/boss_sprite/test/none")
self.renderer.register_boss_fight_boss_animation(boss_none, "none") self.renderer.register_boss_fight_boss_animation(boss_none, "none")
self.renderer.boss_fight_GUI_container = pygame.image.load("assets/textures/boss_fight/fight_actions_GUI.png") self.renderer.boss_fight_GUI_container = pygame.image.load("assets/textures/boss_fight/fight_actions_GUI.png").convert_alpha()
game = Game() game = Game()