// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Containers/Ticker.h" #include "Subsystems/GameInstanceSubsystem.h" #include "MusicSubsystem.generated.h" class UAudioComponent; class UMusicPlaylistDataAsset; class USoundBase; DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMusicTrackChanged, USoundBase*, NewTrack); /** * Ce qui arrive au morceau en cours quand on change de scene. * * La question ne se pose que parce que le lecteur, lui, ne s'arrete jamais : * sans consigne, la musique du menu se poursuit telle quelle dans la partie, au * milieu du morceau ou elle en etait. */ UENUM(BlueprintType) enum class EMusicLevelChangeMode : uint8 { /** Le morceau en cours poursuit sa route, comme si de rien n'etait. */ Continue UMETA(DisplayName = "Continue"), /** * Le morceau en cours s'eteint et le suivant demarre : nouveau tirage si * l'aleatoire est actif, morceau suivant de la liste sinon. La playlist, elle, * ne change pas. */ New UMETA(DisplayName = "New") }; /** * Le lecteur de musique du jeu. Un seul, pour toute la duree de l'application. * * UGameInstanceSubsystem et pas un acteur : il naƮt avec le GameInstance et meurt * avec le jeu, exactement comme USurvivalGameInstance. Aucune map ne le possede, * donc aucun OpenLevel ne le detruit -- c'est le seul endroit d'ou une musique * peut traverser le passage menu -> partie sans se couper. Le moteur l'instancie * tout seul, il n'y a rien a poser dans une scene ni a cocher nulle part. * * Le composant audio lui-meme est cree via CreateSound2D avec * bPersistAcrossLevelTransition : le moteur le construit alors SANS monde et sans * proprietaire, et lui pose bIgnoreForFlushing -- le drapeau que le vidage du * peripherique audio, declenche a chaque changement de niveau, saute * explicitement. Sans ce parametre le son s'arreterait net au chargement, meme si * l'objet C++, lui, survivait. * * Effet de bord voulu : ces composants sont marques bIsUISound, donc ils * continuent de jouer quand le jeu est en pause. Le menu de pause garde sa * musique sans une ligne de plus. */ UCLASS() class SURVIVAL_PROJET_API UMusicSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; /** Raccourci depuis n'importe quel objet ayant un monde. */ UFUNCTION(BlueprintPure, Category = "Musique", meta = (WorldContext = "WorldContextObject", DisplayName = "Get Music Subsystem")) static UMusicSubsystem* Get(const UObject* WorldContextObject); /** * Demarre une playlist. * * Si c'est DEJA celle qui joue, ne fait rien : c'est ce comportement qui rend * la transition menu -> jeu inaudible quand les deux maps designent la meme * playlist. Si c'en est une autre, croisement entre l'ancienne qui s'eteint et * la nouvelle qui entre. * * @param bForceRestart Repart du debut meme si la playlist est deja en cours. */ UFUNCTION(BlueprintCallable, Category = "Musique") void PlayPlaylist(UMusicPlaylistDataAsset* Playlist, bool bForceRestart = false); /** @param FadeOutDuration Negatif = utiliser le fondu declare par la playlist. */ UFUNCTION(BlueprintCallable, Category = "Musique") void StopMusic(float FadeOutDuration = -1.f); /** Passe au morceau suivant, dans l'ordre courant (aleatoire ou non). */ UFUNCTION(BlueprintCallable, Category = "Musique") void SkipToNextTrack(); /** * Bascule aleatoire / ordre de la liste. * * Ne coupe jamais le morceau en cours : seul l'enchainement change. Le curseur * se recale sur le morceau qui joue, donc en repassant en mode ordonne la * lecture reprend juste apres lui au lieu de sauter au debut de la liste. */ UFUNCTION(BlueprintCallable, Category = "Musique") void SetShuffleEnabled(bool bEnabled); UFUNCTION(BlueprintPure, Category = "Musique") bool IsShuffleEnabled() const { return bShuffleEnabled; } /** * Ce que devient le morceau en cours au prochain changement de scene. * * La consigne vit ICI et pas sur l'acteur qui l'a posee : l'acteur meurt avec * sa map, alors que le changement de scene se produit justement apres. Une * seule map -- le menu -- suffit donc a la declarer pour tout le jeu. */ UFUNCTION(BlueprintCallable, Category = "Musique") void SetLevelChangeMode(EMusicLevelChangeMode Mode) { LevelChangeMode = Mode; } UFUNCTION(BlueprintPure, Category = "Musique") EMusicLevelChangeMode GetLevelChangeMode() const { return LevelChangeMode; } /** Volume joueur, 0 a 1. A brancher sur l'onglet Audio des reglages plus tard. */ UFUNCTION(BlueprintCallable, Category = "Musique") void SetMusicVolume(float NewVolume); UFUNCTION(BlueprintPure, Category = "Musique") float GetMusicVolume() const { return MusicVolume; } UFUNCTION(BlueprintPure, Category = "Musique") bool IsPlaying() const { return bIsPlaying; } UFUNCTION(BlueprintPure, Category = "Musique") USoundBase* GetCurrentTrack() const; UFUNCTION(BlueprintPure, Category = "Musique") UMusicPlaylistDataAsset* GetCurrentPlaylist() const { return CurrentPlaylist; } /** Diffuse a chaque changement de morceau, nullptr a l'arret. */ UPROPERTY(BlueprintAssignable, Category = "Musique") FOnMusicTrackChanged OnMusicTrackChanged; private: /** Fin NATURELLE d'un morceau uniquement -- voir FadeOutAndRelease. */ UFUNCTION() void HandleTrackFinished(); /** Applique LevelChangeMode a chaque map chargee. */ void HandlePostLoadMap(UWorld* LoadedWorld); /** (Re)construit l'ordre de lecture du cycle a venir. */ void BuildPlayOrder(); void AdvanceAndPlay(float FadeInDuration); void PlayTrackAtCursor(float FadeInDuration); UAudioComponent* CreateMusicComponent(USoundBase* Sound); /** * Detache puis eteint un composant : apres cet appel, plus rien de ce qu'il * diffuse ne revient vers nous. */ void FadeOutAndRelease(TObjectPtr& Component, float FadeOutDuration); void ScheduleGap(float DelaySeconds); void CancelPendingGap(); float GetEffectiveVolume() const; UPROPERTY() TObjectPtr CurrentPlaylist; /** * UPROPERTY obligatoire : ces composants n'ont ni monde ni acteur proprietaire, * cette reference est la SEULE qui les tienne hors de portee du ramasse-miettes. */ UPROPERTY() TObjectPtr MusicComponent; /** La voie de sortie, le temps d'un croisement. Une seule a la fois. */ UPROPERTY() TObjectPtr FadingOutComponent; /** * Index dans Tracks, deja melangee si l'aleatoire est actif. * * Un ordre pre-melange plutot qu'un tirage a chaque morceau : le tirage * independant rejoue le meme titre deux fois de suite une fois sur N, et sur * une playlist de cinq morceaux ca s'entend tout de suite. */ TArray PlayOrder; int32 OrderCursor = INDEX_NONE; int32 LastPlayedTrackIndex = INDEX_NONE; bool bShuffleEnabled = true; bool bIsPlaying = false; float MusicVolume = 1.f; EMusicLevelChangeMode LevelChangeMode = EMusicLevelChangeMode::Continue; /** Abonnement au chargement de map, retire a la fermeture du jeu. */ FDelegateHandle PostLoadMapHandle; /** * Le silence entre deux morceaux passe par le ticker de Core, pas par un * FTimerHandle : un timer appartient au monde, donc il disparaƮtrait au * chargement de la map suivante -- et il ne tourne pas en pause. Le ticker de * Core ignore les deux. */ FTSTicker::FDelegateHandle GapTickerHandle; };