(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user