Files
Unreal_EmberWild/Source/Survival_projet/Public/HeadBobComponent.h
T
2026-08-06 13:27:21 +02:00

235 lines
9.6 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HeadBobComponent.generated.h"
class ACharacter;
class UCharacterMovementComponent;
/**
* Les reglages d'un etat de deplacement : immobile, marche, sprint, accroupi, en l'air.
*
* Regroupes dans une struct plutot qu'a plat sur le composant pour deux raisons.
* D'abord le Details panel : cinq etats x cinq valeurs a plat feraient vingt-cinq
* lignes melangees, alors que la struct donne un repli par etat. Ensuite le code :
* passer d'un etat a l'autre est une interpolation champ a champ, et une struct
* permet de l'ecrire une seule fois au lieu d'une par valeur.
*/
USTRUCT(BlueprintType)
struct FHeadBobProfile
{
GENERATED_BODY()
FHeadBobProfile() = default;
FHeadBobProfile(float InStrideFrequency, float InVerticalAmount, float InLateralAmount, float InRollAmount, float InPitchAmount)
: StrideFrequency(InStrideFrequency)
, VerticalAmount(InVerticalAmount)
, LateralAmount(InLateralAmount)
, RollAmount(InRollAmount)
, PitchAmount(InPitchAmount)
{
}
/**
* Foulees par seconde. Une foulee compte DEUX pas : l'oscillation verticale
* bat donc au double de cette valeur, une bosse par pied qui touche le sol.
* A 1.1, les pas tombent a 2.2 Hz, ce qui correspond a une marche normale.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (ClampMin = "0", UIMax = "4"))
float StrideFrequency = 1.1f;
/** Amplitude verticale en cm, mesuree du repos au sommet (le debattement total vaut le double). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (ClampMin = "0", UIMax = "10"))
float VerticalAmount = 1.6f;
/** Amplitude laterale en cm : le balancement gauche-droite du buste. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (ClampMin = "0", UIMax = "10"))
float LateralAmount = 1.2f;
/** Roulis en degres au pic du balancement lateral. La tete penche dans le sens du pas. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (UIMin = "-3", UIMax = "3"))
float RollAmount = 0.4f;
/**
* Tangage en degres au pic vertical. A 0 par defaut, et ce n'est pas un oubli :
* le tangage deplace le reticule, donc il degrade la visee. A n'ouvrir que si
* l'on assume un jeu ou l'on ne vise pas au pixel.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (UIMin = "-3", UIMax = "3"))
float PitchAmount = 0.f;
};
/**
* Balancement de tete procedural, sans la moindre animation authoree.
*
* Le principe tient en une phase unique, avancee chaque frame par la frequence de
* l'etat courant, et une amplitude qui glisse d'un etat a l'autre. C'est ce qui
* rend le passage marche -> sprint continu : ce n'est pas une animation qu'on
* coupe pour en lancer une autre, ce sont deux jeux de coefficients entre lesquels
* on interpole pendant que la phase, elle, ne s'interrompt jamais. Remettre la
* phase a zero a chaque changement d'etat produirait exactement le "clac" qu'on
* cherche a eviter.
*
* Le composant ne touche PAS a la camera. Il expose un decalage de position et un
* decalage de rotation, que AFpsPlayer::ApplyCameraTransform additionne aux siens.
* C'est la condition pour que cette fonction reste le seul endroit du projet qui
* ecrit la transform de la camera -- elle est rappelee depuis CalcCamera, et deux
* ecrivains se marcheraient dessus une frame sur deux.
*
* Rien n'est replique et rien n'a a l'etre : le balancement n'existe que dans les
* yeux du joueur local. Les bras suivent gratuitement, puisqu'ils sont attaches a
* la camera.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UHeadBobComponent : public UActorComponent
{
GENERATED_BODY()
public:
UHeadBobComponent();
/**
* Fait avancer la phase et recalcule les decalages.
*
* Appelee par le pawn depuis son Tick, et surtout PAS depuis un TickComponent a
* lui : l'ordre de tick entre un acteur et ses composants n'est pas garanti sans
* dependance explicite, et le resultat serait un decalage lu une frame apres
* avoir ete calcule -- invisible a 120 fps, franchement mou a 30.
*/
void UpdateBob(float DeltaTime);
/** Decalage a ajouter a la position RELATIVE de la camera (X avant, Y droite, Z haut), en cm. */
UFUNCTION(BlueprintPure, Category = "Head Bob")
FVector GetLocationOffset() const { return LocationOffset; }
/** Decalage a ajouter a la rotation de la camera, en degres. */
UFUNCTION(BlueprintPure, Category = "Head Bob")
FRotator GetRotationOffset() const { return RotationOffset; }
/**
* Remet le balancement au repos sans transition.
* Pour une teleportation ou une prise de controle : on ne veut pas que la tete
* finisse le pas d'avant a l'autre bout de la carte.
*/
UFUNCTION(BlueprintCallable, Category = "Head Bob")
void ResetBob();
/**
* Facteur global pousse par les reglages joueur (curseur "secousses de camera").
* A 0 la tete ne bouge plus du tout : c'est un confort d'accessibilite, pas une
* option cosmetique, et il doit valoir pour le balancement comme pour les chocs.
*/
UFUNCTION(BlueprintCallable, Category = "Head Bob")
void SetIntensityScale(float NewScale);
protected:
virtual void BeginPlay() override;
// ------------------------------------------------------------------
// Reglages generaux
// ------------------------------------------------------------------
/** Coupe entierement l'effet, sans avoir a remettre les cinq profils a zero. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob")
bool bEnableHeadBob = true;
/**
* Multiplicateur d'amplitude commun aux cinq profils.
* C'est le curseur a bouger pour un reglage d'ensemble ("un peu moins de tout")
* sans avoir a retoucher vingt valeurs une par une.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (ClampMin = "0", UIMax = "3"))
float GlobalIntensity = 1.f;
/**
* Vitesse de fondu d'un profil vers un autre (facteur d'interpolation
* exponentielle, par seconde). Plus haut = le sprint prend son amplitude plus
* vite. Trop haut, le changement d'etat redevient une cassure.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob", meta = (ClampMin = "0.1", UIMax = "20"))
float ProfileBlendSpeed = 6.f;
// ------------------------------------------------------------------
// Seuils de choix d'etat
// ------------------------------------------------------------------
/**
* En dessous de cette vitesse horizontale (cm/s), on est considere immobile.
* Le seuil se lit sur la vitesse REELLE et non sur la touche enfoncee : marcher
* dans un mur laisse l'input actif alors que le corps ne bouge plus, et sans ce
* detail la tete continuerait de gigoter face a la cloison.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Seuils", meta = (ClampMin = "0"))
float IdleSpeedThreshold = 10.f;
/**
* Au-dessus de cette vitesse horizontale (cm/s), on bascule sur le profil de
* sprint. A regler entre WalkSpeed et SprintSpeed du pawn -- 400 et 650 par
* defaut, d'ou ces 520 : assez haut pour que la phase d'acceleration ne compte
* pas comme du sprint, assez bas pour que le sprint reel soit toujours detecte.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Seuils", meta = (ClampMin = "0"))
float SprintSpeedThreshold = 520.f;
// ------------------------------------------------------------------
// Profils, un par etat. Valeurs par defaut dans le constructeur.
// ------------------------------------------------------------------
/** Immobile : une respiration lente, quasi purement verticale. C'est ce qui evite la statue. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Profils")
FHeadBobProfile IdleProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Profils")
FHeadBobProfile WalkProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Profils")
FHeadBobProfile SprintProfile;
/** Accroupi : plus lent et plus discret que la marche, le corps est ramasse. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Profils")
FHeadBobProfile CrouchProfile;
/**
* En l'air : tout a zero, y compris la frequence -- la phase se fige donc en vol
* et repart d'ou elle en etait a la reception, ce qui evite que le premier pas
* apres un saut tombe sur un pied arbitraire. Le profil reste editable malgre
* tout, au cas ou l'on veuille un flottement pendant la chute.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Head Bob|Profils")
FHeadBobProfile AirProfile;
private:
/** Choisit le profil vise a partir de l'etat reel du CharacterMovement. */
const FHeadBobProfile& SelectTargetProfile() const;
/** Rapproche BlendedProfile de Target, champ par champ. */
void BlendTowards(const FHeadBobProfile& Target, float Step);
/**
* Position dans le cycle de foulee, en radians, ramenee dans [0, 2*PI[.
* Le repli n'est pas cosmetique : sans lui la valeur grossit indefiniment et la
* precision du float se degrade, jusqu'a un balancement qui devient saccade au
* bout d'une longue partie.
*/
float StridePhase = 0.f;
/** Profil courant, resultat du fondu entre les profils d'etat. */
FHeadBobProfile BlendedProfile;
FVector LocationOffset = FVector::ZeroVector;
FRotator RotationOffset = FRotator::ZeroRotator;
/** Pousse par les reglages joueur, se multiplie a GlobalIntensity. */
float IntensityScale = 1.f;
/** Caches : ces deux pointeurs sont lus a chaque frame, pas question d'y caster. */
TObjectPtr<ACharacter> OwnerCharacter;
TObjectPtr<UCharacterMovementComponent> OwnerMovement;
};