Files
Unreal_EmberWild/Source/Survival_projet/Public/FootstepComponent.h
T
2026-08-11 15:23:10 +02:00

363 lines
14 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Engine/EngineTypes.h"
#include "FootstepSoundsDataAsset.h"
#include "FootstepComponent.generated.h"
class ACharacter;
class UCharacterMovementComponent;
class USoundAttenuation;
class USoundBase;
class USoundConcurrency;
/**
* Les reglages d'une allure : sa foulee et son volume.
*
* Groupes dans une struct pour la meme raison que FHeadBobProfile : trois
* allures a plat feraient neuf lignes melangees dans le Details panel, alors
* que la struct donne un repli par allure.
*/
USTRUCT(BlueprintType)
struct FFootstepGaitSettings
{
GENERATED_BODY()
FFootstepGaitSettings() = default;
FFootstepGaitSettings(float InStrideLength, float InVolume)
: StrideLength(InStrideLength)
, Volume(InVolume)
{
}
/**
* Distance parcourue entre deux pas, en cm.
*
* C'est le reglage central du systeme, et il se calcule plutot qu'il ne se
* devine : pour que les pas tombent en rythme avec le balancement de tete,
* il faut StrideLength = Vitesse / (2 x StrideFrequency du profil de
* UHeadBobComponent) -- le "2" parce qu'une foulee compte deux pas. Les
* valeurs par defaut sortent de ce calcul avec les vitesses du pawn.
*
* Corollaire a retenir : changer WalkSpeed ou SprintSpeed sans retoucher ici
* desynchronise le son de l'image, et ca s'entend avant de se voir.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps", meta = (ClampMin = "10", UIMax = "300"))
float StrideLength = 180.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps", meta = (ClampMin = "0", UIMax = "2"))
float Volume = 1.f;
/**
* Variation aleatoire de hauteur, en plus ou en moins autour de 1.
*
* Le pack ne livre qu'un ou deux echantillons par surface et par allure :
* sans ce grain, une course en ligne droite repete exactement le meme son
* trois fois par seconde et l'oreille l'entend comme une boucle. Quelques
* pourcents suffisent, au-dela le personnage change de poids a chaque pas.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps", meta = (ClampMin = "0", UIMax = "0.4"))
float PitchVariation = 0.06f;
};
/**
* Bruits de pas : mesure la distance parcourue, cherche la matiere sous les
* pieds, joue le son qui va avec.
*
* ---------------------------------------------------------------------------
* Pourquoi la distance et pas un AnimNotify
* ---------------------------------------------------------------------------
* C'est le choix structurant. Trois raisons de ne pas accrocher le son aux
* animations :
*
* - Le corps 3e personne du joueur local est en bOwnerNoSee. Faire dependre
* son propre son d'un mesh qu'il ne voit pas est fragile, et le devient
* d'autant plus le jour ou l'on optimisera son tick d'animation.
* - Les animations du pack n'ont aucun notify, et il faudrait en poser dans le
* BlendSpace, ou ils se declenchent selon l'echantillon dominant : le
* passage marche -> course y saute ou double un pas.
* - Les bras FPS sont sur BasePose_Skeleton, qui n'a rien a voir avec
* SK_BodyA. Aucun notify ne peut servir aux deux.
*
* La distance, elle, donne un espacement regulier, se regle en PIE sans
* recompiler, et se comporte identiquement sur le pawn local et sur ceux des
* amis.
*
* ---------------------------------------------------------------------------
* Reseau : rien n'est replique, et rien n'a a l'etre
* ---------------------------------------------------------------------------
* Exactement le raisonnement de UPlayerBodyAnimInstance : tout ce que ce
* composant lit DERIVE d'un etat deja synchronise -- la vitesse par le
* CharacterMovement, l'accroupissement par ACharacter, le mode de deplacement
* par la replication du mouvement. Chaque machine calcule donc les pas de tous
* les pawns qu'elle connait. Un Multicast par pas, a trois pas par seconde et
* par joueur, serait du trafic pur pour un resultat identique.
*
* Le son du joueur local part en 2D, celui des autres en 3D a leurs pieds :
* un pas local joue en 3D depuis les chevilles est attenue et sonne creux.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UFootstepComponent : public UActorComponent
{
GENERATED_BODY()
public:
UFootstepComponent();
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* Coupe ou rallume les pas. Pour la mort, une cinematique, un mode fantome.
* Coupe, le compteur de foulee est remis a zero : rallumer ne doit pas
* declencher le pas reste en attente depuis l'autre bout de la carte.
*/
UFUNCTION(BlueprintCallable, Category = "Footsteps")
void SetFootstepsEnabled(bool bEnabled);
/** Remet le cycle de foulee a zero. Pour une teleportation ou un respawn. */
UFUNCTION(BlueprintCallable, Category = "Footsteps")
void ResetStride();
/**
* Joue un pas immediatement, a l'allure courante. Sert aux montages et aux
* AnimNotify si l'on veut un jour caler un geste precis sur un son.
*/
UFUNCTION(BlueprintCallable, Category = "Footsteps")
void PlayFootstepNow();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/**
* Saut et reception, sur TOUTES les machines.
*
* Et surtout pas ACharacter::Landed / OnJumped, qui semblent pourtant faits
* pour ca : Landed vient de ProcessLanded, qui n'est appele que la ou le
* mouvement est reellement simule, et OnJumped que la ou l'input est lu. Ni
* l'un ni l'autre n'existe sur le pawn d'un ami vu depuis notre machine, ou
* le mouvement arrive deja fait. Le mode de deplacement, lui, EST replique,
* donc ce delegue est diffusé partout -- c'est le seul signal qui couvre les
* quatre joueurs.
*/
UFUNCTION()
void HandleMovementModeChanged(ACharacter* Character, EMovementMode PrevMovementMode, uint8 PreviousCustomMode);
// ------------------------------------------------------------------
// Assets
// ------------------------------------------------------------------
/** La table surface -> sons. Sans elle le composant ne fait rien. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
TObjectPtr<UFootstepSoundsDataAsset> SoundBank;
/**
* Attenuation appliquee aux pas des AUTRES joueurs (le pas local est en 2D).
*
* A remplir : le pack ne fournit aucune attenuation, donc sans cet asset les
* cues sont jouees en 3D sans courbe de distance et s'entendent d'un bout a
* l'autre de la carte au meme volume.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
TObjectPtr<USoundAttenuation> Attenuation;
/**
* Concurrence, facultative. A quatre joueurs qui courent, huit a douze pas
* par seconde peuvent se superposer ; une regle a 4 ou 5 voix evite que les
* pas mangent le reste du mixage.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
TObjectPtr<USoundConcurrency> Concurrency;
// ------------------------------------------------------------------
// Reglages generaux
// ------------------------------------------------------------------
/** Coupe entierement l'effet sans avoir a vider la banque de sons. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
bool bEnableFootsteps = true;
/**
* Joue le pas du joueur local en 2D plutot qu'a la position de ses pieds.
* Decoche pour l'entendre spatialise comme celui des autres -- utile pour
* regler l'attenuation sans avoir besoin d'un second joueur.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
bool bPlayLocalStepsIn2D = true;
/**
* Multiplicateur applique au seul pas local. Sous 1 par defaut : un son 2D
* arrive a pleine puissance dans les deux oreilles, la ou celui des autres
* passe par l'attenuation.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps", meta = (ClampMin = "0", UIMax = "2"))
float LocalVolumeScale = 0.7f;
/** Joue le pas final quand le personnage s'immobilise. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps")
bool bPlayStopSounds = true;
// ------------------------------------------------------------------
// Allures
// ------------------------------------------------------------------
/** Accroupi. Valeurs par defaut dans le constructeur. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures")
FFootstepGaitSettings CrouchGait;
/** Marche normale. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures")
FFootstepGaitSettings WalkGait;
/** Sprint. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures")
FFootstepGaitSettings SprintGait;
/**
* En dessous de cette vitesse horizontale (cm/s), on considere le
* personnage immobile. Meme seuil et meme raison que UHeadBobComponent :
* marcher dans un mur laisse l'input actif alors que le corps n'avance plus.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures", meta = (ClampMin = "0"))
float IdleSpeedThreshold = 10.f;
/**
* Au-dessus de cette vitesse horizontale (cm/s), on passe sur l'allure de
* sprint. A caler entre WalkSpeed et SprintSpeed du pawn, comme le seuil
* homonyme du balancement de tete -- les deux doivent basculer ensemble,
* sinon la tete accelere avant les pieds.
*
* La vitesse REELLE et pas AFpsPlayer::IsSprinting() : le composant reste
* ainsi utilisable par un PNJ ou un animal, qui n'ont pas de touche Maj.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures", meta = (ClampMin = "0"))
float SprintSpeedThreshold = 520.f;
/**
* Fraction de foulee deja "parcourue" au demarrage du mouvement.
*
* A 0, le premier pas ne tombe qu'apres une foulee entiere : le personnage
* part en silence sur presque deux metres, ce qui se lit comme un temps de
* reaction du jeu. A 1 il claque avant que le corps ait bouge. La moitie
* place le premier pas juste apres l'entree en mouvement.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Allures", meta = (ClampMin = "0", ClampMax = "1"))
float FirstStepFraction = 0.5f;
// ------------------------------------------------------------------
// Saut et reception
// ------------------------------------------------------------------
/**
* Vitesse de chute (cm/s) en dessous de laquelle la reception est muette.
* Descendre une marche d'escalier passe techniquement par un instant de
* chute ; sans ce seuil, la moindre bosse du terrain claque.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Air", meta = (ClampMin = "0"))
float LandMinFallSpeed = 150.f;
/** Volume du saut et de la reception. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Air", meta = (ClampMin = "0", UIMax = "2"))
float AirVolume = 1.f;
// ------------------------------------------------------------------
// Trace de sol
// ------------------------------------------------------------------
/**
* Profondeur sondee sous les pieds, en cm. Quelques dizaines suffisent : le
* trace part du centre de la capsule et on ne cherche le sol que lorsqu'on
* marche dessus.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Trace", meta = (ClampMin = "1"))
float TraceDepth = 60.f;
/**
* Trace en collision COMPLEXE, et ce n'est pas negociable pour ce systeme.
*
* Un PhysicalMaterial pose sur un MATERIAU n'est rendu que par un trace
* per-poly : le trace simple remonte celui du BodySetup du mesh, qui est
* presque toujours vide. Le symptome d'un decochage est immediat et trompeur
* -- tout le monde marche sur DefaultSurface, comme si la table n'etait pas
* remplie.
*
* Le cout est nul a l'echelle du jeu : deux a trois traces par seconde et
* par personnage, jamais un par frame.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Trace")
bool bTraceComplex = true;
/** Canal du trace de sol. Visibility couvre tout le decor du projet. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Footsteps|Trace")
TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
private:
/** L'allure correspondant a l'etat reel du CharacterMovement. */
EFootstepGait SelectGait(float HorizontalSpeed) const;
/** Les reglages de l'allure courante. */
const FFootstepGaitSettings& GetGaitSettings(EFootstepGait Gait) const;
/**
* Cherche la matiere sous les pieds et rend les sons correspondants.
* @param OutLocation ou jouer le son : le point d'impact, ou les pieds si
* le trace n'a rien touche (le sol peut etre hors du canal trace).
*/
const FFootstepSurfaceSounds* QueryGroundSounds(FVector& OutLocation) const;
/** Joue une cue, en 2D chez le proprietaire et en 3D partout ailleurs. */
void PlayCue(USoundBase* Sound, float Volume, float PitchVariation, const FVector& Location) const;
/** Vrai quand le son doit sortir des haut-parleurs de CETTE machine. */
bool ShouldPlaySounds() const;
// ------------------------------------------------------------------
// Etat
// ------------------------------------------------------------------
/** Distance horizontale accumulee depuis le dernier pas, en cm. */
float DistanceSinceLastStep = 0.f;
/** Position de l'acteur au tick precedent, pour mesurer le deplacement reel. */
FVector LastLocation = FVector::ZeroVector;
/**
* Vrai des le premier tick valide. Sans lui, le premier deplacement mesure
* partirait de l'origine du monde et declencherait une salve de pas.
*/
bool bHasLastLocation = false;
/** Etat du tick precedent, pour detecter le demarrage et l'arret. */
bool bWasMoving = false;
/** Allure du dernier pas joue : c'est elle qui choisit le son d'arret. */
EFootstepGait LastGait = EFootstepGait::Jog;
/**
* Nombre de pas depuis l'entree en mouvement. Le son d'arret n'est joue
* qu'a partir de un : un a-coup de collision suffit sinon a faire claquer
* un pas final alors que le personnage n'a pas bouge.
*/
int32 StepsSinceMoveStart = 0;
/**
* Plus grande vitesse de chute observee, en cm/s et positive.
*
* Mesuree pendant la chute plutot que lue a l'impact : au moment ou le mode
* repasse a Walking, le moteur a deja remis Velocity.Z a zero. Un tick a
* 20 Hz l'echantillonne largement assez -- et le cas ou il n'a pas eu le
* temps de tourner du tout est precisement celui d'une chute negligeable,
* qu'on veut de toute facon silencieuse.
*/
float MaxFallSpeed = 0.f;
/** Caches : lus a chaque tick, pas question d'y caster. */
TObjectPtr<ACharacter> OwnerCharacter;
TObjectPtr<UCharacterMovementComponent> OwnerMovement;
};