352 lines
11 KiB
C++
352 lines
11 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "SurvivalUserSettings.h"
|
|
|
|
#include "AudioDevice.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/World.h"
|
|
#include "Misc/App.h"
|
|
#include "Sound/AudioSettings.h"
|
|
#include "Sound/SoundClass.h"
|
|
#include "Sound/SoundMix.h"
|
|
#include "SurvivalAudioSettings.h"
|
|
|
|
namespace
|
|
{
|
|
/** Arrondit sur le palier le plus proche, puis borne. */
|
|
float SnapToStep(float Value, float Min, float Max, float Step)
|
|
{
|
|
const float Snapped = Min + FMath::RoundToFloat((Value - Min) / Step) * Step;
|
|
return FMath::Clamp(Snapped, Min, Max);
|
|
}
|
|
|
|
/** Pourcentage affiche -> amplitude reelle, courbe perceptive comprise. */
|
|
float VolumePercentToAmplitude(float Percent)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
|
|
const float Normalized = FMath::Clamp(Percent, VolumeMin, VolumeMax) / 100.f;
|
|
return FMath::Pow(Normalized, VolumeCurveExponent);
|
|
}
|
|
|
|
/**
|
|
* Le peripherique audio a qui parler.
|
|
*
|
|
* En PIE le monde de jeu peut avoir le sien, distinct de celui de l'editeur :
|
|
* viser systematiquement le peripherique principal ferait regler le volume de
|
|
* l'editeur pendant que le jeu, lui, ne bouge pas.
|
|
*/
|
|
FAudioDevice* GetSettingsAudioDevice()
|
|
{
|
|
if (!GEngine)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (const UWorld* PlayWorld = GEngine->GetCurrentPlayWorld())
|
|
{
|
|
if (FAudioDevice* WorldDevice = PlayWorld->GetAudioDeviceRaw())
|
|
{
|
|
return WorldDevice;
|
|
}
|
|
}
|
|
|
|
return GEngine->GetMainAudioDeviceRaw();
|
|
}
|
|
|
|
/**
|
|
* Le mix par lequel passent les volumes du joueur.
|
|
*
|
|
* C'est celui de Project Settings > Audio > Default Base Sound Mix, et pas une
|
|
* reference a nous. Deux raisons : le moteur le REPOSE a chaque chargement de
|
|
* map (UWorld::UpdateWorldComponents -> SetDefaultBaseSoundMix), donc il est
|
|
* toujours actif sans qu'on ait a le pousser nous-memes ; et un mix de base est
|
|
* evalue a plein regime en permanence, alors qu'un mix pousse a la main
|
|
* s'eteindrait au bout de sa duree.
|
|
*/
|
|
USoundMix* GetSettingsSoundMix()
|
|
{
|
|
const FSoftObjectPath& MixPath = GetDefault<UAudioSettings>()->DefaultBaseSoundMix;
|
|
return MixPath.IsValid() ? Cast<USoundMix>(MixPath.TryLoad()) : nullptr;
|
|
}
|
|
|
|
/**
|
|
* Pose le volume d'une SoundClass, en pourcentage.
|
|
*
|
|
* bApplyToChildren merite une explication, parce que se tromper la donne un
|
|
* volume maitre qui n'agit que sur les sons non tagues : le moteur propage les
|
|
* proprietes du parent vers les enfants (ParseSoundClasses) AVANT d'appliquer
|
|
* les ajusteurs du mix (ApplyClassAdjusters). Un ajusteur pose sur la classe
|
|
* maitre arrive donc trop tard pour redescendre tout seul -- il faut demander
|
|
* explicitement la recursion. Les feuilles, elles, gardent false : leur
|
|
* ajusteur n'a personne a qui redescendre.
|
|
*
|
|
* Aucun fondu non plus, et ce n'est pas un oubli : toute duree non nulle rend le
|
|
* curseur inutilisable. Quand on repose un override alors que le precedent n'a
|
|
* pas fini son fondu, UpdateClassAdjustorOverrideEntry relance l'interpolation
|
|
* (FDynamicParameter::Set remet CurrTimeSec a zero) mais n'appelle PAS
|
|
* Update(DeltaTime) cette frame-la. Un curseur qu'on glisse repose l'override a
|
|
* chaque image : la rampe repart indefiniment de zero et GetValue() rend toujours
|
|
* la valeur precedente. Le volume ne rattrape son retard qu'une fois le geste
|
|
* ARRETE. A zero, Set() ecrit directement la valeur -- et le mixeur lisse deja le
|
|
* gain de chaque source dans son buffer de rendu, donc pas de clic pour autant.
|
|
*/
|
|
void ApplyClassVolume(FAudioDevice& AudioDevice, USoundMix& Mix, USoundClass* SoundClass, float Percent, bool bApplyToChildren)
|
|
{
|
|
if (!SoundClass)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AudioDevice.SetSoundMixClassOverride(&Mix, SoundClass,
|
|
VolumePercentToAmplitude(Percent), /*Pitch*/ 1.f, /*FadeInTime*/ 0.f, bApplyToChildren);
|
|
}
|
|
}
|
|
|
|
USurvivalUserSettings::USurvivalUserSettings()
|
|
{
|
|
// Le constructeur tourne sur le CDO, avant toute lecture du .ini : ces
|
|
// valeurs sont donc bien les defauts d'usine, ecrasees ensuite par le
|
|
// fichier s'il existe.
|
|
SetSurvivalGameplayToDefaults();
|
|
SetSurvivalDisplayToDefaults();
|
|
SetSurvivalAudioToDefaults();
|
|
}
|
|
|
|
USurvivalUserSettings* USurvivalUserSettings::Get()
|
|
{
|
|
return GEngine ? Cast<USurvivalUserSettings>(GEngine->GetGameUserSettings()) : nullptr;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetSurvivalGameplayToDefaults()
|
|
{
|
|
LookSensitivity = 35.f;
|
|
bSeparateVerticalSensitivity = false;
|
|
LookSensitivityVertical = 35.f;
|
|
bInvertLookY = false;
|
|
FieldOfView = 90.f;
|
|
CameraShakeScale = 100.f;
|
|
bSprintToggle = false;
|
|
bCrouchToggle = false;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetSurvivalDisplayToDefaults()
|
|
{
|
|
Brightness = 100.f;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetSurvivalAudioToDefaults()
|
|
{
|
|
// 80 et non 100 sur le maitre : il faut laisser au joueur de la marge vers le
|
|
// haut, sinon le seul reglage possible est de baisser.
|
|
MasterVolume = 80.f;
|
|
|
|
// La musique sous les effets, comme partout : elle est continue, eux sont des
|
|
// evenements, et un evenement doit passer par-dessus le fond.
|
|
MusicVolume = 60.f;
|
|
EffectsVolume = 100.f;
|
|
UiVolume = 80.f;
|
|
|
|
// Le defaut du moteur, qu'on se contente d'exposer.
|
|
bPlayInBackground = false;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetToDefaults()
|
|
{
|
|
// Le "Reinitialiser" global du moteur doit aussi remettre NOS valeurs :
|
|
// sans cette surcharge, elles resteraient seules inchangees.
|
|
Super::SetToDefaults();
|
|
|
|
SetSurvivalGameplayToDefaults();
|
|
SetSurvivalDisplayToDefaults();
|
|
SetSurvivalAudioToDefaults();
|
|
}
|
|
|
|
void USurvivalUserSettings::SetBrightness(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
Brightness = SnapToStep(Value, BrightnessMin, BrightnessMax, BrightnessStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::ApplySurvivalSettings()
|
|
{
|
|
// Le gamma n'appartient pas a UGameUserSettings : il vit sur GEngine, donc
|
|
// sur un objet GLOBAL au processus. Deux consequences :
|
|
//
|
|
// 1. DisplayGamma est un EXPOSANT dont la valeur moteur est 2.2, pas un
|
|
// multiplicateur neutre a 1. On module la reference, on ne la remplace
|
|
// pas.
|
|
// 2. En editeur, cet objet est celui de l'editeur : y toucher assombrit
|
|
// toute l'interface, y compris apres avoir quitte le PIE. On ne l'applique
|
|
// donc qu'en jeu. La luminosite ne se teste pas en PIE, mais en
|
|
// Standalone -- qui est un processus separe.
|
|
if (GEngine && !GIsEditor)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
|
|
const float Normalized = FMath::Clamp(Brightness, BrightnessMin, BrightnessMax) / 100.f;
|
|
GEngine->DisplayGamma = BaseDisplayGamma * Normalized;
|
|
}
|
|
|
|
OnSurvivalSettingsApplied.Broadcast();
|
|
}
|
|
|
|
void USurvivalUserSettings::ApplyAudioSettings()
|
|
{
|
|
// Ce reglage-la ne passe pas par le mix : c'est l'application entiere qui est
|
|
// baissee quand la fenetre perd le focus, bien en amont des SoundClass. Le
|
|
// moteur le stocke dans son propre .ini, d'ou une valeur reposee a chaque
|
|
// application plutot que memorisee ailleurs.
|
|
FApp::SetUnfocusedVolumeMultiplier(bPlayInBackground ? 1.f : 0.f);
|
|
|
|
FAudioDevice* AudioDevice = GetSettingsAudioDevice();
|
|
if (!AudioDevice)
|
|
{
|
|
// Normal tres tot au demarrage, ou en serveur dedie : les valeurs sont
|
|
// deja enregistrees, le prochain passage les posera.
|
|
return;
|
|
}
|
|
|
|
USoundMix* Mix = GetSettingsSoundMix();
|
|
if (!Mix)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("USurvivalUserSettings : aucun Default Base Sound Mix dans Project Settings > Audio, les curseurs de volume resteront sans effet."));
|
|
return;
|
|
}
|
|
|
|
USurvivalAudioSettings* AudioAssets = USurvivalAudioSettings::Get();
|
|
|
|
// Le maitre EN PREMIER et avec recursion : il redescend sur les trois autres,
|
|
// qui reappliquent ensuite la leur par-dessus. Les deux se multiplient, ce qui
|
|
// est exactement ce qu'on attend d'un volume general.
|
|
ApplyClassVolume(*AudioDevice, *Mix, AudioAssets->GetMasterSoundClass(), MasterVolume, /*bApplyToChildren*/ true);
|
|
ApplyClassVolume(*AudioDevice, *Mix, AudioAssets->GetMusicSoundClass(), MusicVolume, /*bApplyToChildren*/ false);
|
|
ApplyClassVolume(*AudioDevice, *Mix, AudioAssets->GetEffectsSoundClass(), EffectsVolume, /*bApplyToChildren*/ false);
|
|
ApplyClassVolume(*AudioDevice, *Mix, AudioAssets->GetUiSoundClass(), UiVolume, /*bApplyToChildren*/ false);
|
|
}
|
|
|
|
void USurvivalUserSettings::ApplyNonResolutionSettings()
|
|
{
|
|
Super::ApplyNonResolutionSettings();
|
|
|
|
// ApplySettings() passe forcement par ici : brancher la diffusion a ce
|
|
// niveau garantit que nos reglages suivent, quel que soit le chemin.
|
|
ApplySurvivalSettings();
|
|
ApplyAudioSettings();
|
|
}
|
|
|
|
float USurvivalUserSettings::GetLookMultiplierX() const
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
|
|
const float Normalized = FMath::GetRangePct(SensitivityMin, SensitivityMax, LookSensitivity);
|
|
return FMath::Lerp(LookMultiplierAtMin, LookMultiplierAtMax, Normalized);
|
|
}
|
|
|
|
float USurvivalUserSettings::GetLookMultiplierY() const
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
|
|
// Sans axe separe, le vertical suit l'horizontal : c'est ce que veulent
|
|
// 95 % des joueurs, et deux curseurs desynchronises par inadvertance
|
|
// donnent une visee bancale sans qu'on comprenne pourquoi.
|
|
if (!bSeparateVerticalSensitivity)
|
|
{
|
|
return GetLookMultiplierX();
|
|
}
|
|
|
|
const float Normalized = FMath::GetRangePct(SensitivityMin, SensitivityMax, LookSensitivityVertical);
|
|
return FMath::Lerp(LookMultiplierAtMin, LookMultiplierAtMax, Normalized);
|
|
}
|
|
|
|
float USurvivalUserSettings::GetCameraShakeMultiplier() const
|
|
{
|
|
return FMath::Clamp(CameraShakeScale / 100.f, 0.f, 1.f);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetLookSensitivity(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
LookSensitivity = SnapToStep(Value, SensitivityMin, SensitivityMax, SensitivityStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetSeparateVerticalSensitivity(bool bValue)
|
|
{
|
|
// En repassant sur "non separe" on recale le vertical sur l'horizontal :
|
|
// sinon la valeur oubliee ressortirait telle quelle a la reactivation.
|
|
if (!bValue)
|
|
{
|
|
LookSensitivityVertical = LookSensitivity;
|
|
}
|
|
|
|
bSeparateVerticalSensitivity = bValue;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetLookSensitivityVertical(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
LookSensitivityVertical = SnapToStep(Value, SensitivityMin, SensitivityMax, SensitivityStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetInvertLookY(bool bValue)
|
|
{
|
|
bInvertLookY = bValue;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetFieldOfView(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
FieldOfView = SnapToStep(Value, FieldOfViewMin, FieldOfViewMax, FieldOfViewStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetCameraShakeScale(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
CameraShakeScale = SnapToStep(Value, CameraShakeMin, CameraShakeMax, CameraShakeStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetSprintToggle(bool bValue)
|
|
{
|
|
bSprintToggle = bValue;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetCrouchToggle(bool bValue)
|
|
{
|
|
bCrouchToggle = bValue;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetMasterVolume(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
MasterVolume = SnapToStep(Value, VolumeMin, VolumeMax, VolumeStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetMusicVolume(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
MusicVolume = SnapToStep(Value, VolumeMin, VolumeMax, VolumeStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetEffectsVolume(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
EffectsVolume = SnapToStep(Value, VolumeMin, VolumeMax, VolumeStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetUiVolume(float Value)
|
|
{
|
|
using namespace SurvivalSettingsRange;
|
|
UiVolume = SnapToStep(Value, VolumeMin, VolumeMax, VolumeStep);
|
|
}
|
|
|
|
void USurvivalUserSettings::SetPlayInBackground(bool bValue)
|
|
{
|
|
bPlayInBackground = bValue;
|
|
}
|
|
|
|
void USurvivalUserSettings::SetCharacterAppearance(const FCharacterAppearance& InAppearance)
|
|
{
|
|
CharacterAppearance = InAppearance;
|
|
}
|