41d6e6a7f3
Coffres : AStorageContainer, un UInventoryComponent configure en conteneur, affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui. Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo. Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple (inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee sous le curseur se voit. Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme, sinon passer de la barre a la grille viderait ce qui vient d'etre affiche. L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory etant instancie deux fois en mode coffre, une case a cocher par instance serait un bug qui attend qu'on oublie de la decocher. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
225 lines
6.4 KiB
C++
225 lines
6.4 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "StorageContainer.h"
|
|
|
|
#include "Components/BoxComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "FpsPlayerController.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "InventoryComponent.h"
|
|
#include "ItemDataAsset.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "StorageContainer"
|
|
|
|
AStorageContainer::AStorageContainer()
|
|
{
|
|
// Le tick n'existe que pour animer le couvercle : il demarre eteint et ne
|
|
// s'allume que le temps du mouvement.
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
PrimaryActorTick.bStartWithTickEnabled = false;
|
|
|
|
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh"));
|
|
SetRootComponent(BaseMesh);
|
|
|
|
// Les meshes ne portent AUCUNE collision : elle vit entierement dans la
|
|
// boite. Un mesh de pack sans collision simple rendrait le coffre muet sans
|
|
// qu'aucune erreur ne le signale, et celle du couvercle partirait avec lui
|
|
// des l'ouverture.
|
|
BaseMesh->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
|
|
InteractionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("InteractionBox"));
|
|
InteractionBox->SetupAttachment(BaseMesh);
|
|
|
|
// Explicite plutot que de compter sur le profil par defaut : ce composant a
|
|
// deux roles indissociables, empecher le joueur de traverser le coffre et
|
|
// bloquer le canal Visibility pour que le trace d'interaction le trouve.
|
|
// Un profil qui laisserait passer Visibility rendrait le coffre inutilisable
|
|
// sans qu'aucune erreur ne le signale.
|
|
InteractionBox->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
|
|
|
|
// Valeur de depart pour un coffre qui n'aurait pas encore de mesh :
|
|
// FitInteractionBox() l'ecrase des qu'il y en a un.
|
|
InteractionBox->SetBoxExtent(FVector(40.f, 30.f, 30.f));
|
|
|
|
LidPivot = CreateDefaultSubobject<USceneComponent>(TEXT("LidPivot"));
|
|
LidPivot->SetupAttachment(BaseMesh);
|
|
|
|
LidMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LidMesh"));
|
|
LidMesh->SetupAttachment(LidPivot);
|
|
LidMesh->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
|
|
Storage = CreateDefaultSubobject<UInventoryComponent>(TEXT("Storage"));
|
|
|
|
DisplayName = LOCTEXT("ChestName", "Chest");
|
|
InteractionPrompt = LOCTEXT("OpenChest", "Ouvrir le coffre");
|
|
}
|
|
|
|
void AStorageContainer::PreInitializeComponents()
|
|
{
|
|
// Avant InitializeComponents(), donc avant que le composant fige sa taille
|
|
// par defaut de 30 slots avec une barre rapide.
|
|
if (Storage)
|
|
{
|
|
Storage->ConfigureAsContainer(SlotCount);
|
|
}
|
|
|
|
Super::PreInitializeComponents();
|
|
}
|
|
|
|
void AStorageContainer::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// Le couvercle repose a zero, quelle que soit la rotation laissee dans le
|
|
// Blueprint : le repos EST la reference de l'animation.
|
|
LidAlpha = 0.f;
|
|
ApplyLidRotation();
|
|
|
|
if (!Storage)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (const FStorageStartingItem& Entry : StartingItems)
|
|
{
|
|
if (!Entry.Item || Entry.Quantity <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 Left = Storage->AddItem(Entry.Item, Entry.Quantity);
|
|
if (Left > 0)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("%s : %d x %s n'ont pas tenu dans le coffre (%d cases)."),
|
|
*GetName(), Left, *GetNameSafe(Entry.Item), SlotCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AStorageContainer::OnConstruction(const FTransform& Transform)
|
|
{
|
|
Super::OnConstruction(Transform);
|
|
|
|
// Tourne aussi dans l'editeur : la boite se recale a l'instant ou tu
|
|
// changes de mesh, sans avoir a lancer le jeu pour t'en assurer.
|
|
if (bAutoFitInteractionBox)
|
|
{
|
|
FitInteractionBox();
|
|
}
|
|
}
|
|
|
|
void AStorageContainer::FitInteractionBox()
|
|
{
|
|
if (!InteractionBox || !BaseMesh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Volume combine des deux meshes, exprime dans le repere de la caisse --
|
|
// celui de la boite, qui en est fille.
|
|
FBox Combined(ForceInit);
|
|
|
|
const UStaticMeshComponent* Meshes[] = { BaseMesh, LidMesh };
|
|
for (const UStaticMeshComponent* Component : Meshes)
|
|
{
|
|
const UStaticMesh* Mesh = Component ? Component->GetStaticMesh() : nullptr;
|
|
if (!Mesh)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Transform RELATIVE a la racine et non absolue : la boite doit rester
|
|
// juste quel que soit l'endroit ou l'acteur est pose dans le niveau.
|
|
const FTransform Relative = Component->GetComponentTransform().GetRelativeTransform(BaseMesh->GetComponentTransform());
|
|
Combined += Mesh->GetBoundingBox().TransformBy(Relative);
|
|
}
|
|
|
|
if (!Combined.IsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InteractionBox->SetRelativeLocation(Combined.GetCenter());
|
|
InteractionBox->SetBoxExtent(Combined.GetExtent() + FVector(InteractionBoxPadding));
|
|
}
|
|
|
|
FText AStorageContainer::GetInteractionPrompt_Implementation(AActor* Interactor) const
|
|
{
|
|
return InteractionPrompt;
|
|
}
|
|
|
|
void AStorageContainer::Interact_Implementation(AActor* Interactor)
|
|
{
|
|
// L'ecran vit sur le controller, pas sur le pawn : c'est lui qu'on cherche.
|
|
const APawn* InteractingPawn = Cast<APawn>(Interactor);
|
|
AFpsPlayerController* PlayerController = InteractingPawn ? Cast<AFpsPlayerController>(InteractingPawn->GetController()) : nullptr;
|
|
|
|
if (!PlayerController)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PlayerController->OpenStorage(this);
|
|
}
|
|
|
|
void AStorageContainer::SetOpen(bool bInOpen)
|
|
{
|
|
if (bOpen == bInOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bOpen = bInOpen;
|
|
|
|
if (USoundBase* Sound = bOpen ? OpenSound : CloseSound)
|
|
{
|
|
UGameplayStatics::PlaySoundAtLocation(this, Sound, GetActorLocation());
|
|
}
|
|
|
|
// Le tick s'eteint tout seul en fin de course. Le rallumer ici suffit, y
|
|
// compris si l'animation precedente n'etait pas terminee : l'alpha repart
|
|
// de sa valeur courante, donc refermer un coffre a moitie ouvert ne saute
|
|
// pas.
|
|
SetActorTickEnabled(true);
|
|
}
|
|
|
|
void AStorageContainer::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
const float Target = bOpen ? 1.f : 0.f;
|
|
const float Speed = 1.f / FMath::Max(0.05f, LidDuration);
|
|
|
|
LidAlpha = FMath::FInterpConstantTo(LidAlpha, Target, DeltaTime, Speed);
|
|
ApplyLidRotation();
|
|
|
|
if (FMath::IsNearlyEqual(LidAlpha, Target))
|
|
{
|
|
// On force la valeur exacte avant de se rendormir : sans ca le couvercle
|
|
// resterait a un poil de sa position finale pour le reste de la partie.
|
|
LidAlpha = Target;
|
|
ApplyLidRotation();
|
|
SetActorTickEnabled(false);
|
|
}
|
|
}
|
|
|
|
void AStorageContainer::ApplyLidRotation()
|
|
{
|
|
if (!LidPivot)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Lissage aux extremites : un couvercle a vitesse constante demarre et
|
|
// s'arrete d'un coup, ce qui se lit comme une saccade sur un objet lourd.
|
|
const float Eased = FMath::SmoothStep(0.f, 1.f, LidAlpha);
|
|
|
|
LidPivot->SetRelativeRotation(OpenRotation * Eased);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|