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