Files
Unreal_EmberWild/Source/Survival_projet/Public/MusicSubsystem.h
T

205 lines
7.4 KiB
C++

// 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<UAudioComponent>& Component, float FadeOutDuration);
void ScheduleGap(float DelaySeconds);
void CancelPendingGap();
float GetEffectiveVolume() const;
UPROPERTY()
TObjectPtr<UMusicPlaylistDataAsset> 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<UAudioComponent> MusicComponent;
/** La voie de sortie, le temps d'un croisement. Une seule a la fois. */
UPROPERTY()
TObjectPtr<UAudioComponent> 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<int32> 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;
};