Compare commits

..

53 commits

Author SHA1 Message Date
Adastram 3a53f2a8ba Added title image 2024-01-16 10:05:28 +01:00
Adastram 71725cdaef Merge branch 'game_assembly' of https://git.tipragot.fr/rfg/nsi-rpg into Merge-in-game_assembly-(NO-MORRE-TIME) 2024-01-16 07:53:33 +01:00
Adastram 95e9b4af00 Format Debug info 2024-01-16 07:35:25 +01:00
Adastram 277c8c4132 Added fullscreen option 2024-01-16 07:32:24 +01:00
Adastram d0c98925e2 Fixed fps cap and latency system 2024-01-15 14:56:34 +01:00
Adastram 779ac9194b Fix du crash quand on ferme le jeu 2024-01-14 23:51:26 +01:00
Adastram 937fc88297 Ajout du controle des fps et d'un système de contrebalance de latence 2024-01-14 23:51:07 +01:00
Adastram 0f5f6cc21c Reduced move of element caused by pos reducing and expanding from time to time using format to force 3 decimals 2024-01-14 22:09:51 +01:00
Adastram 24b6090385 Updated OptionMenu based on main 2024-01-14 17:57:36 +01:00
Adastram 41e2bdb3d6 Fixed base music volume 2024-01-14 17:53:09 +01:00
Adastram 02df5fd97e Imported OST in Option Menu 2024-01-14 17:52:47 +01:00
Yannis 7ff96fe5c9 Retrait de la grande vitesse du joueur pour les tests 2024-01-13 23:33:27 +01:00
Yannis b65fb9d127 Fix du crash lors de la mort du joueur 2024-01-13 23:30:27 +01:00
Yannis 28df17050f Update du pdn tileset 2024-01-13 23:17:02 +01:00
Yannis af05762706 Retrait des modifications sur le joueur 2024-01-13 23:16:38 +01:00
Yannis e043093748 Ajout des entrées de temples 2024-01-13 23:15:08 +01:00
Yannis 28a4cf1db0 Nettoyage du code 2024-01-13 22:57:07 +01:00
Yannis 29adfda8dd Fix dans la map 2024-01-13 22:50:21 +01:00
Yannis 989587379a Fix des problèmes lors du merge 2024-01-13 22:19:42 +01:00
Yannis 323e190474 Merge remote-tracking branch 'origin/main' into game_assembly
# Conflicts:
#	src/main.py
2024-01-13 21:42:10 +01:00
Adastram 3f0e4d0aaa Added debug ost in gitignore to prevent from import again 2024-01-12 23:46:41 +01:00
Adastram b59290118e Removed wrong OST files 2024-01-12 23:45:28 +01:00
Adastram 9effb87332 Started debugging sound control 2024-01-10 11:15:34 +01:00
Adastram ec12e2dc17 Moved default settings to engine 2024-01-10 10:49:36 +01:00
Adastram 12aff5f73f Removed zoom control from key while not in debug mode 2024-01-10 10:46:07 +01:00
Adastram 0502f1d278 Added zoom control from settings_manager (debug mode still works) 2024-01-10 10:42:20 +01:00
Adastram 988500af43 Added sound volume control in engine 2024-01-10 10:31:58 +01:00
Adastram f95ff4834a Merge pull request 'MergeToOptionMenu' (#26) from MergeToOptionMenu into OptionMenu
Reviewed-on: #26
2024-01-10 09:09:44 +00:00
Adastram ef64ce324e Added SoundManager latest to start building SettingsMenu 2024-01-10 10:07:25 +01:00
Adastram e34c7b8b8a Merge branch 'OptionMenu' into MergeToOptionMenu 2024-01-10 10:06:36 +01:00
Adastram a86d89c563 Ajout du support du réglage de son dans le sound_manager 2024-01-09 23:12:28 +01:00
Adastram c625e15d34 Added global sound volume managing 2024-01-09 21:24:37 +01:00
Adastram 77b41aee66 Temp 2024-01-09 17:04:38 +01:00
Adastram 75391e5876 Synced main with sound_manager manager 2024-01-09 16:15:26 +01:00
Adastram fd7434993d Added local sound stop 2024-01-09 15:56:54 +01:00
Adastram 8b0dddea1a Added better dict tipage 2024-01-09 14:10:34 +01:00
Yannis 4a613ccfb6 Merge remote-tracking branch 'origin/game_assembly' into game_assembly 2024-01-09 12:00:57 +01:00
Yannis 8d12c791d1 Ajout des temples 2024-01-09 12:00:24 +01:00
Adastram 2146cacc48 Implemented single global sound stop 2024-01-09 11:59:23 +01:00
Adastram 5b2420dfae Added support of local sound and volume based on distance from emiter 2024-01-09 11:40:39 +01:00
Adastram 490b5c46ba Added Shuffle and Next Function fro music 2024-01-09 11:27:11 +01:00
Adastram b2a1311839 Merge branch 'game_assembly' of https://gitea.tipragot.fr/rfg/nsi-rpg into game_assembly 2024-01-09 11:20:56 +01:00
Adastram 37db2829a7 Added shuffle function for background music 2024-01-09 11:20:47 +01:00
Yannis 347febaf11 Fix du spam de la touche o 2024-01-09 10:21:27 +01:00
Adastram 0a659dcbe0 Removed Debug prints 2024-01-09 10:03:19 +01:00
Adastram 7c6031a713 FIXED SOUND MANAGER AND MAIN 2024-01-09 08:02:33 +01:00
Adastram df86633f8e Added main OST and auto_audio_start, WORKING VERSION 2024-01-09 08:01:52 +01:00
Adastram b9a81ba12e Started Gloval sound handling 2024-01-08 23:27:45 +01:00
Yannis 6219dcdb71 Réglage du joueur 2024-01-08 21:17:43 +01:00
Yannis b3663ed81b Fix collisions map 5 2024-01-08 21:03:48 +01:00
Yannis 04e51bd376 Update de la map 5 2024-01-08 20:55:52 +01:00
Yannis 75ce3b6c37 Update de la map 5 2024-01-08 19:12:26 +01:00
Adastram f454bfff6d Merge branch 'SoundManager' (Musique de fond) 2024-01-08 10:57:02 +01:00
19 changed files with 5471 additions and 199 deletions

6
.gitignore vendored
View file

@ -160,3 +160,9 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/ .idea/
# DEBUG OST
assets/OST/Hyrule Field - The Legend of Zelda Ocarina of Time.mp3
assets/OST/Lost Woods - The Legend of Zelda Ocarina of Time.mp3
assets/OST/Title Theme - The Legend of Zelda Ocarina of Time.mp3
assets/OST/Vampire Killer.mp3
assets/sounds/Vampire Killer.mp3

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

BIN
assets/textures/temples.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 210 KiB

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,12 @@ from src.engine.entity import Entity
class Camera: class Camera:
def __init__(self): def __init__(self, DEBUG_MODE: bool, zoom: float):
self.DEBUG_MODE = DEBUG_MODE
self.x = 0 self.x = 0
self.y = 0 self.y = 0
self.zoom = 1.75 self.zoom = zoom
# Décalage lors du mouvement du joueur # Décalage lors du mouvement du joueur
self.player_moving_offset = 100 self.player_moving_offset = 100
@ -19,9 +21,12 @@ class Camera:
self.followed_entity: Entity | None = None self.followed_entity: Entity | None = None
def update(self, delta: float): def update(self, delta: float, zoom: float):
"""Met à jour la caméra. Permet, par exemple, de faire le scrolling.""" """Met à jour la caméra. Permet, par exemple, de faire le scrolling."""
if not self.DEBUG_MODE:
self.zoom = zoom
# Si on suit une entité, on met à jour les coordonnées de suivi # Si on suit une entité, on met à jour les coordonnées de suivi
if self.followed_entity is not None: if self.followed_entity is not None:
self.target_x = (self.followed_entity.x + self.followed_entity.mouvements[0] * self.target_x = (self.followed_entity.x + self.followed_entity.mouvements[0] *

View file

@ -11,6 +11,7 @@ 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
from src.engine.settings_manager import SettingsManager
import pygame import pygame
@ -29,43 +30,90 @@ class Engine:
self.running = False self.running = False
# Composants du moteur de jeu # Composants du moteur de jeu
self.settings_manager = SettingsManager(60, 1.75) # DOIT ABSOLUMENT ETRE EN PREMIER (Sinon les autres composants qui nécessite les settings crash)
self.renderer = Renderer(self) self.renderer = Renderer(self)
self.event_handler = EventHandler(self) self.event_handler = EventHandler(self)
self.map_manager = MapManager() self.map_manager = MapManager()
self.camera = Camera() self.camera = Camera(self.DEBUG_MODE, self.settings_manager.get_zoom())
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.event_sheduler = EventSheduler(self)
self.dialogs_manager = DialogsManager(self) self.dialogs_manager = DialogsManager(self)
self.menu_manager = MenuManager(self) self.menu_manager = MenuManager(self)
self.sound_manager = SoundManager(60) self.sound_manager = SoundManager(self.settings_manager.get_music_master_volume(),
self.settings_manager.get_sound_global_master_volume(),
self.settings_manager.get_sound_master_volume())
self.global_latency = 0
self.last_latency = []
self.latency_precision = self.settings_manager.latency_precision
def loop(self): def loop(self):
"""Fonction à lancer au début du programme et qui va lancer les updates dans une boucle. """Fonction à lancer au début du programme et qui va lancer les updates dans une boucle.
Attend jusqu'à la fin du jeu.""" Attend jusqu'à la fin du jeu."""
self.running = True self.running = True
delta = 1. # Le delta est le temps depuis la dernière image
last_time = time.time_ns()/10E8
while self.running:
self.update(delta)
new_time = time.time_ns()/10E8 # Initialisation ddes valeurs de delta et de last_time
delta = new_time-last_time delta = 1. # Le delta est le temps depuis la dernière image
last_time = new_time last_time = time.time()
while self.running:
refresh_rate = self.settings_manager.get_refresh_rate()
if refresh_rate == -1: # Pas de limite, vers l'infini et l'au-delà !!!
self.update(delta)
new_time = time.time()
delta = new_time - last_time
last_time = new_time
else:
while time.time() < last_time + 1 / refresh_rate - self.global_latency:
pass
new_time = time.time()
delta = new_time-last_time
last_time = new_time
self.update(delta)
new_refresh_rate = self.settings_manager.get_refresh_rate()
if refresh_rate != new_refresh_rate:
refresh_rate = new_refresh_rate
self.global_latency = 0
self.last_latency = []
latency = 0
latency = delta - 1/refresh_rate
if not latency > self.global_latency * 100 or self.global_latency == 0: # Impossible que le jeu prenne autant de retard, on skip cette latence dans le calcul, l'utilisateur a surement cliquer hors de la fenêtre
if len(self.last_latency) < self.latency_precision:
self.last_latency.append(latency)
else:
self.last_latency.pop(0)
self.last_latency.append(latency)
n = 0
for i in self.last_latency:
n += i
self.global_latency = n/len(self.last_latency)
def update(self, delta: float): def update(self, delta: float):
"""Fonction qui regroupe toutes les updates des composants. Elle permet de mettre à jour le jeu quand on """Fonction qui regroupe toutes les updates des composants. Elle permet de mettre à jour le jeu quand on
l'appelle.""" l'appelle."""
self.camera.update(delta) self.camera.update(delta, self.settings_manager.get_zoom())
self.entity_manager.update(delta) self.entity_manager.update(delta)
self.renderer.update(delta) self.renderer.update(delta)
self.event_handler.update(delta) self.event_handler.update(delta)
self.event_sheduler.update() self.event_sheduler.update()
self.dialogs_manager.update(delta) self.dialogs_manager.update(delta)
self.sound_manager.update(delta) self.sound_manager.update(delta, self.settings_manager.get_music_master_volume(),
self.settings_manager.get_sound_global_master_volume(),
self.settings_manager.get_sound_master_volume())
def stop(self): def stop(self):
"""Arrête le programme.""" """Arrête le programme."""
self.running = False self.running = False
pygame.quit() pygame.quit()

View file

@ -1,5 +1,6 @@
import math import math
from src.engine.enums import EntityDeathResult
from src.engine.map_manager import MapManager from src.engine.map_manager import MapManager
from src.engine.mobs_AI import MobAI from src.engine.mobs_AI import MobAI
@ -42,6 +43,9 @@ class Entity:
self.shadow = None self.shadow = None
self.death_callback = None
self.death_result = EntityDeathResult.REMOVED
def set_default_life(self, life: int): def set_default_life(self, life: int):
"""Définit le nombre de PV de l'entité. Mettre -1 pour rendre l'entité immortelle.""" """Définit le nombre de PV de l'entité. Mettre -1 pour rendre l'entité immortelle."""
self.life_points = life self.life_points = life

View file

@ -1,4 +1,5 @@
from src.engine.entity import Entity from src.engine.entity import Entity
from src.engine.enums import EntityDeathResult
from src.engine.map_manager import MapManager from src.engine.map_manager import MapManager
@ -29,10 +30,16 @@ class EntityManager:
def update(self, delta: float): def update(self, delta: float):
"""Met à jour toutes les entités enregistrées.""" """Met à jour toutes les entités enregistrées."""
for entity_name in list(self.entities.keys()): for entity_name in list(self.entities.keys()):
entity = self.entities[entity_name] entity: Entity = self.entities[entity_name]
entity.update(delta) entity.update(delta)
if entity.life_points == 0: if entity.life_points == 0:
self.entities.pop(entity_name) if entity.death_callback is not None:
entity.death_callback()
if entity.death_result == EntityDeathResult.REMOVED:
self.entities.pop(entity_name)
elif entity.death_result == EntityDeathResult.RESET_LIFE:
entity.life_points = entity.max_life_points
if entity.brain is not None and not self.paused: if entity.brain is not None and not self.paused:
entity.brain.update(delta) entity.brain.update(delta)

View file

@ -8,3 +8,8 @@ class GameState(Enum):
BOSS_FIGHT = 2 BOSS_FIGHT = 2
MAIN_MENU = 3 MAIN_MENU = 3
# AJouter si besoin, mais à utiliser de préférence avec parsimony # AJouter si besoin, mais à utiliser de préférence avec parsimony
class EntityDeathResult(Enum):
REMOVED = 0 # The entity is removed
RESET_LIFE = 1

View file

@ -225,9 +225,9 @@ class EventHandler:
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}")
self.key_pressed.remove(K_o)
if K_x in self.key_pressed: if K_x in self.key_pressed:
self.engine.camera.target_zoom *= 1.01 self.engine.settings_manager.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.settings_manager.zoom *= 0.99

View file

@ -89,6 +89,15 @@ class Button(Widget):
"""Modifie la valeur du hover.""" """Modifie la valeur du hover."""
self.hovered = state self.hovered = state
class Image(Widget):
"""Un widget d'image."""
def __init__(self, x: int | float, y: int | float, size: int | float, image_path: str, widget_name: str,
centered: bool = False, is_window_relative: int = -1):
super().__init__(x, y, is_window_relative, widget_name)
self.size = size
self.image = pygame.image.load(image_path)
self.centered = centered
class Menu: class Menu:
"""Un menu contenant des widgets.""" """Un menu contenant des widgets."""

View file

@ -9,7 +9,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, Slider from src.engine.menu_manager import Label, Button, Slider, Image
class Renderer: class Renderer:
@ -17,11 +17,13 @@ class Renderer:
def __init__(self, core: 'engine.Engine'): def __init__(self, core: 'engine.Engine'):
self.engine = core self.engine = core
self.fullscreen_size = display.Info().current_w, display.Info().current_h
self.timer = 0 # Timer local self.timer = 0 # Timer local
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 ( self.window_size = self.fullscreen_size if self.window_type == FULLSCREEN else (
600, 600) 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
self.animations: dict[str: Anim] = {} self.animations: dict[str: Anim] = {}
@ -175,11 +177,11 @@ class Renderer:
self.window.blit(font.SysFont("Arial", 20).render(f"FPS: {round(1/delta if delta else 1)}, Game Status: {'Paused' if self.engine.entity_manager.paused else 'Playing'}", True, (255, 0, 0)), self.window.blit(font.SysFont("Arial", 20).render(f"FPS: {round(1/delta if delta else 1)}, Game Status: {'Paused' if self.engine.entity_manager.paused else 'Playing'}", True, (255, 0, 0)),
(0, 0)) (0, 0))
player = self.engine.entity_manager.get_by_name('player') player = self.engine.entity_manager.get_by_name('player')
self.window.blit(font.SysFont("Arial", 20).render(f"X: {round(player.x, 2)} Y:{round(player.y, 2)}", self.window.blit(font.SysFont("Arial", 20).render(f"X: {round(player.x, 2):.2f} Y:{round(player.y, 2):.2f}",
True, (255, 0, 0)), (0, 30)) True, (255, 0, 0)), (0, 30))
self.window.blit(font.SysFont("Arial", 20).render(f"Zoom: {round(self.engine.camera.zoom, 2)}", self.window.blit(font.SysFont("Arial", 20).render(f"Zoom: {round(self.engine.settings_manager.get_zoom(), 2)}",
True, (255, 0, 0)), (0, 60)) True, (255, 0, 0)), (0, 60))
self.window.blit(font.SysFont("Arial", 20).render(f"Volume: {self.engine.sound_manager.music_get_volume()}, Pos: {self.engine.sound_manager.music_get_current_song_pos()}s, Index: {self.engine.sound_manager.music_current_index}, Paused: {self.engine.sound_manager.music_is_paused}", self.window.blit(font.SysFont("Arial", 20).render(f"Volume: {self.engine.sound_manager.music_get_volume()}, Pos: {self.engine.sound_manager.music_get_current_song_pos():.3f}s, Index: {self.engine.sound_manager.music_current_index}, Paused: {self.engine.sound_manager.music_is_paused}",
True, (255, 0, 0)), (0, 90)) True, (255, 0, 0)), (0, 90))
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))
@ -384,6 +386,36 @@ class Renderer:
y - rail_image.get_height() // 2)) y - rail_image.get_height() // 2))
self.window.blit(slider_image, (x+widget.value*width-slider_image.get_width()//2, self.window.blit(slider_image, (x+widget.value*width-slider_image.get_width()//2,
y-slider_image.get_height()//2)) y-slider_image.get_height()//2))
elif isinstance(widget, Image):
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
image = widget.image
if widget.is_window_relative == 0:
image = transform.scale(image, (image.get_width()*window_size[0]/self.window_size[0],
image.get_height()*window_size[0]/self.window_size[0]))
elif widget.is_window_relative == 1:
image = transform.scale(image, (image.get_width()*window_size[1]/self.window_size[1],
image.get_height()*window_size[1]/self.window_size[1]))
elif widget.is_window_relative == 2:
image = transform.scale(image, (image.get_width()*window_size[0]/self.window_size[0],
image.get_height()*window_size[1]/self.window_size[1]))
# On affiche l'image
if widget.centered:
self.window.blit(image, (x-image.get_width()//2,
y-image.get_height()//2))
else:
self.window.blit(image, (x, y))
def render_dialogs_box(self): def render_dialogs_box(self):
"""Rend la boite de dialogue lorsqu'un dialogue est lancé.""" """Rend la boite de dialogue lorsqu'un dialogue est lancé."""
@ -659,4 +691,12 @@ class Renderer:
self.fadein_fade_opacity = round(fade_opacity * 255 / 100) self.fadein_fade_opacity = round(fade_opacity * 255 / 100)
self.fadein_pause = pause_world self.fadein_pause = pause_world
self.fadein_fade_callback = callback self.fadein_fade_callback = callback
self.engine.entity_manager.pause() self.engine.entity_manager.pause()
def set_display(self, window_type: FULLSCREEN | RESIZABLE, size: tuple[int, int] = None):
self.window_type = window_type
self.window_size = self.fullscreen_size if self.window_type == FULLSCREEN else (
size[0], size[1])
self.window = display.set_mode(self.window_size, self.window_type)
display.flip()

View file

@ -0,0 +1,27 @@
class SettingsManager:
def __init__(self, default_master_volume: float, default_zoom: float) -> None:
self.refresh_rate = 30
self.latency_precision = 100 # Nombre de valeurs de latence stocké (Pour faire la moyenne)
self.master_volume = default_master_volume
self.sound_master_volume = 100
self.music_master_volume = 100
self.sound_global_master_volume = 100
self.zoom = default_zoom
def get_refresh_rate(self):
return self.refresh_rate
def get_zoom(self):
return self.zoom
def get_music_master_volume(self):
return round(self.master_volume / 100 * self.music_master_volume, 3)
def get_sound_global_master_volume(self):
return round(self.master_volume / 100 * self.sound_global_master_volume, 3)
def get_sound_master_volume(self):
return round(self.master_volume / 100 * self.sound_master_volume, 3)

View file

@ -1,44 +1,113 @@
from pygame import mixer from src.engine.entity import Entity
from random import randint
from pygame import mixer, error
from math import sqrt
from time import time
class SoundManager: class SoundManager:
def __init__(self, music_base_volume: float): def __init__(self, music_master_volume: float, sound_global_master_volume: float, sound_master_volume: float):
self.__tick = 0 # Compteur de la valeur d'un tick sur 1 (Utilisé pour le comptage de tick) self.__tick = 0 # Compteur de la valeur d'un tick (Utilisé pour le comptage de tick)
self.tick = 0 # Compteur de tick
self.time = 0 # Temps local a la class (en s) self.time = 0 # Temps local a la class (en s)
self.music_master_volume = music_master_volume
self.sound_global_master_volume = sound_global_master_volume
self.sound_master_volume = sound_master_volume
self.music_playlist = [] self.music_playlist = []
self.music_current_song = "" self.music_current_song = ""
self.music_play_playlist = False self.music_play_playlist = False
self.music_current_index = 0 self.music_current_index = 0
self.music_shuffle_playlist = True
self.music_next_request = False
self.music_set_volume(100)
self.music_before_pause_pos = 0 self.music_before_pause_pos = 0
self.music_before_pause_song = "" self.music_before_pause_song = ""
self.music_is_paused = False self.music_is_paused = False
self.music_pos_delay = 0 self.music_pos_delay = 0
self.music_set_volume(music_base_volume) self.sound_currently_playing: dict[float: list[mixer.Sound, float, list[float, float], float]] = {} # Format {unique_id : [Sound, max_volume, [pos_x, pos_y], stop_at]}
self.sound_loaded: dict[str: mixer.Sound] = {}# Format : {name: mixer.Sound}
self.sound_global_currently_playing: dict[float: list[mixer.Sound, float, float]] = {} # Format {unique_id: [Sound, volume, stop_at]}
self.sound_hears_anchor = None
def update(self, delta: float): def update(self, delta: float, music_master_volume: float, sound_global_master_volume: float, sound_master_volume: float):
self.__tick += delta self.__tick += delta
self.tick = int(self.__tick / delta) self.tick = int(self.__tick / delta)
self.time = self.tick * delta self.time = self.tick * delta
self.music_master_volume = music_master_volume
self.sound_global_master_volume = sound_global_master_volume
self.sound_master_volume = sound_master_volume
if self.sound_hears_anchor: # Update la position des "Oreilles" du joueur (Ou de l'entité séléctionné comme ancre pour les oreilles)
self.sound_hears_x = self.sound_hears_anchor.x
self.sound_hears_y = self.sound_hears_anchor.y
for key in self.sound_global_currently_playing.keys(): # Son globaux
sound_container: list[mixer.Sound, float, float] = self.sound_global_currently_playing[key]
if sound_container[2] > self.time:
self.sound_global_currently_playing.pop(key)
else:
sound_container[0].set_volume(round(sound_global_master_volume / 100 * sound_container[1] / 100, 3))
for key in self.sound_currently_playing.keys(): # Son locaux
sound_container: list[mixer.Sound, float, list[float, float], float] = self.sound_currently_playing[key]
if sound_container[3] > self.time: # Timeout des sons
self.sound_currently_playing.pop(key)
else: # Gère le volume en fonction de la position
sound = sound_container[0]
max_volume = sound_container[1]
pos_x, pos_y = sound_container[2]
sound.set_volume(max(0, int((round(sound_master_volume / 100 * max_volume / 100, 3)) - sqrt((pos_x - self.sound_hears_x) ** 2 + (pos_y - self.sound_hears_y) ** 2))) / (round(sound_master_volume / 100 * max_volume / 100, 3)))
if self.music_play_playlist and not self.music_is_paused: # Musique de fond if self.music_play_playlist and not self.music_is_paused: # Musique de fond
if not mixer.music.get_busy(): try:
if not mixer.music.get_busy() or self.music_next_request:
if len(self.music_playlist) == 0: if self.music_next_request:
pass self.music_next_request = False
elif self.music_current_song == "": mixer.music.fadeout(1)
self.__music_play(self.music_playlist[0])
else: if len(self.music_playlist) == 0:
just_played_index = self.music_playlist.index(self.music_current_song) pass
if len(self.music_playlist) - 1 <= just_played_index: # Dernier son de la playlist / la playlist a rétréci entre temps elif self.music_current_song == "":
self.music_current_index = 0 self.__music_play(self.music_playlist[0])
self.__music_play(self.music_playlist[0]) # Recommence depuis le début de la playlist
else: else:
self.music_current_index = just_played_index + 1 if self.music_current_song in self.music_playlist:
self.__music_play(self.music_playlist[self.music_current_index]) # Joue la musique suivante dans la playlist just_played_index = self.music_playlist.index(self.music_current_song)
if self.music_shuffle_playlist and len(self.music_playlist) != 1:
while True:
new_index = randint(0, len(self.music_playlist) - 1)
if new_index != just_played_index:
break
self.music_current_index = new_index
self.__music_play(self.music_playlist[new_index])
elif len(self.music_playlist) - 1 <= just_played_index: # Dernier son de la playlist / la playlist a rétréci entre temps
self.music_current_index = 0
self.__music_play(self.music_playlist[0]) # Recommence depuis le début de la playlist
else:
self.music_current_index = just_played_index + 1
self.__music_play(self.music_playlist[self.music_current_index]) # Joue la musique suivante dans la playlist
else: # Song removed from playlist, no idea what was the index, starting again from start or from random index if playlist_shuffle = True
new_index = randint(0, len(self.music_playlist) - 1)
self.music_current_index = new_index
self.__music_play(self.music_playlist[new_index])
except error:
pass
def music_get_volume(self): def music_get_volume(self):
@ -46,7 +115,7 @@ class SoundManager:
def music_set_volume(self, new_volume: float): def music_set_volume(self, new_volume: float):
"""Définit le nouveau volume de la musique""" """Définit le nouveau volume de la musique"""
mixer.music.set_volume((round(new_volume / 100, 3))) mixer.music.set_volume(round(self.music_master_volume / 100 * new_volume / 100, 3))
def music_pause(self, fade_s: float, restart_tolerance: float = 33): def music_pause(self, fade_s: float, restart_tolerance: float = 33):
"""Met en pause la musique, la musique reprendra à la fin de la musique moin la tolérance (en pourcentage)""" """Met en pause la musique, la musique reprendra à la fin de la musique moin la tolérance (en pourcentage)"""
@ -61,14 +130,12 @@ class SoundManager:
self.music_before_pause_pos = 0 self.music_before_pause_pos = 0
self.music_before_pause_song = "" self.music_before_pause_song = ""
def music_get_current_song_pos(self): def music_get_current_song_pos(self):
if mixer.music.get_busy(): if mixer.music.get_busy():
return round(mixer.music.get_pos() /1000 + self.music_pos_delay, 3) return round(mixer.music.get_pos() / 1000 + self.music_pos_delay, 3)
else: else:
return round(self.music_before_pause_pos, 3) return round(self.music_before_pause_pos, 3)
def __music_play(self, song: str, fade_s: float = 0, start_at: float = 0): def __music_play(self, song: str, fade_s: float = 0, start_at: float = 0):
mixer.music.unload() mixer.music.unload()
mixer.music.load(song) mixer.music.load(song)
@ -84,7 +151,7 @@ class SoundManager:
def music_remove_from_playlist(self, song_path: str = None, index: int = None): def music_remove_from_playlist(self, song_path: str = None, index: int = None):
if song_path: if song_path:
index = self.music_playlist.index(song_path) index = self.music_playlist.index(song_path)
if index: if index != None:
self.music_playlist.pop(index) self.music_playlist.pop(index)
def music_start_playlist(self): def music_start_playlist(self):
@ -92,3 +159,65 @@ class SoundManager:
def music_stop_playlist(self): def music_stop_playlist(self):
self.music_play_playlist = False self.music_play_playlist = False
def music_playlist_set_shuffle(self, shuffle: bool):
self.music_shuffle_playlist = shuffle
def music_next(self):
self.music_next_request = True
def sound_link_hears(self, entity: Entity):
self.sound_hears_anchor = entity
def create_unique_id(self):
return time()*10e99999
def sound_load(self, file_path: str, name: str):
self.sound_loaded[name] = mixer.Sound(file_path)
def sound_play(self, name: str, max_volume: float, pos_x: float, pos_y: float):
sound = self.sound_loaded[name]
stop_at = stop_at = self.time + sound.get_length()
unique_id = self.create_unique_id()
self.sound_currently_playing[unique_id] = [sound, max_volume, [pos_x, pos_y], stop_at] # Format {unique_id : [Sound, max_volume, [pos_x, pos_y], stop_at]
return unique_id
def sound_stop(self, name: str, unique_id: float = None, all: bool = False):
if all:
for key in self.sound_currently_playing.keys():
self.sound_currently_playing.pop(key)[0].stop()
elif unique_id:
sound_container = self.sound_currently_playing.get(unique_id, None)
if sound_container:
self.sound_currently_playing.pop(unique_id)[0].stop()
else:
for key in self.sound_currently_playing.keys():
if self.sound_loaded[name] == self.sound_currently_playing[key][0]:
self.sound_currently_playing.pop(key)[0].stop()
def sound_global_play(self, name: str, volume: float):
"""Joue un son avec le même son dans tout le monde"""
sound = self.sound_loaded[name]
stop_at = self.time + sound.get_length()
unique_id = self.create_unique_id()
self.sound_global_currently_playing[unique_id] = [sound, volume, stop_at]
sound.play()
return unique_id
def sound_global_stop(self, name: str, unique_id: float = None, all: bool = False):
if all:
for key in self.sound_global_currently_playing.keys():
self.sound_global_currently_playing.pop(key)[0].stop()
elif unique_id:
sound_container = self.sound_global_currently_playing.get(unique_id, None)
if sound_container:
self.sound_global_currently_playing.pop(unique_id)[0].stop()
else:
for key in self.sound_global_currently_playing.keys():
if self.sound_loaded[name] == self.sound_global_currently_playing[key][0]:
self.sound_global_currently_playing.pop(key)[0].stop()

View file

@ -3,9 +3,9 @@ import pygame.image
from src.custom_AI import WolfAI 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, EntityDeathResult
from src.engine.menu_manager import Menu, Label, Button from src.engine.menu_manager import Menu, Label, Button, Image
from pygame.locals import RESIZABLE, FULLSCREEN
class Game(Engine): class Game(Engine):
def __init__(self): def __init__(self):
@ -29,6 +29,10 @@ class Game(Engine):
self.event_handler.register_button_area((0, 0, 0.1, 0.1), lambda : print("salut"), 0) self.event_handler.register_button_area((0, 0, 0.1, 0.1), lambda : print("salut"), 0)
self.sound_manager.music_add_to_playlist(".\\assets\\OST\\Main Title (Y'as pas de boss la donc jpp le mettre pour un fight).mp3")
self.sound_manager.music_start_playlist()
self.setup_main_menu() self.setup_main_menu()
def start_game(self): def start_game(self):
@ -39,10 +43,14 @@ class Game(Engine):
self.renderer.fadeout(1, (0, 0, 0), 100, True, self.start_game) self.renderer.fadeout(1, (0, 0, 0), 100, True, self.start_game)
self.menu_manager.hide() self.menu_manager.hide()
self.sound_manager.music_remove_from_playlist(".\\assets\\OST\\Main Title (Y'as pas de boss la donc jpp le mettre pour un fight).mp3")
self.sound_manager.music_add_to_playlist(".\\assets\\OST\\Bruit de foret pour yannis.mp3")
self.sound_manager.music_next()
def setup_main_menu(self): def setup_main_menu(self):
"""Crée les éléments du menu principal.""" """Crée les éléments du menu principal."""
menu = Menu() menu = Menu()
menu.add_widget(Label(0.5, 0.1, "The Forest's Secret", 0.1, (0, 0, 0), "game_title", True, 0)) menu.add_widget(Image(0, 0, 1, ".\\assets\\textures\\Title_Screen.png", "title_screen_image", False, 2))
btn_base_image = pygame.image.load("assets/textures/GUI/button_1.png").convert_alpha() btn_base_image = pygame.image.load("assets/textures/GUI/button_1.png").convert_alpha()
btn_hover_image = pygame.image.load("assets/textures/GUI/button_2.png").convert_alpha() btn_hover_image = pygame.image.load("assets/textures/GUI/button_2.png").convert_alpha()
@ -51,14 +59,42 @@ class Game(Engine):
slider_hover_image = pygame.image.load("assets/textures/GUI/slider_cursor_2.png").convert_alpha() slider_hover_image = pygame.image.load("assets/textures/GUI/slider_cursor_2.png").convert_alpha()
slider_rail_image = pygame.image.load("assets/textures/GUI/slider_rail_1.png").convert_alpha() slider_rail_image = pygame.image.load("assets/textures/GUI/slider_rail_1.png").convert_alpha()
menu.add_widget(Button(0.5, 0.3, "play", 0.08, (0, 0, 0), self.play_button_callback, btn_base_image, btn_hover_image, "play_button", True, 0)) menu.add_widget(Button(0.5, 0.4, "Play", 0.08, (0, 0, 0), self.play_button_callback, btn_base_image, btn_hover_image, "play_button", True, 0))
self.menu_manager.register_menu(menu, "main") self.menu_manager.register_menu(menu, "main")
self.menu_manager.show("main") self.menu_manager.show("main")
self.create_boss_temple_area()
def setup_settings_menu(self):
"""Crée les éléments du menu de paramètre"""
menu = Menu()
menu.add_widget(Label(0, 0.3, "Paramètres", 0.05, (192,192,192), True, 0))
#base_image = pygame.image.load("assets\\textures\\GUI\\setting_menu.png")
#hover_image = pygame.image.load("assets\\textures\\GUI\\setting_menu_hovered.png")
def create_boss_temple_area(self):
"""Enregistre les zones d'entrées de boss fight."""
self.event_sheduler.register_area((3104, 608, 48, 16), lambda _: print("temple 1"), ["player"], True)
self.event_sheduler.register_area((4544, 592, 48, 16), lambda _: print("temple 2"), ["player"], True)
self.event_sheduler.register_area((5664, 688, 32, 16), lambda _: print("temple 3"), ["player"], True)
self.event_sheduler.register_area((6720, 720, 16, 32), lambda _: print("temple 4"), ["player"], True)
self.create_boss_temple_area()
def create_boss_temple_area(self):
"""Enregistre les zones d'entrées de boss fight."""
self.event_sheduler.register_area((3104, 608, 48, 16), lambda _: print("temple 1"), ["player"], True)
self.event_sheduler.register_area((4544, 592, 48, 16), lambda _: print("temple 2"), ["player"], True)
self.event_sheduler.register_area((5664, 688, 32, 16), lambda _: print("temple 3"), ["player"], True)
self.event_sheduler.register_area((6720, 720, 16, 32), lambda _: print("temple 4"), ["player"], True)
def create_player_entity(self): def create_player_entity(self):
"""Crée une entité joueur.""" """Crée une entité joueur."""
# On crée les animations
anim = Anim(0.5) anim = Anim(0.5)
anim.load_animation_from_directory("assets/textures/entities/player/none") anim.load_animation_from_directory("assets/textures/entities/player/none")
self.renderer.register_animation(anim, "player_none") self.renderer.register_animation(anim, "player_none")
@ -67,18 +103,33 @@ class Game(Engine):
anim.load_animation_from_directory("assets/textures/entities/player/walking") anim.load_animation_from_directory("assets/textures/entities/player/walking")
self.renderer.register_animation(anim, "player_walking") self.renderer.register_animation(anim, "player_walking")
# On crée l'entité
player = self.entity_manager.register_entity("player") player = self.entity_manager.register_entity("player")
player.link_animation("player_none") player.link_animation("player_none")
player.collision_rect = [-6, -7, 6, 16] player.collision_rect = [-6, -7, 6, 16]
player.death_result = EntityDeathResult.RESET_LIFE
player.set_default_life(15) player.death_callback = self.create_player_entity
player.max_speed = 64.0
self.entity_manager.set_player_entity("player") self.entity_manager.set_player_entity("player")
player.shadow = "player_shadow" player.shadow = "player_shadow"
self.renderer.register_shadow("assets/textures/entities/player/shadow.png", "player_shadow") self.renderer.register_shadow("assets/textures/entities/player/shadow.png", "player_shadow")
# On définit ses attributs
player.set_default_life(15)
player.max_speed = 64.0
player.x = 220.
player.y = 767.
# On place la caméra au niveau du joueur
self.camera.x = player.x
self.camera.y = player.y
self.camera.target_x = player.x
self.camera.target_y = player.y
# On enregistre l'entité
self.entity_manager.set_player_entity("player")
self.camera.follow_entity(player) self.camera.follow_entity(player)
def spawn_mobs(self): def spawn_mobs(self):