// 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 OwnerCharacter; TObjectPtr OwnerMovement; };