Import initial du projet Survival (UE 5.8)
Boucle de jeu complète : récolte, inventaire, consommation, stats de survie, mort et respawn. Gameplay en C++, Blueprints réservés au câblage d'assets. Assets binaires (.uasset, .umap, textures, audio) suivis via Git LFS. Binaries/, Intermediate/, Saved/ et DerivedDataCache/ sont ignorés : régénérés au build, ils pèsent 3 Go pour 1,3 Go de contenu utile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "FpsPlayer.h"
|
||||
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "InteractionComponent.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "ItemDataAsset.h"
|
||||
#include "PickupItem.h"
|
||||
#include "SurvivalStatsComponent.h"
|
||||
|
||||
// Sets default values
|
||||
AFpsPlayer::AFpsPlayer()
|
||||
{
|
||||
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// Gabarit du joueur : ~176 cm de haut, 34 cm de rayon
|
||||
UCapsuleComponent* Capsule = GetCapsuleComponent();
|
||||
Capsule->InitCapsuleSize(34.f, 88.f);
|
||||
|
||||
// La souris fait tourner le personnage horizontalement (yaw) ;
|
||||
// le pitch reste sur le controller et n'est applique qu'a la camera.
|
||||
bUseControllerRotationYaw = true;
|
||||
bUseControllerRotationPitch = false;
|
||||
bUseControllerRotationRoll = false;
|
||||
|
||||
// Camera a hauteur des yeux. La position est exprimee par rapport au centre
|
||||
// de la capsule, d'ou la soustraction de la demi-hauteur.
|
||||
FirstPersonCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
|
||||
FirstPersonCamera->SetupAttachment(Capsule);
|
||||
FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, EyeHeightStanding - Capsule->GetUnscaledCapsuleHalfHeight()));
|
||||
FirstPersonCamera->SetFieldOfView(BaseFOV);
|
||||
// On pilote la rotation nous-memes dans UpdateCameraEffects() : bUsePawnControlRotation
|
||||
// ecraserait la rotation chaque frame et tous les effets de tilt seraient invisibles.
|
||||
FirstPersonCamera->bUsePawnControlRotation = false;
|
||||
|
||||
InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("InteractionComponent"));
|
||||
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));
|
||||
SurvivalStats = CreateDefaultSubobject<USurvivalStatsComponent>(TEXT("SurvivalStats"));
|
||||
|
||||
// En vue FPS on ne veut pas voir son propre corps (mesh 3e personne),
|
||||
// mais on garde son ombre.
|
||||
GetMesh()->SetOwnerNoSee(true);
|
||||
GetMesh()->SetCastHiddenShadow(true);
|
||||
|
||||
UCharacterMovementComponent* Movement = GetCharacterMovement();
|
||||
Movement->MaxWalkSpeed = WalkSpeed;
|
||||
Movement->MaxWalkSpeedCrouched = CrouchSpeed;
|
||||
Movement->JumpZVelocity = 450.f;
|
||||
Movement->AirControl = 0.3f;
|
||||
Movement->BrakingDecelerationWalking = 2000.f;
|
||||
// 120 cm de haut accroupi (une vraie position accroupie, pas un rampant)
|
||||
Movement->SetCrouchedHalfHeight(60.f);
|
||||
// Indispensable, sinon Crouch() est ignore
|
||||
Movement->GetNavAgentPropertiesRef().bCanCrouch = true;
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AFpsPlayer::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);
|
||||
|
||||
// On enregistre le mapping context aupres du sous-systeme Enhanced Input
|
||||
// du joueur local. Sans ca, aucune touche n'est lue.
|
||||
if (const APlayerController* PlayerController = Cast<APlayerController>(GetController()))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
|
||||
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
|
||||
{
|
||||
if (DefaultMappingContext)
|
||||
{
|
||||
Subsystem->AddMappingContext(DefaultMappingContext, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DefaultMappingContext n'est pas assigne, aucun input ne fonctionnera."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AFpsPlayer::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// On memorise la vitesse de chute a chaque frame : au moment ou Landed()
|
||||
// est appele, la vitesse a deja pu etre remise a zero par le moteur.
|
||||
if (GetCharacterMovement()->IsFalling())
|
||||
{
|
||||
LastFallSpeed = GetCharacterMovement()->Velocity.Z;
|
||||
}
|
||||
|
||||
UpdateCameraEffects(DeltaTime);
|
||||
}
|
||||
|
||||
void AFpsPlayer::UpdateCameraEffects(float DeltaTime)
|
||||
{
|
||||
// Un gros hoquet de framerate (chargement, breakpoint) ferait diverger
|
||||
// l'integration du ressort ci-dessous. On plafonne le pas de temps.
|
||||
const float Step = FMath::Min(DeltaTime, 1.f / 30.f);
|
||||
|
||||
// ==================================================================
|
||||
// 1. Animation d'accroupissement
|
||||
// ==================================================================
|
||||
// CrouchAlpha va de 0 (debout) a 1 (accroupi) en CrouchTransitionDuration
|
||||
// secondes. Duree fixe plutot qu'un FInterpTo : le mouvement est identique
|
||||
// a chaque fois et se termine vraiment, au lieu de tendre asymptotiquement.
|
||||
if (!FMath::IsNearlyEqual(CrouchAlpha, CrouchTargetAlpha))
|
||||
{
|
||||
const float CrouchStep = Step / FMath::Max(CrouchTransitionDuration, KINDA_SMALL_NUMBER);
|
||||
CrouchAlpha = FMath::Clamp(CrouchAlpha + CrouchStep * FMath::Sign(CrouchTargetAlpha - CrouchAlpha), 0.f, 1.f);
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// 2. Ressort d'impact (saut, atterrissage, et tout AddCameraPunch)
|
||||
// ==================================================================
|
||||
// L'impulsion n'est pas versee d'un coup : la vitesse de la camera passerait
|
||||
// de 0 a plusieurs centaines de cm/s en une frame, et cette discontinuite se
|
||||
// lit comme un a-coup. On l'etale sur PunchImpulseRampTime.
|
||||
if (!FMath::IsNearlyZero(PendingPunch))
|
||||
{
|
||||
const float ReleaseRatio = (PunchImpulseRampTime > KINDA_SMALL_NUMBER)
|
||||
? FMath::Clamp(Step / PunchImpulseRampTime, 0.f, 1.f)
|
||||
: 1.f;
|
||||
const float Released = PendingPunch * ReleaseRatio;
|
||||
PunchVelocity += Released;
|
||||
PendingPunch -= Released;
|
||||
}
|
||||
|
||||
// Ressort amorti classique : acceleration = -k*x - c*v.
|
||||
// On exprime k et c a partir d'une frequence et d'un taux d'amortissement,
|
||||
// beaucoup plus parlants a regler qu'une raideur brute.
|
||||
const float Omega = 2.f * PI * PunchFrequency;
|
||||
const float Acceleration = -(Omega * Omega * PunchOffset) - (2.f * PunchDamping * Omega * PunchVelocity);
|
||||
PunchVelocity += Acceleration * Step;
|
||||
PunchOffset = FMath::Clamp(PunchOffset + PunchVelocity * Step, -PunchMaxOffset, PunchMaxOffset);
|
||||
|
||||
// ==================================================================
|
||||
// 3. 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.
|
||||
const bool bMovingFastEnough = GetCharacterMovement()->Velocity.SizeSquared2D() > FMath::Square(WalkSpeed * 0.9f);
|
||||
const float TargetFOV = BaseFOV + ((bIsSprinting && bMovingFastEnough) ? SprintFOVOffset : 0.f);
|
||||
FirstPersonCamera->SetFieldOfView(FMath::FInterpTo(FirstPersonCamera->FieldOfView, TargetFOV, Step, FOVInterpSpeed));
|
||||
|
||||
// ==================================================================
|
||||
// 4. Application : position
|
||||
// ==================================================================
|
||||
// SmoothStep donne une courbe en S : demarrage doux, acceleration, arrivee douce.
|
||||
const float Smoothed = FMath::SmoothStep(0.f, 1.f, CrouchAlpha);
|
||||
const float EyeHeightAboveFeet = FMath::Lerp(EyeHeightStanding, EyeHeightCrouched, Smoothed);
|
||||
|
||||
// Point cle : quand la capsule retrecit, le CharacterMovement redescend l'acteur
|
||||
// d'un coup pour garder les pieds au sol. La camera, attachee a la capsule,
|
||||
// suivait ce saut instantane -- c'est ce qui rendait le crouch "snap".
|
||||
// En raisonnant en hauteur d'oeil AU-DESSUS DES PIEDS et en soustrayant la
|
||||
// demi-hauteur courante de la capsule, ce saut est annule exactement.
|
||||
const float CapsuleHalfHeight = GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
|
||||
FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, EyeHeightAboveFeet - CapsuleHalfHeight + PunchOffset));
|
||||
|
||||
// ==================================================================
|
||||
// 5. Application : rotation
|
||||
// ==================================================================
|
||||
// sin(alpha * PI) vaut 0 aux deux extremites et 1 au milieu : le tilt monte
|
||||
// puis redescend tout seul pendant la transition, et est nul au repos.
|
||||
// CrouchTiltDirection inverse le sens selon qu'on descend ou qu'on remonte.
|
||||
const float CrouchTiltShape = FMath::Sin(CrouchAlpha * PI) * CrouchTiltDirection;
|
||||
|
||||
FRotator ViewOffset(-CrouchTiltPitch * CrouchTiltShape, 0.f, CrouchTiltRoll * CrouchTiltShape);
|
||||
// Le regard suit le deplacement vertical de la camera : elle descend, on
|
||||
// regarde vers le bas. C'est ce qui vend l'impact a l'atterrissage.
|
||||
ViewOffset.Pitch += PunchOffset * PunchPitchPerCm;
|
||||
|
||||
if (const AController* OwningController = GetController())
|
||||
{
|
||||
FirstPersonCamera->SetWorldRotation(OwningController->GetControlRotation() + ViewOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayer::AddCameraPunch(float Strength)
|
||||
{
|
||||
// Mis en attente plutot qu'applique directement : c'est UpdateCameraEffects
|
||||
// qui l'injecte progressivement dans le ressort.
|
||||
PendingPunch += Strength;
|
||||
}
|
||||
|
||||
void AFpsPlayer::Landed(const FHitResult& Hit)
|
||||
{
|
||||
Super::Landed(Hit);
|
||||
|
||||
// L'amplitude suit la vitesse de chute : une marche d'escalier ne doit pas
|
||||
// secouer autant qu'une chute de dix metres.
|
||||
const float FallSpeed = FMath::Abs(LastFallSpeed);
|
||||
if (FallSpeed > LandPunchMinFallSpeed)
|
||||
{
|
||||
const float Range = FMath::Max(LandPunchMaxFallSpeed - LandPunchMinFallSpeed, 1.f);
|
||||
const float Ratio = FMath::Clamp((FallSpeed - LandPunchMinFallSpeed) / Range, 0.f, 1.f);
|
||||
AddCameraPunch(-LandPunchStrength * Ratio);
|
||||
|
||||
// Aide au reglage : montre la vitesse d'impact reelle et l'amplitude
|
||||
// obtenue. Supprime ce log quand le ressenti te convient.
|
||||
UE_LOG(LogTemp, Log, TEXT("Atterrissage : chute %.0f cm/s, ratio %.2f, impulsion %.0f cm/s (~%.1f cm de camera)"),
|
||||
FallSpeed, Ratio, LandPunchStrength * Ratio, LandPunchStrength * Ratio * 0.018f);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Atterrissage ignore : chute %.0f cm/s, sous le seuil de %.0f cm/s"),
|
||||
FallSpeed, LandPunchMinFallSpeed);
|
||||
}
|
||||
|
||||
LastFallSpeed = 0.f;
|
||||
}
|
||||
|
||||
void AFpsPlayer::OnJumped_Implementation()
|
||||
{
|
||||
Super::OnJumped_Implementation();
|
||||
|
||||
AddCameraPunch(-JumpPunchStrength);
|
||||
}
|
||||
|
||||
// Called to bind functionality to input
|
||||
void AFpsPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||
|
||||
UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
|
||||
if (!Input)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : l'input component n'est pas un UEnhancedInputComponent."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (MoveAction)
|
||||
{
|
||||
Input->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AFpsPlayer::Move);
|
||||
}
|
||||
|
||||
if (LookAction)
|
||||
{
|
||||
Input->BindAction(LookAction, ETriggerEvent::Triggered, this, &AFpsPlayer::Look);
|
||||
}
|
||||
|
||||
if (JumpAction)
|
||||
{
|
||||
// Jump() et StopJumping() viennent directement de ACharacter
|
||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
|
||||
Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
|
||||
}
|
||||
|
||||
if (SprintAction)
|
||||
{
|
||||
Input->BindAction(SprintAction, ETriggerEvent::Started, this, &AFpsPlayer::StartSprint);
|
||||
Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::StopSprint);
|
||||
}
|
||||
|
||||
if (CrouchAction)
|
||||
{
|
||||
Input->BindAction(CrouchAction, ETriggerEvent::Started, this, &AFpsPlayer::StartCrouch);
|
||||
Input->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AFpsPlayer::StopCrouch);
|
||||
}
|
||||
|
||||
if (InteractAction)
|
||||
{
|
||||
Input->BindAction(InteractAction, ETriggerEvent::Started, this, &AFpsPlayer::Interact);
|
||||
}
|
||||
|
||||
if (UseItemAction)
|
||||
{
|
||||
Input->BindAction(UseItemAction, ETriggerEvent::Started, this, &AFpsPlayer::UseSelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayer::Move(const FInputActionValue& Value)
|
||||
{
|
||||
const FVector2D Axis = Value.Get<FVector2D>();
|
||||
if (Axis.IsNearlyZero() || !Controller)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// On se deplace par rapport a la direction du regard, mais a plat :
|
||||
// on ne garde que le yaw pour ne pas "voler" en regardant vers le haut.
|
||||
const FRotator YawRotation(0.f, Controller->GetControlRotation().Yaw, 0.f);
|
||||
const FVector Forward = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
const FVector Right = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||
|
||||
AddMovementInput(Forward, Axis.Y);
|
||||
AddMovementInput(Right, Axis.X);
|
||||
}
|
||||
|
||||
void AFpsPlayer::Look(const FInputActionValue& Value)
|
||||
{
|
||||
// Si IA_Look n'est pas en Axis2D, Get<FVector2D>() renvoie toujours Y = 0
|
||||
// et le regard reste bloque sur l'axe horizontal. On le signale une fois.
|
||||
if (Value.GetValueType() != EInputActionValueType::Axis2D && !bLoggedLookTypeWarning)
|
||||
{
|
||||
bLoggedLookTypeWarning = true;
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("AFpsPlayer : IA_Look doit avoir Value Type = Axis2D (Vector2D). ")
|
||||
TEXT("Avec un autre type, l'axe vertical est ignore."));
|
||||
}
|
||||
|
||||
const FVector2D Axis = Value.Get<FVector2D>();
|
||||
|
||||
// Pas de DeltaTime ici : la souris fournit un delta, pas une vitesse.
|
||||
// Le multiplier par DeltaTime rendrait la visee dependante du framerate.
|
||||
AddControllerYawInput(Axis.X * LookSensitivityX);
|
||||
AddControllerPitchInput(Axis.Y * LookSensitivityY * (bInvertLookY ? -1.f : 1.f));
|
||||
}
|
||||
|
||||
void AFpsPlayer::StartSprint()
|
||||
{
|
||||
// Pas de sprint accroupi : on se releve d'abord
|
||||
if (bIsCrouched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bIsSprinting = true;
|
||||
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
|
||||
}
|
||||
|
||||
void AFpsPlayer::StopSprint()
|
||||
{
|
||||
bIsSprinting = false;
|
||||
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
|
||||
}
|
||||
|
||||
void AFpsPlayer::UseSelectedItem()
|
||||
{
|
||||
const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
|
||||
if (Selected.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const UItemDataAsset* Item = Selected.Item;
|
||||
|
||||
// Objet non consommable : on ne fait rien pour l'instant. C'est ici que
|
||||
// viendront les outils (frapper), les armes (tirer), les placeables.
|
||||
if (!Item->bIsConsumable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Les valeurs de restauration sont appliquees a CHAQUE utilisation.
|
||||
SurvivalStats->ApplyRestore(Item->HealthRestore, Item->HungerRestore, Item->ThirstRestore);
|
||||
|
||||
// ConsumeUse gere les deux cas : usage unique (l'exemplaire disparait) et
|
||||
// usages multiples (une charge en moins, l'objet reste tant qu'il en a).
|
||||
// On vise le slot actif precisement, et pas RemoveItem qui piocherait
|
||||
// n'importe quelle pile du meme objet, y compris dans le sac.
|
||||
InventoryComponent->ConsumeUse(InventoryComponent->GetSelectedHotbarIndex());
|
||||
}
|
||||
|
||||
FVector AFpsPlayer::PlaceOnGround(const FVector& Start) const
|
||||
{
|
||||
// APickupItem ne simule pas la physique : sans ce trace vers le bas,
|
||||
// l'objet resterait suspendu en l'air la ou on l'a lache.
|
||||
FCollisionQueryParams Params(SCENE_QUERY_STAT(DropGroundTrace), /*bTraceComplex=*/false, this);
|
||||
|
||||
FHitResult GroundHit;
|
||||
if (GetWorld()->LineTraceSingleByChannel(GroundHit, Start, Start - FVector(0.f, 0.f, 500.f), ECC_Visibility, Params))
|
||||
{
|
||||
return GroundHit.ImpactPoint + FVector(0.f, 0.f, DropGroundOffset);
|
||||
}
|
||||
|
||||
return Start;
|
||||
}
|
||||
|
||||
APickupItem* AFpsPlayer::SpawnPickup(const FInventorySlot& Slot, const FVector& Location)
|
||||
{
|
||||
if (!DroppedPickupClass || Slot.IsEmpty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// SpawnActorDeferred et non SpawnActor : il faut poser ItemData AVANT que
|
||||
// OnConstruction et BeginPlay tournent, sinon le pickup apparait sans mesh
|
||||
// et son CanInteract le rendrait inerte.
|
||||
const FTransform SpawnTransform(FRotator(0.f, GetActorRotation().Yaw, 0.f), Location);
|
||||
|
||||
APickupItem* Dropped = GetWorld()->SpawnActorDeferred<APickupItem>(
|
||||
DroppedPickupClass, SpawnTransform, this, nullptr,
|
||||
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
|
||||
|
||||
if (!Dropped)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Dropped->InitPickup(Slot.Item, Slot.Quantity, Slot.RemainingUses);
|
||||
Dropped->FinishSpawning(SpawnTransform);
|
||||
|
||||
return Dropped;
|
||||
}
|
||||
|
||||
bool AFpsPlayer::DropSlot(int32 SlotIndex, int32 Quantity)
|
||||
{
|
||||
if (!DroppedPickupClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, impossible de jeter un objet."));
|
||||
return false;
|
||||
}
|
||||
|
||||
FInventorySlot Taken;
|
||||
if (!InventoryComponent->TakeFromSlot(SlotIndex, Quantity, Taken) || Taken.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FVector EyeLocation = FirstPersonCamera->GetComponentLocation();
|
||||
const FVector Forward = FirstPersonCamera->GetForwardVector();
|
||||
FVector DropLocation = EyeLocation + Forward * DropDistance;
|
||||
|
||||
// Un mur devant nous : on depose juste avant, sinon l'objet finirait
|
||||
// de l'autre cote de la cloison et deviendrait inatteignable.
|
||||
FCollisionQueryParams Params(SCENE_QUERY_STAT(DropItemTrace), /*bTraceComplex=*/false, this);
|
||||
FHitResult WallHit;
|
||||
if (GetWorld()->LineTraceSingleByChannel(WallHit, EyeLocation, DropLocation, ECC_Visibility, Params))
|
||||
{
|
||||
DropLocation = WallHit.ImpactPoint - Forward * 20.f;
|
||||
}
|
||||
|
||||
if (!SpawnPickup(Taken, PlaceOnGround(DropLocation)))
|
||||
{
|
||||
// On ne perd pas l'objet : il retourne dans l'inventaire, usure comprise.
|
||||
InventoryComponent->AddItem(Taken.Item, Taken.Quantity, Taken.RemainingUses);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AFpsPlayer::DropAllItems()
|
||||
{
|
||||
if (!DroppedPickupClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, le butin est perdu."));
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 SlotCount = InventoryComponent->GetSlots().Num();
|
||||
const FVector Center = GetActorLocation();
|
||||
|
||||
int32 Scattered = 0;
|
||||
for (int32 Index = 0; Index < SlotCount; ++Index)
|
||||
{
|
||||
FInventorySlot Taken;
|
||||
// TakeFromSlot renvoie false sur un slot vide : pas besoin de tester.
|
||||
if (!InventoryComponent->TakeFromSlot(Index, MAX_int32, Taken) || Taken.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Angle d'or : repartit les objets en spirale reguliere autour du
|
||||
// corps, sans qu'ils se superposent ni forment de motif visible.
|
||||
const float Angle = FMath::DegreesToRadians(Scattered * 137.5f);
|
||||
const float Radius = 40.f + Scattered * 4.f;
|
||||
const FVector Offset(FMath::Cos(Angle) * Radius, FMath::Sin(Angle) * Radius, 0.f);
|
||||
|
||||
SpawnPickup(Taken, PlaceOnGround(Center + Offset));
|
||||
++Scattered;
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("AFpsPlayer : %d piles laissees au sol a la mort."), Scattered);
|
||||
}
|
||||
|
||||
void AFpsPlayer::HandleDeath()
|
||||
{
|
||||
if (bIsDead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bIsDead = true;
|
||||
|
||||
// Tout tombe sur place. On le fait AVANT de couper les inputs : l'ordre
|
||||
// n'a pas d'importance fonctionnelle, mais perdre le butin en silence
|
||||
// serait le pire bug possible dans un jeu de survie.
|
||||
DropAllItems();
|
||||
|
||||
ClearTransientInputStates();
|
||||
GetCharacterMovement()->DisableMovement();
|
||||
|
||||
if (APlayerController* OwningController = Cast<APlayerController>(GetController()))
|
||||
{
|
||||
DisableInput(OwningController);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayer::ClearTransientInputStates()
|
||||
{
|
||||
StopSprint();
|
||||
StopJumping();
|
||||
|
||||
// L'accroupissement n'est volontairement pas annule : c'est un etat
|
||||
// visible que le joueur maitrise, et il se retablit tout seul au
|
||||
// prochain appui sur la touche.
|
||||
}
|
||||
|
||||
void AFpsPlayer::StartCrouch()
|
||||
{
|
||||
StopSprint();
|
||||
Crouch();
|
||||
}
|
||||
|
||||
void AFpsPlayer::StopCrouch()
|
||||
{
|
||||
// UnCrouch() echoue silencieusement s'il y a un plafond : le perso
|
||||
// reste accroupi jusqu'a ce que la place se libere.
|
||||
UnCrouch();
|
||||
}
|
||||
|
||||
void AFpsPlayer::Interact()
|
||||
{
|
||||
InteractionComponent->TryInteract();
|
||||
}
|
||||
|
||||
void AFpsPlayer::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
|
||||
{
|
||||
Super::OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
|
||||
|
||||
CrouchTargetAlpha = 1.f;
|
||||
CrouchTiltDirection = 1.f;
|
||||
}
|
||||
|
||||
void AFpsPlayer::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
|
||||
{
|
||||
Super::OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
|
||||
|
||||
CrouchTargetAlpha = 0.f;
|
||||
CrouchTiltDirection = -1.f;
|
||||
}
|
||||
Reference in New Issue
Block a user