(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "InventoryWidget.h"
|
||||
|
||||
#include "Components/UniformGridPanel.h"
|
||||
#include "Components/UniformGridSlot.h"
|
||||
#include "FpsPlayer.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "InventoryDragDropOperation.h"
|
||||
#include "InventorySlotWidget.h"
|
||||
#include "ItemDetailsWidget.h"
|
||||
|
||||
void UInventoryWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
// Une grille de coffre n'a rien a chercher toute seule : son contenu lui
|
||||
// est donne par l'ecran, apres coup.
|
||||
if (bAutoBindToPawn)
|
||||
{
|
||||
BindToOwningPawn();
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::BindToOwningPawn()
|
||||
{
|
||||
UInventoryComponent* NewInventory = ResolveInventory();
|
||||
if (!NewInventory)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : aucun UInventoryComponent trouve sur le pawn possede."));
|
||||
}
|
||||
|
||||
BindToInventory(NewInventory);
|
||||
}
|
||||
|
||||
void UInventoryWidget::BindToInventory(UInventoryComponent* InInventory)
|
||||
{
|
||||
if (BoundInventory.Get() == InInventory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// On annonce la sortie de survol AVANT de changer d'inventaire : le panneau
|
||||
// de detail a besoin de savoir QUEL affichage devient perime, et une fois
|
||||
// BoundInventory reaffecte cette information est perdue.
|
||||
SetHoveredSlot(INDEX_NONE);
|
||||
|
||||
if (BoundInventory.IsValid())
|
||||
{
|
||||
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged);
|
||||
}
|
||||
|
||||
BoundInventory = InInventory;
|
||||
|
||||
if (BoundInventory.IsValid())
|
||||
{
|
||||
BoundInventory->OnInventoryChanged.AddDynamic(this, &UInventoryWidget::HandleInventoryChanged);
|
||||
}
|
||||
|
||||
// Les cases gardent l'ancien composant en memoire : on force leur
|
||||
// recreation en vidant la grille.
|
||||
ClearSlotWidgets();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UInventoryWidget::NativeDestruct()
|
||||
{
|
||||
if (BoundInventory.IsValid())
|
||||
{
|
||||
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged);
|
||||
}
|
||||
BoundInventory.Reset();
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
UInventoryComponent* UInventoryWidget::ResolveInventory() const
|
||||
{
|
||||
const APlayerController* OwningController = GetOwningPlayer();
|
||||
AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
|
||||
return Player ? Player->GetInventoryComponent() : nullptr;
|
||||
}
|
||||
|
||||
void UInventoryWidget::HandleInventoryChanged()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UInventoryWidget::SetQuickTransferTarget(UInventoryComponent* InTarget)
|
||||
{
|
||||
QuickTransferTarget = InTarget;
|
||||
|
||||
// Les cases deja creees ne reliront rien d'elles-memes : on leur pousse.
|
||||
for (UInventorySlotWidget* SlotWidget : SlotWidgets)
|
||||
{
|
||||
if (SlotWidget)
|
||||
{
|
||||
SlotWidget->SetQuickTransferTarget(InTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::SetColumnCount(int32 NewColumnCount)
|
||||
{
|
||||
const int32 Clamped = FMath::Max(1, NewColumnCount);
|
||||
if (ColumnCount == Clamped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ColumnCount = Clamped;
|
||||
|
||||
// La position d'une case dans le UniformGrid est figee a son ajout : on
|
||||
// repart des cases plutot que de recalculer chaque UUniformGridSlot. Ca
|
||||
// n'arrive qu'a l'ouverture d'un coffre, pas a chaque rafraichissement.
|
||||
ClearSlotWidgets();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void UInventoryWidget::NotifySlotHovered(int32 Index, bool bHovered)
|
||||
{
|
||||
if (bHovered)
|
||||
{
|
||||
SetHoveredSlot(Index);
|
||||
}
|
||||
else if (HoveredSlotIndex == Index)
|
||||
{
|
||||
// Le test d'egalite evite d'effacer le survol quand les evenements
|
||||
// arrivent dans l'ordre "entre dans B" puis "sort de A".
|
||||
SetHoveredSlot(INDEX_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::SetHoveredSlot(int32 NewIndex)
|
||||
{
|
||||
if (HoveredSlotIndex == NewIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 PreviousIndex = HoveredSlotIndex;
|
||||
HoveredSlotIndex = NewIndex;
|
||||
|
||||
if (!ItemDetailsPanel || !bItemDetailsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HoveredSlotIndex != INDEX_NONE)
|
||||
{
|
||||
ItemDetailsPanel->ShowSlot(BoundInventory.Get(), HoveredSlotIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On efface la case qu'on QUITTE, pas le panneau : passer d'une case de
|
||||
// la barre rapide a une case de la grille declenche "entre" puis "sort",
|
||||
// et un effacement inconditionnel viderait ce qui vient d'etre affiche.
|
||||
ItemDetailsPanel->ClearSlot(BoundInventory.Get(), PreviousIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::NotifyExternalSlotHovered(UInventoryComponent* InInventory, int32 InSlotIndex, bool bHovered)
|
||||
{
|
||||
if (!ItemDetailsPanel || !bItemDetailsEnabled || InSlotIndex == INDEX_NONE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bHovered)
|
||||
{
|
||||
ItemDetailsPanel->ShowSlot(InInventory, InSlotIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemDetailsPanel->ClearSlot(InInventory, InSlotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::UpdateItemDetails()
|
||||
{
|
||||
if (!ItemDetailsPanel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bItemDetailsEnabled || HoveredSlotIndex == INDEX_NONE)
|
||||
{
|
||||
ItemDetailsPanel->Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemDetailsPanel->ShowSlot(BoundInventory.Get(), HoveredSlotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::SetItemDetailsEnabled(bool bEnabled)
|
||||
{
|
||||
bItemDetailsEnabled = bEnabled;
|
||||
|
||||
if (ItemDetailsPanel)
|
||||
{
|
||||
// On repose la visibilite a chaque appel, meme quand la valeur ne change
|
||||
// pas : c'est ce qui evite de dependre de ce qui a ete regle dans la
|
||||
// Designer, exactement comme le HideStorage() de l'ecran.
|
||||
// Collapsed et non Hidden -- coupe, le panneau ne doit pas non plus
|
||||
// reserver sa largeur, sinon la grille trainerait une colonne vide.
|
||||
ItemDetailsPanel->SetVisibility(bEnabled ? ESlateVisibility::SelfHitTestInvisible : ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
UpdateItemDetails();
|
||||
}
|
||||
|
||||
bool UInventoryWidget::GetHoveredSlot(UInventoryComponent*& OutInventory, int32& OutIndex) const
|
||||
{
|
||||
OutInventory = BoundInventory.Get();
|
||||
OutIndex = HoveredSlotIndex;
|
||||
|
||||
return OutInventory != nullptr && OutIndex != INDEX_NONE;
|
||||
}
|
||||
|
||||
bool UInventoryWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
|
||||
{
|
||||
Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
|
||||
|
||||
// On n'arrive ici que si AUCUNE case n'a traite le depot : les widgets
|
||||
// enfants sont consultes en premier et arretent la propagation.
|
||||
const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
|
||||
if (!Payload || Payload->SourceSlotIndex == INDEX_NONE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Lache DANS la grille mais entre deux cases (l'espacement) : on absorbe.
|
||||
// Jeter un objet au sol parce que le joueur a rate une case de trois
|
||||
// pixels serait impardonnable dans un jeu de survie.
|
||||
if (SlotGrid && SlotGrid->GetCachedGeometry().IsUnderLocation(InDragDropEvent.GetScreenSpacePosition()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Lache hors de la grille : on jette au sol, comme dans Valheim ou Raft.
|
||||
// Depuis l'inventaire SOURCE et non celui de cette grille : sortir une pile
|
||||
// du coffre en la lachant dans le vide doit la retirer du coffre.
|
||||
if (const APlayerController* OwningController = GetOwningPlayer())
|
||||
{
|
||||
if (AFpsPlayer* Player = Cast<AFpsPlayer>(OwningController->GetPawn()))
|
||||
{
|
||||
Player->DropFromInventory(Payload->SourceInventory.Get(), Payload->SourceSlotIndex, Payload->Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UInventoryWidget::Refresh()
|
||||
{
|
||||
if (!SlotGrid)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget::Refresh : SlotGrid est nul. Le widget nomme 'SlotGrid' manque dans le Blueprint."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Debranchee : la grille se vide au lieu de laisser a l'ecran le contenu
|
||||
// d'un conteneur qu'on ne pilote plus. Pas un avertissement -- c'est l'etat
|
||||
// normal d'une grille de coffre entre deux ouvertures.
|
||||
if (!BoundInventory.IsValid())
|
||||
{
|
||||
ClearSlotWidgets();
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<FInventorySlot>& Slots = BoundInventory->GetSlots();
|
||||
|
||||
// La grille commence APRES la barre rapide : les deux conteneurs sont
|
||||
// independants, un objet dans la barre n'apparait pas ici. Sur un coffre,
|
||||
// ConfigureAsContainer() met la barre a zero, donc l'offset vaut 0 et tout
|
||||
// s'affiche.
|
||||
const int32 Offset = BoundInventory->GetBackpackStartIndex();
|
||||
const int32 GridCount = FMath::Max(0, Slots.Num() - Offset);
|
||||
|
||||
// Le nombre de slots peut changer en cours de partie (sac equipe).
|
||||
// On ne reconstruit que si la taille a bouge : recreer toutes les cases
|
||||
// a chaque ramassage serait du gaspillage pur.
|
||||
RebuildGrid(GridCount);
|
||||
|
||||
for (int32 Index = 0; Index < SlotWidgets.Num(); ++Index)
|
||||
{
|
||||
const int32 Absolute = Offset + Index;
|
||||
if (SlotWidgets[Index] && Slots.IsValidIndex(Absolute))
|
||||
{
|
||||
SlotWidgets[Index]->SetSlot(Slots[Absolute]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UInventoryWidget::ClearSlotWidgets()
|
||||
{
|
||||
for (UInventorySlotWidget* SlotWidget : SlotWidgets)
|
||||
{
|
||||
if (SlotWidget)
|
||||
{
|
||||
SlotWidget->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
SlotWidgets.Reset();
|
||||
|
||||
// La case survolee vient de disparaitre : sans cette remise a zero, la
|
||||
// touche "jeter" agirait sur un index qui ne designe plus rien -- et le
|
||||
// panneau de detail continuerait de decrire un objet qui n'est plus la.
|
||||
// NativeOnMouseLeave n'arrive JAMAIS quand le widget s'efface sous le
|
||||
// curseur, ce qui est exactement ce qui se passe en fermant un coffre.
|
||||
SetHoveredSlot(INDEX_NONE);
|
||||
}
|
||||
|
||||
void UInventoryWidget::RebuildGrid(int32 DesiredCount)
|
||||
{
|
||||
if (SlotWidgets.Num() == DesiredCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SlotWidgetClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : SlotWidgetClass n'est pas assignee, la grille restera vide."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Trop de cases : on retire le surplus par la fin.
|
||||
while (SlotWidgets.Num() > DesiredCount)
|
||||
{
|
||||
const int32 Last = SlotWidgets.Num() - 1;
|
||||
if (SlotWidgets[Last])
|
||||
{
|
||||
SlotWidgets[Last]->RemoveFromParent();
|
||||
}
|
||||
SlotWidgets.RemoveAt(Last);
|
||||
}
|
||||
|
||||
if (!BoundInventory.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Pas assez : on en cree, en les placant en ligne / colonne.
|
||||
const int32 Columns = FMath::Max(1, ColumnCount);
|
||||
while (SlotWidgets.Num() < DesiredCount)
|
||||
{
|
||||
UInventorySlotWidget* NewSlot = CreateWidget<UInventorySlotWidget>(this, SlotWidgetClass);
|
||||
if (!NewSlot)
|
||||
{
|
||||
// Arrive typiquement quand le Widget Blueprint de case ne compile
|
||||
// plus, apres un changement de BindWidget cote C++ par exemple.
|
||||
UE_LOG(LogTemp, Error, TEXT("UInventoryWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
|
||||
*GetNameSafe(SlotWidgetClass));
|
||||
break;
|
||||
}
|
||||
|
||||
const int32 Index = SlotWidgets.Num();
|
||||
|
||||
// On donne l'index ABSOLU dans le tableau, pas la position dans la
|
||||
// grille. C'est ce qui rend le glisser entre la barre et la grille
|
||||
// gratuit : les deux manipulent les memes index.
|
||||
const int32 Absolute = BoundInventory->GetBackpackStartIndex() + Index;
|
||||
NewSlot->InitSlot(BoundInventory.Get(), Absolute);
|
||||
NewSlot->OnHoverChanged.AddDynamic(this, &UInventoryWidget::NotifySlotHovered);
|
||||
NewSlot->SetQuickTransferTarget(QuickTransferTarget.Get());
|
||||
|
||||
SlotGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns);
|
||||
SlotWidgets.Add(NewSlot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user