// 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(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); }