235 lines
9.6 KiB
C++
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;
|
|
};
|