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>
194 lines
4.8 KiB
C++
194 lines
4.8 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "HotbarWidget.h"
|
|
|
|
#include "Components/HorizontalBox.h"
|
|
#include "Components/HorizontalBoxSlot.h"
|
|
#include "FpsPlayer.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "InventoryComponent.h"
|
|
#include "InventorySlotWidget.h"
|
|
|
|
void UHotbarWidget::NativeConstruct()
|
|
{
|
|
Super::NativeConstruct();
|
|
|
|
BindToOwningPawn();
|
|
}
|
|
|
|
void UHotbarWidget::BindToOwningPawn()
|
|
{
|
|
UInventoryComponent* NewInventory = ResolveInventory();
|
|
if (BoundInventory.Get() == NewInventory)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (BoundInventory.IsValid())
|
|
{
|
|
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UHotbarWidget::HandleInventoryChanged);
|
|
BoundInventory->OnSelectedHotbarSlotChanged.RemoveDynamic(this, &UHotbarWidget::HandleSelectionChanged);
|
|
}
|
|
|
|
BoundInventory = NewInventory;
|
|
|
|
if (BoundInventory.IsValid())
|
|
{
|
|
BoundInventory->OnInventoryChanged.AddDynamic(this, &UHotbarWidget::HandleInventoryChanged);
|
|
BoundInventory->OnSelectedHotbarSlotChanged.AddDynamic(this, &UHotbarWidget::HandleSelectionChanged);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("UHotbarWidget : aucun UInventoryComponent trouve sur le pawn possede."));
|
|
}
|
|
|
|
// Les cases memorisent l'ancien composant : on les recree.
|
|
for (UInventorySlotWidget* SlotWidget : SlotWidgets)
|
|
{
|
|
if (SlotWidget)
|
|
{
|
|
SlotWidget->RemoveFromParent();
|
|
}
|
|
}
|
|
SlotWidgets.Reset();
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void UHotbarWidget::NativeDestruct()
|
|
{
|
|
if (BoundInventory.IsValid())
|
|
{
|
|
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UHotbarWidget::HandleInventoryChanged);
|
|
BoundInventory->OnSelectedHotbarSlotChanged.RemoveDynamic(this, &UHotbarWidget::HandleSelectionChanged);
|
|
}
|
|
BoundInventory.Reset();
|
|
|
|
Super::NativeDestruct();
|
|
}
|
|
|
|
UInventoryComponent* UHotbarWidget::ResolveInventory() const
|
|
{
|
|
const APlayerController* OwningController = GetOwningPlayer();
|
|
AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
|
|
return Player ? Player->GetInventoryComponent() : nullptr;
|
|
}
|
|
|
|
void UHotbarWidget::HandleInventoryChanged()
|
|
{
|
|
Refresh();
|
|
}
|
|
|
|
void UHotbarWidget::SetQuickTransferTarget(UInventoryComponent* InTarget)
|
|
{
|
|
QuickTransferTarget = InTarget;
|
|
|
|
for (UInventorySlotWidget* SlotWidget : SlotWidgets)
|
|
{
|
|
if (SlotWidget)
|
|
{
|
|
SlotWidget->SetQuickTransferTarget(InTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UHotbarWidget::HandleSelectionChanged(int32 NewIndex)
|
|
{
|
|
// Seule la surbrillance bouge : inutile de repousser tout le contenu.
|
|
for (int32 Index = 0; Index < SlotWidgets.Num(); ++Index)
|
|
{
|
|
if (SlotWidgets[Index])
|
|
{
|
|
SlotWidgets[Index]->SetSelected(Index == NewIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UHotbarWidget::HandleSlotHovered(int32 SlotIndex, bool bHovered)
|
|
{
|
|
OnSlotHovered.Broadcast(BoundInventory.Get(), SlotIndex, bHovered);
|
|
}
|
|
|
|
void UHotbarWidget::Refresh()
|
|
{
|
|
if (!SlotContainer)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("UHotbarWidget::Refresh : SlotContainer est nul. Le widget nomme 'SlotContainer' manque dans le Blueprint."));
|
|
return;
|
|
}
|
|
|
|
if (!BoundInventory.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const TArray<FInventorySlot>& Slots = BoundInventory->GetSlots();
|
|
const int32 Count = BoundInventory->GetHotbarSlotCount();
|
|
|
|
RebuildSlots(Count);
|
|
|
|
const int32 Selected = BoundInventory->GetSelectedHotbarIndex();
|
|
for (int32 Index = 0; Index < SlotWidgets.Num(); ++Index)
|
|
{
|
|
if (!SlotWidgets[Index] || !Slots.IsValidIndex(Index))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SlotWidgets[Index]->SetSlot(Slots[Index]);
|
|
SlotWidgets[Index]->SetSelected(Index == Selected);
|
|
}
|
|
}
|
|
|
|
void UHotbarWidget::RebuildSlots(int32 DesiredCount)
|
|
{
|
|
if (SlotWidgets.Num() == DesiredCount)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!SlotWidgetClass)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("UHotbarWidget : SlotWidgetClass n'est pas assignee, la barre restera vide."));
|
|
return;
|
|
}
|
|
|
|
while (SlotWidgets.Num() > DesiredCount)
|
|
{
|
|
const int32 Last = SlotWidgets.Num() - 1;
|
|
if (SlotWidgets[Last])
|
|
{
|
|
SlotWidgets[Last]->RemoveFromParent();
|
|
}
|
|
SlotWidgets.RemoveAt(Last);
|
|
}
|
|
|
|
while (SlotWidgets.Num() < DesiredCount)
|
|
{
|
|
UInventorySlotWidget* NewSlot = CreateWidget<UInventorySlotWidget>(this, SlotWidgetClass);
|
|
if (!NewSlot)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("UHotbarWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
|
|
*GetNameSafe(SlotWidgetClass));
|
|
break;
|
|
}
|
|
|
|
const int32 Index = SlotWidgets.Num();
|
|
|
|
// Meme indexation que la grille : la case 0 de la barre EST le slot 0
|
|
// de l'inventaire. C'est ce qui rend le glisser-deposer entre les deux
|
|
// gratuit -- ils manipulent le meme tableau.
|
|
NewSlot->InitSlot(BoundInventory.Get(), Index);
|
|
NewSlot->OnHoverChanged.AddDynamic(this, &UHotbarWidget::HandleSlotHovered);
|
|
NewSlot->SetQuickTransferTarget(QuickTransferTarget.Get());
|
|
|
|
if (UHorizontalBoxSlot* BoxSlot = SlotContainer->AddChildToHorizontalBox(NewSlot))
|
|
{
|
|
BoxSlot->SetPadding(FMargin(SlotPadding));
|
|
}
|
|
|
|
SlotWidgets.Add(NewSlot);
|
|
}
|
|
}
|