(Feat) Add Animation

This commit is contained in:
2026-08-06 13:27:21 +02:00
parent fca1b271f1
commit 72a34f6e40
64 changed files with 1943 additions and 83 deletions
+95 -6
View File
@@ -3,6 +3,8 @@
#include "FpsPlayer.h"
#include "Animation/AnimInstance.h"
#include "Animation/AnimMontage.h"
#include "Camera/CameraComponent.h"
#include "Camera/CameraTypes.h"
#include "Components/CapsuleComponent.h"
@@ -14,11 +16,13 @@
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "CraftingComponent.h"
#include "HeadBobComponent.h"
#include "InteractionComponent.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "Net/UnrealNetwork.h"
#include "PickupItem.h"
#include "SurvivalGameInstance.h"
#include "SurvivalStatsComponent.h"
#include "SurvivalUserSettings.h"
@@ -74,6 +78,31 @@ AFpsPlayer::AFpsPlayer()
// ecraserait la rotation chaque frame et tous les effets de tilt seraient invisibles.
FirstPersonCamera->bUsePawnControlRotation = false;
// Bras vue premiere personne, accroches a la camera pour suivre le regard.
FirstPersonArms = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonArms"));
FirstPersonArms->SetupAttachment(FirstPersonCamera);
// Symetrique exact du mesh 3e personne ci-dessous : chacun ne voit que ses
// propres bras. Sans ca, les bras d'un ami flotteraient devant notre objectif.
FirstPersonArms->SetOnlyOwnerSee(true);
// Un mesh colle a l'objectif projetterait une ombre enorme en travers du
// decor. L'ombre du joueur, elle, vient du mesh 3e personne.
FirstPersonArms->SetCastShadow(false);
FirstPersonArms->bCastDynamicShadow = false;
// Les bras ne sont pas dans le monde, ils sont devant l'objectif : une
// collision les ferait pousser des objets et surtout bloquer le trace
// d'interaction, qui part exactement d'entre eux.
FirstPersonArms->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Chez l'hote, le pawn de chaque ami porte AUSSI ce composant, invisible pour
// tout le monde. Sans cette option le serveur evaluerait quatre AnimBlueprints
// pour quatre meshes que personne ne rend.
FirstPersonArms->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
// Point de depart a affiner dans BP_FpsPlayer : la bonne valeur depend de
// l'origine du FBX (racine aux pieds ? au bassin ?) et de l'orientation
// d'export. Le yaw de -90 est le decalage habituel d'un FBX Blender/Maya.
FirstPersonArms->SetRelativeLocationAndRotation(FVector(0.f, 0.f, -160.f), FRotator(0.f, -90.f, 0.f));
HeadBob = CreateDefaultSubobject<UHeadBobComponent>(TEXT("HeadBob"));
InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("InteractionComponent"));
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));
CraftingComponent = CreateDefaultSubobject<UCraftingComponent>(TEXT("CraftingComponent"));
@@ -131,10 +160,18 @@ void AFpsPlayer::BeginPlay()
{
if (DefaultMappingContext)
{
// On ACTIVE le contexte ici, mais on ne le DECLARE pas comme
// remappable : c'est USurvivalGameInstance qui s'en charge, parce
// que le menu principal n'a pas de pawn et doit quand meme
// afficher les touches.
// La DECLARATION des contextes remappables appartient au
// GameInstance -- le menu principal n'a pas de pawn et doit
// pourtant afficher les touches. On la demande quand meme ici, et
// AVANT d'activer le contexte : c'est elle qui traduit les touches
// vers la disposition clavier du joueur, et un AZERTY doit trouver
// ses touches des la premiere image, pas au premier passage dans
// l'ecran de reglages. L'appel est idempotent.
if (USurvivalGameInstance* GameInstance = GetGameInstance<USurvivalGameInstance>())
{
GameInstance->RegisterMappableContexts();
}
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
else
@@ -176,6 +213,10 @@ void AFpsPlayer::ApplyUserSettings()
bSprintToggleMode = Settings->GetSprintToggle();
bCrouchToggleMode = Settings->GetCrouchToggle();
// Le curseur "secousses de camera" vaut aussi pour le balancement : un joueur
// qui le met a zero demande une camera fixe, pas une camera fixe qui respire.
HeadBob->SetIntensityScale(CameraShakeMultiplier);
// Repasser en maintien alors que la touche n'est plus enfoncee laisserait le
// personnage bloque en sprint : on repart d'un etat propre.
if (!bSprintToggleMode && bIsSprinting)
@@ -228,7 +269,15 @@ void AFpsPlayer::UpdateCameraEffects(float DeltaTime)
CrouchTiltSpring.Update(Step, CrouchTiltFrequency, CrouchTiltDamping, CrouchTiltRampTime, 1.5f);
// ==================================================================
// 3. FOV de sprint
// 3. Balancement de tete
// ==================================================================
// Integre ici et applique dans ApplyCameraTransform, exactement comme les
// ressorts ci-dessus : c'est ce qui permet a CalcCamera de recomposer la
// transform sans faire avancer le temps deux fois.
HeadBob->UpdateBob(Step);
// ==================================================================
// 4. FOV de sprint
// ==================================================================
// On exige un vrai deplacement : sinon le FOV s'ouvrirait juste en
// maintenant Shift a l'arret, ce qui donne un effet de pompage bizarre.
@@ -257,7 +306,18 @@ void AFpsPlayer::ApplyCameraTransform()
// demi-hauteur courante de la capsule, ce saut est annule exactement --
// a condition de lire les deux au meme instant, d'ou l'appel depuis CalcCamera.
const float CapsuleHalfHeight = GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, EyeHeightAboveFeet - CapsuleHalfHeight + PunchSpring.Offset));
// Le balancement s'AJOUTE au reste. Son Y est un decalage lateral dans le
// repere de la capsule, dont le yaw suit deja le regard (bUseControllerRotationYaw) :
// la tete se porte donc bien a droite de ce qu'on regarde, sans conversion.
// Le tangage, lui, n'est pas dans la capsule -- c'est voulu, le balancement
// reste horizontal meme quand on regarde ses pieds.
const FVector BobOffset = HeadBob->GetLocationOffset();
FirstPersonCamera->SetRelativeLocation(FVector(
BobOffset.X,
BobOffset.Y,
EyeHeightAboveFeet - CapsuleHalfHeight + PunchSpring.Offset + BobOffset.Z));
// ==================================================================
// Rotation
@@ -274,6 +334,9 @@ void AFpsPlayer::ApplyCameraTransform()
// regarde vers le bas. C'est ce qui vend l'impact a l'atterrissage.
ViewOffset.Pitch += PunchSpring.Offset * PunchPitchPerCm;
// Roulis et tangage du balancement, dans le meme sac que le reste.
ViewOffset += HeadBob->GetRotationOffset();
if (const AController* OwningController = GetController())
{
FirstPersonCamera->SetWorldRotation(OwningController->GetControlRotation() + ViewOffset);
@@ -812,9 +875,35 @@ void AFpsPlayer::StopCrouch()
void AFpsPlayer::Interact()
{
// Le geste part AVANT la RPC, pas apres sa reponse : un montage declenche a
// l'aller-retour serveur donnerait des bras qui repondent 60 ms apres le clic,
// et le joueur lirait ca comme un jeu qui rame. Le prix a payer est un geste
// dans le vide si le serveur refuse l'interaction -- infiniment moins genant.
//
// Le test de type est ici et pas dans le composant d'interaction : celui-ci ne
// connait que l'interface IInteractable et n'a pas a apprendre ce qu'est un
// pickup. Un coffre ou un etabli n'ont pas a declencher un geste de ramassage.
if (Cast<APickupItem>(InteractionComponent->GetFocusedActor()))
{
PlayArmsMontage(GrabMontage);
}
InteractionComponent->TryInteract();
}
float AFpsPlayer::PlayArmsMontage(UAnimMontage* Montage, float PlayRate)
{
// IsLocallyControlled : chez l'hote, le pawn de chaque ami passerait aussi par
// ici a chaque ramassage et animerait un mesh que personne ne rend.
if (!Montage || !IsLocallyControlled())
{
return 0.f;
}
UAnimInstance* ArmsAnim = FirstPersonArms->GetAnimInstance();
return ArmsAnim ? ArmsAnim->Montage_Play(Montage, PlayRate) : 0.f;
}
void AFpsPlayer::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
{
Super::OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);