Files

142 lines
5.4 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "HeadBobComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
UHeadBobComponent::UHeadBobComponent()
{
// C'est le pawn qui appelle UpdateBob depuis son propre Tick, juste avant de
// composer la transform de la camera. Un tick a nous ne servirait qu'a
// introduire une incertitude d'ordre.
PrimaryComponentTick.bCanEverTick = false;
// Valeurs de depart, a affiner a l'oeil dans BP_FpsPlayer. Elles sont ici et
// non en initialiseurs de membre pour que le constructeur reste le seul
// endroit a lire quand on cherche "combien ca bouge par defaut".
IdleProfile = FHeadBobProfile(0.15f, 0.55f, 0.05f, 0.03f, 0.f);
WalkProfile = FHeadBobProfile(1.1f, 1.6f, 1.2f, 0.4f, 0.f);
SprintProfile = FHeadBobProfile(1.55f, 2.1f, 1.7f, 0.8f, 0.f);
CrouchProfile = FHeadBobProfile(0.7f, 1.f, 0.9f, 0.3f, 0.f);
AirProfile = FHeadBobProfile(0.f, 0.f, 0.f, 0.f, 0.f);
// On demarre deja sur le profil d'immobilite : partir de zeros ferait monter
// la respiration pendant la premiere seconde de jeu, ce qui se voit.
BlendedProfile = IdleProfile;
}
void UHeadBobComponent::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
if (!OwnerCharacter)
{
UE_LOG(LogTemp, Warning, TEXT("UHeadBobComponent : le proprietaire n'est pas un ACharacter, le balancement de tete est desactive."));
return;
}
// GetCharacterMovement() est un simple accesseur sur un membre du Character,
// mais le stocker evite un appel indirect par frame pour rien.
OwnerMovement = OwnerCharacter->GetCharacterMovement();
}
void UHeadBobComponent::UpdateBob(float DeltaTime)
{
// IsLocallyControlled : chez l'hote, le pawn de chaque ami porte AUSSI ce
// composant. Sans cette garde, le serveur calculerait trois balancements pour
// des cameras que personne ne regarde.
if (!bEnableHeadBob || !OwnerCharacter || !OwnerMovement || !OwnerCharacter->IsLocallyControlled())
{
// Remise a zero et pas simple sortie : couper l'effet en pleine partie
// laisserait sinon la camera figee sur le dernier decalage calcule.
LocationOffset = FVector::ZeroVector;
RotationOffset = FRotator::ZeroRotator;
return;
}
// Meme garde-fou que les ressorts de camera : un hoquet de framerate ferait
// sauter la phase de plusieurs pas d'un coup.
const float Step = FMath::Min(DeltaTime, 1.f / 30.f);
BlendTowards(SelectTargetProfile(), Step);
// La phase avance a la frequence FONDUE, pas a celle du profil vise : c'est
// ce qui fait que le rythme des pas accelere progressivement au demarrage du
// sprint au lieu de doubler d'une frame a l'autre.
StridePhase = FMath::Fmod(StridePhase + BlendedProfile.StrideFrequency * 2.f * PI * Step, 2.f * PI);
const float Intensity = GlobalIntensity * IntensityScale;
// Une foulee = deux pas. Le balancement lateral suit la foulee entiere (on
// bascule sur un pied puis sur l'autre), la montee-descente bat deux fois plus
// vite, une bosse par pied qui touche le sol. C'est ce rapport 1:2 qui dessine
// le fameux 8 couche et qui fait qu'on lit une marche plutot qu'un flottement.
const float LateralWave = FMath::Sin(StridePhase);
const float VerticalWave = FMath::Sin(StridePhase * 2.f);
// X reste nul : une composante avant-arriere donnerait un effet de zoom qui,
// en vue premiere personne, se lit comme du mal de mer plus que comme un pas.
LocationOffset = FVector(
0.f,
LateralWave * BlendedProfile.LateralAmount * Intensity,
VerticalWave * BlendedProfile.VerticalAmount * Intensity);
// Le roulis suit le balancement lateral : la tete penche du cote ou le corps
// se porte. Le tangage suit la montee-descente, et vaut 0 par defaut.
RotationOffset = FRotator(
VerticalWave * BlendedProfile.PitchAmount * Intensity,
0.f,
LateralWave * BlendedProfile.RollAmount * Intensity);
}
const FHeadBobProfile& UHeadBobComponent::SelectTargetProfile() const
{
if (OwnerMovement->IsFalling())
{
return AirProfile;
}
// Vitesse a plat : la composante verticale d'une pente ou d'un ascenseur n'a
// rien a voir avec la cadence des pas.
const float GroundSpeed = OwnerMovement->Velocity.Size2D();
// L'immobilite passe AVANT l'accroupissement : accroupi sans bouger, on
// respire, on ne marche pas sur place.
if (GroundSpeed < IdleSpeedThreshold)
{
return IdleProfile;
}
if (OwnerCharacter->bIsCrouched)
{
return CrouchProfile;
}
return (GroundSpeed > SprintSpeedThreshold) ? SprintProfile : WalkProfile;
}
void UHeadBobComponent::BlendTowards(const FHeadBobProfile& Target, float Step)
{
BlendedProfile.StrideFrequency = FMath::FInterpTo(BlendedProfile.StrideFrequency, Target.StrideFrequency, Step, ProfileBlendSpeed);
BlendedProfile.VerticalAmount = FMath::FInterpTo(BlendedProfile.VerticalAmount, Target.VerticalAmount, Step, ProfileBlendSpeed);
BlendedProfile.LateralAmount = FMath::FInterpTo(BlendedProfile.LateralAmount, Target.LateralAmount, Step, ProfileBlendSpeed);
BlendedProfile.RollAmount = FMath::FInterpTo(BlendedProfile.RollAmount, Target.RollAmount, Step, ProfileBlendSpeed);
BlendedProfile.PitchAmount = FMath::FInterpTo(BlendedProfile.PitchAmount, Target.PitchAmount, Step, ProfileBlendSpeed);
}
void UHeadBobComponent::ResetBob()
{
StridePhase = 0.f;
BlendedProfile = IdleProfile;
LocationOffset = FVector::ZeroVector;
RotationOffset = FRotator::ZeroRotator;
}
void UHeadBobComponent::SetIntensityScale(float NewScale)
{
IntensityScale = FMath::Max(NewScale, 0.f);
}