Füge neues Inventar- und Ausrüstungs-UI-System hinzu

Ein neues modulares Inventar- und Ausrüstungs-UI-System wurde implementiert, das folgende Hauptfunktionen bietet:
- Unterstützung für Ausrüstungs-Slots (`UEquipmentWidget`, `UEquipmentSlotWidget`).
- Darstellung von Inventargegenständen in Listen- oder Rasteransicht (`UInventoryItemListWidget`, `UInventoryGridWidget`).
- Schnellzugriffsleiste für große und kleine Slots (`UQuickSlotListWidget`).
- Verwaltung von Inventarbehältern mit zusammenklappbaren Bereichen (`UInventoryContainerListWidget`).

Neue Datenstrukturen (`InventoryTypes.h`) definieren Enums und Strukturen für Gegenstände, Ausrüstungs-Slots und Behälter. Der `PlayerController` wurde erweitert, um das Inventar-UI zu öffnen/schließen (`ToggleInventory`). Neue `.uasset`-Dateien wurden hinzugefügt, um die UI-Komponenten zu unterstützen.
This commit is contained in:
2026-09-14 16:26:35 +02:00
parent c857293eb4
commit a900208273
33 changed files with 1044 additions and 0 deletions
@@ -0,0 +1,104 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "InventoryTypes.generated.h"
class UTexture2D;
/**
* Where an equipped item is displayed. Purely a display label for WBP_EquipmentSlot - there is
* no attachment-point/socket hierarchy and no validation that an item is appropriate for the
* slot it's put in. Multiple slots can share the same type (e.g. two Back slots, two Belt
* slots) - disambiguate those via FEquipmentSlotData::SlotDisplayNameOverride.
*/
UENUM(BlueprintType)
enum class EEquipmentSlotType : uint8
{
Head,
Body,
Hands,
Legs,
Feet,
Back,
Belt
};
/**
* A single inventory/equipment/quick-slot item as far as the UI is concerned. Mass and Volume
* describe the item's physical footprint for weight/capacity tracking - they never affect how
* large the item appears on screen, since every item always renders at the same fixed 2x2 UI
* size (see UInventoryItemWidget). An ItemID of NAME_None means the slot is empty.
*/
USTRUCT(BlueprintType)
struct FItemData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FName ItemID;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
TObjectPtr<UTexture2D> Icon = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 Quantity = 1;
/** Physical mass in kg, used for carry-weight totals - not UI size. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
float Mass = 0.0f;
/** Physical volume in liters, used for carry-capacity totals - not UI size. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
float Volume = 0.0f;
/** Optional basic state label shown alongside the item (e.g. "Broken", "New", "Equipped"). Empty means no state to show. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FText StateText;
bool IsEmpty() const { return ItemID.IsNone(); }
};
/**
* One equipment slot's contents. SlotType is a display label only - WBP_EquipmentSlot shows
* whatever it's given here and does not validate that EquippedItem actually belongs in SlotType.
*/
USTRUCT(BlueprintType)
struct FEquipmentSlotData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
EEquipmentSlotType SlotType = EEquipmentSlotType::Head;
/** Optional override shown instead of SlotType's enum name - use this to tell apart slots that share a type (e.g. "Back 1"/"Back 2", "Belt 1"/"Belt 2"). Empty falls back to the SlotType display name. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
FText SlotDisplayNameOverride;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Equipment")
FItemData EquippedItem;
};
/**
* One inventory container's contents (the player's own bag, a chest being looted, ...). Items
* are a flat, ordered list - there is no physical/spatial grid position per item, only UI-list
* ordering (no Tetris-style placement).
*/
USTRUCT(BlueprintType)
struct FInventoryContainerInfo
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FName ContainerID;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
TArray<FItemData> Items;
};
@@ -32,7 +32,9 @@ public class KingshearthLegacy : ModuleRules
"KingshearthLegacy/AttributeSets",
"KingshearthLegacy/GameplayEffects",
"KingshearthLegacy/Interaction",
"KingshearthLegacy/Inventory",
"KingshearthLegacy/UI/Menu",
"KingshearthLegacy/UI/Inventory",
"KingshearthLegacy/Variant_Platforming",
"KingshearthLegacy/Variant_Platforming/Animation",
"KingshearthLegacy/Variant_Combat",
@@ -13,6 +13,7 @@
#include "UI/PlayerHUDWidget.h"
#include "UI/Menu/InGameMenuWidget.h"
#include "UI/Menu/MenuManagerSubsystem.h"
#include "UI/Inventory/EquipmentInventoryTabWidget.h"
#include "KingshearthLegacyCharacter.h"
void AKingshearthLegacyPlayerController::BeginPlay()
@@ -70,6 +71,11 @@ void AKingshearthLegacyPlayerController::SetupInputComponent()
{
EnhancedInputComponent->BindAction(MenuAction, ETriggerEvent::Started, this, &AKingshearthLegacyPlayerController::ToggleInGameMenu);
}
if (InventoryAction)
{
EnhancedInputComponent->BindAction(InventoryAction, ETriggerEvent::Started, this, &AKingshearthLegacyPlayerController::ToggleInventory);
}
}
}
}
@@ -137,3 +143,27 @@ void AKingshearthLegacyPlayerController::ToggleInGameMenu()
MenuManager->PushScreen(InGameMenuClass);
}
}
void AKingshearthLegacyPlayerController::ToggleInventory()
{
if (!IsLocalPlayerController() || !InventoryScreenClass)
{
return;
}
UGameInstance* GI = GetGameInstance();
UMenuManagerSubsystem* MenuManager = GI ? GI->GetSubsystem<UMenuManagerSubsystem>() : nullptr;
if (!MenuManager)
{
return;
}
if (MenuManager->IsAnyScreenOpen())
{
MenuManager->PopScreen();
}
else
{
MenuManager->PushScreen(InventoryScreenClass);
}
}
@@ -11,6 +11,7 @@ class UInputAction;
class UUserWidget;
class UPlayerHUDWidget;
class UInGameMenuWidget;
class UEquipmentInventoryTabWidget;
/**
* Basic PlayerController class for a third person game
@@ -59,6 +60,14 @@ protected:
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
TObjectPtr<UInputAction> MenuAction;
/** Inventory/equipment screen, pushed/popped through the menu manager by ToggleInventory */
UPROPERTY(EditAnywhere, Category = "UI")
TSubclassOf<UEquipmentInventoryTabWidget> InventoryScreenClass;
/** Input action that opens the inventory screen, or closes the top menu screen if one is already open */
UPROPERTY(EditAnywhere, Category = "Input|Input Mappings")
TObjectPtr<UInputAction> InventoryAction;
/** Gameplay initialization */
virtual void BeginPlay() override;
@@ -75,4 +84,8 @@ protected:
UFUNCTION(BlueprintCallable, Category = "UI")
void ToggleInGameMenu();
/** Opens InventoryScreenClass, or closes the top menu screen if one is already open */
UFUNCTION(BlueprintCallable, Category = "UI")
void ToggleInventory();
};
@@ -0,0 +1,86 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquipmentInventoryTabWidget.h"
#include "UI/Inventory/EquipmentWidget.h"
#include "UI/Inventory/QuickSlotListWidget.h"
#include "UI/Inventory/InventoryItemListWidget.h"
#include "UI/Inventory/InventoryGridWidget.h"
#include "KingshearthLegacy.h"
#include "InputCoreTypes.h"
UEquipmentInventoryTabWidget::UEquipmentInventoryTabWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Needed so the menu manager's SetWidgetToFocus (see UMenuManagerSubsystem::RefreshInputMode)
// can actually give this screen keyboard focus - otherwise NativeOnPreviewKeyDown below never fires.
// Only takes effect set here, at construction - UUserWidget::SetIsFocusable asserts if called later.
SetIsFocusable(true);
}
FReply UEquipmentInventoryTabWidget::NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::Tab)
{
Close();
return FReply::Handled();
}
return Super::NativeOnPreviewKeyDown(InGeometry, InKeyEvent);
}
void UEquipmentInventoryTabWidget::InitializeInventory(const TArray<FEquipmentSlotData>& InEquipmentSlots, const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots, const TArray<FItemData>& InInventoryItems)
{
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Inventory] EquipmentInventoryTab InitializeInventory: WBP_Equipment bound: %s, WBP_QuickSlotList bound: %s, WBP_ItemList bound: %s, WBP_InventoryGrid bound: %s"),
WBP_Equipment ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UEquipmentWidget"),
WBP_QuickSlotList ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UQuickSlotListWidget"),
WBP_ItemList ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UInventoryItemListWidget"),
WBP_InventoryGrid ? TEXT("yes") : TEXT("NO - check its Name in the Designer tree matches the native property, and that it's reparented to UInventoryGridWidget"));
if (WBP_Equipment)
{
WBP_Equipment->SetEquipmentSlots(InEquipmentSlots);
}
if (WBP_QuickSlotList)
{
WBP_QuickSlotList->SetQuickSlots(InBigQuickSlots, InSmallQuickSlots);
}
if (WBP_ItemList)
{
WBP_ItemList->SetItems(InInventoryItems);
}
if (WBP_InventoryGrid)
{
WBP_InventoryGrid->SetItems(InInventoryItems);
}
ShowInventoryGridView();
}
void UEquipmentInventoryTabWidget::ShowItemListView()
{
if (WBP_ItemList)
{
WBP_ItemList->SetVisibility(ESlateVisibility::Visible);
}
if (WBP_InventoryGrid)
{
WBP_InventoryGrid->SetVisibility(ESlateVisibility::Collapsed);
}
}
void UEquipmentInventoryTabWidget::ShowInventoryGridView()
{
if (WBP_ItemList)
{
WBP_ItemList->SetVisibility(ESlateVisibility::Collapsed);
}
if (WBP_InventoryGrid)
{
WBP_InventoryGrid->SetVisibility(ESlateVisibility::Visible);
}
}
@@ -0,0 +1,87 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UI/Menu/MenuScreenWidget.h"
#include "Inventory/InventoryTypes.h"
#include "EquipmentInventoryTabWidget.generated.h"
class UEquipmentWidget;
class UQuickSlotListWidget;
class UInventoryItemListWidget;
class UInventoryGridWidget;
/**
* Main inventory screen - a shallow wrapper coordinating its child widgets. Pushed/popped
* through the menu manager like any other UMenuScreenWidget (see
* AKingshearthLegacyPlayerController::ToggleInventory), so opening/closing it gets the usual UI
* input mode + cursor handling for free, and an optional "BackButton" (inherited from
* UMenuScreenWidget) closes it the same way any other menu screen closes:
*
* WBP_EquipmentInventoryTAB
* |-- WBP_Equipment
* | `-- WBP_EquipmentSlot
* |-- WBP_QuickSlotList
* `-- Inventory View
* |-- WBP_ItemList
* `-- WBP_InventoryGrid
*
* Owns no inventory logic itself - it only forwards data supplied by the inventory/equipment
* system to its children, and switches which of WBP_ItemList / WBP_InventoryGrid is visible for
* the "Inventory View" section (both are kept populated with the same items; only one is shown
* at a time).
*
* Nest instances named exactly "WBP_Equipment", "WBP_QuickSlotList", "WBP_ItemList" and
* "WBP_InventoryGrid" in the Designer tree, reparented to their respective C++ base classes.
*/
UCLASS(abstract)
class UEquipmentInventoryTabWidget : public UMenuScreenWidget
{
GENERATED_BODY()
public:
UEquipmentInventoryTabWidget(const FObjectInitializer& ObjectInitializer);
/** Populates every child widget. Call once the inventory/equipment system has data ready (e.g. right after this screen is pushed). */
UFUNCTION(BlueprintCallable, Category = "Inventory")
void InitializeInventory(const TArray<FEquipmentSlotData>& InEquipmentSlots, const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots, const TArray<FItemData>& InInventoryItems);
UFUNCTION(BlueprintCallable, Category = "Inventory")
void ShowItemListView();
UFUNCTION(BlueprintCallable, Category = "Inventory")
void ShowInventoryGridView();
protected:
/**
* While this screen is open, the menu manager puts input in UI-only mode - the PlayerController
* stops receiving Tab presses entirely, so re-pressing Tab can only be caught here, at the
* focused widget itself. Requires bIsFocusable (set true in the constructor) so the menu
* manager's SetWidgetToFocus call actually succeeds.
*
* Uses the Preview (tunneling) pass rather than NativeOnKeyDown (bubbling) - Tab is Slate's
* default "focus next widget" navigation key, and that default navigation runs as a fallback
* once the bubbling pass goes unhandled, which pre-empts NativeOnKeyDown ever getting a
* chance to close the screen. Intercepting in Preview, which runs before that fallback, avoids it.
*/
virtual FReply NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
/** Nested WBP_Equipment instance - named exactly "WBP_Equipment" in the Designer tree. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UEquipmentWidget> WBP_Equipment;
/** Nested WBP_QuickSlotList instance - named exactly "WBP_QuickSlotList" in the Designer tree. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UQuickSlotListWidget> WBP_QuickSlotList;
/** Nested WBP_ItemList instance - named exactly "WBP_ItemList" in the Designer tree, part of the Inventory View toggle. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UInventoryItemListWidget> WBP_ItemList;
/** Nested WBP_InventoryGrid instance - named exactly "WBP_InventoryGrid" in the Designer tree, part of the Inventory View toggle. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UInventoryGridWidget> WBP_InventoryGrid;
};
@@ -0,0 +1,24 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "Inventory/InventoryTypes.h"
#include "EquipmentSlotListEntry.generated.h"
/**
* Plain UObject wrapper around one FEquipmentSlotData - UListView items must be UObjects, not
* structs. Used by WBP_Equipment before calling SetListItems, so each row's UEquipmentSlotWidget
* receives one of these via IUserObjectListEntry.
*/
UCLASS()
class UEquipmentSlotListEntry : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = "Equipment")
FEquipmentSlotData Slot;
};
@@ -0,0 +1,58 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquipmentSlotWidget.h"
#include "UI/Inventory/EquipmentSlotListEntry.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
void UEquipmentSlotWidget::SetSlotData(const FEquipmentSlotData& InSlotData)
{
SlotData = InSlotData;
ApplySlotVisuals();
}
void UEquipmentSlotWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
{
if (const UEquipmentSlotListEntry* ListItem = Cast<UEquipmentSlotListEntry>(ListItemObject))
{
SetSlotData(ListItem->Slot);
}
}
void UEquipmentSlotWidget::ApplySlotVisuals()
{
const bool bHasItem = !SlotData.EquippedItem.IsEmpty();
if (Icon)
{
if (bHasItem && SlotData.EquippedItem.Icon)
{
Icon->SetBrushFromTexture(SlotData.EquippedItem.Icon);
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
Icon->SetVisibility(ESlateVisibility::Collapsed);
}
}
if (QuantityText)
{
if (bHasItem && SlotData.EquippedItem.Quantity > 1)
{
QuantityText->SetText(FText::AsNumber(SlotData.EquippedItem.Quantity));
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
QuantityText->SetVisibility(ESlateVisibility::Collapsed);
}
}
if (SlotLabel)
{
SlotLabel->SetText(!SlotData.SlotDisplayNameOverride.IsEmpty()
? SlotData.SlotDisplayNameOverride
: UEnum::GetDisplayValueAsText(SlotData.SlotType));
}
}
@@ -0,0 +1,57 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/IUserObjectListEntry.h"
#include "Inventory/InventoryTypes.h"
#include "EquipmentSlotWidget.generated.h"
class UImage;
class UTextBlock;
/**
* A single equipment slot's visuals. This is a display slot only - it shows whatever
* FEquipmentSlotData it is given and does not validate that the item is appropriate for the
* slot, and does not represent an attachment point/socket on the character mesh.
*
* Name your widgets exactly "Icon" (Image), "QuantityText" (TextBlock) and "SlotLabel"
* (TextBlock) in the Designer and population is handled here in C++; no BP event graph logic
* needed.
*/
UCLASS(abstract)
class UEquipmentSlotWidget : public UUserWidget, public IUserObjectListEntry
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Equipment")
void SetSlotData(const FEquipmentSlotData& InSlotData);
UFUNCTION(BlueprintPure, Category = "Equipment")
const FEquipmentSlotData& GetSlotData() const { return SlotData; }
protected:
/** IUserObjectListEntry - called by the owning ListView (WBP_Equipment) when this row is assigned an UEquipmentSlotListEntry. */
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
void ApplySlotVisuals();
/** Equipped item's icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the slot is empty. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UImage> Icon;
/** Equipped item's stack count - bind a TextBlock named exactly "QuantityText" in the Designer. Hidden automatically when Quantity <= 1. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> QuantityText;
/** Slot type label (e.g. "Head", "Back 1") - bind a TextBlock named exactly "SlotLabel" in the Designer. Prefers SlotData.SlotDisplayNameOverride when set, falling back to the SlotType enum name. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> SlotLabel;
UPROPERTY(BlueprintReadOnly, Category = "Equipment")
FEquipmentSlotData SlotData;
};
@@ -0,0 +1,29 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquipmentWidget.h"
#include "UI/Inventory/EquipmentSlotListEntry.h"
#include "Components/ListView.h"
void UEquipmentWidget::SetEquipmentSlots(const TArray<FEquipmentSlotData>& InSlots)
{
if (!SlotsList)
{
return;
}
SlotsList->ClearListItems();
ListItems.Reset(InSlots.Num());
TArray<UObject*> SlotObjects;
SlotObjects.Reserve(InSlots.Num());
for (const FEquipmentSlotData& SlotData : InSlots)
{
UEquipmentSlotListEntry* Entry = NewObject<UEquipmentSlotListEntry>(this);
Entry->Slot = SlotData;
ListItems.Add(Entry);
SlotObjects.Add(Entry);
}
SlotsList->SetListItems(SlotObjects);
}
@@ -0,0 +1,38 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Inventory/InventoryTypes.h"
#include "EquipmentWidget.generated.h"
class UEquipmentSlotListEntry;
class UListView;
/**
* The character's full set of equipment slots.
*
* Name a ListView exactly "SlotsList" in the Designer, with its Entry Widget Class set to a WBP
* subclass of UEquipmentSlotWidget, and population is handled here in C++.
*/
UCLASS(abstract)
class UEquipmentWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Equipment")
void SetEquipmentSlots(const TArray<FEquipmentSlotData>& InSlots);
protected:
/** Named exactly "SlotsList" in the Designer, Entry Widget Class set to a WBP subclass of UEquipmentSlotWidget. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UListView> SlotsList;
/** UObject wrappers backing SlotsList - kept alive here so the ListView's UObjects don't get GC'd. */
UPROPERTY()
TArray<TObjectPtr<UEquipmentSlotListEntry>> ListItems;
};
@@ -0,0 +1,42 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InventoryContainerListWidget.h"
#include "UI/Inventory/InventoryGridWidget.h"
#include "Components/TextBlock.h"
void UInventoryContainerListWidget::NativeConstruct()
{
Super::NativeConstruct();
SetExpanded(bExpanded);
}
void UInventoryContainerListWidget::SetContainerData(const FInventoryContainerInfo& InContainerData)
{
ContainerData = InContainerData;
if (ContainerLabel)
{
ContainerLabel->SetText(ContainerData.DisplayName);
}
if (WBP_InventoryGrid)
{
WBP_InventoryGrid->SetItems(ContainerData.Items);
}
}
void UInventoryContainerListWidget::SetExpanded(bool bInExpanded)
{
bExpanded = bInExpanded;
if (WBP_InventoryGrid)
{
WBP_InventoryGrid->SetVisibility(bExpanded ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
}
}
void UInventoryContainerListWidget::ToggleExpanded()
{
SetExpanded(!bExpanded);
}
@@ -0,0 +1,67 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Inventory/InventoryTypes.h"
#include "InventoryContainerListWidget.generated.h"
class UInventoryGridWidget;
class UTextBlock;
/**
* A labeled, collapsible view of one inventory container (the player's own bag, a chest being
* looted, ...):
*
* WBP_InventoryContainerList
* |-- Container Label
* `-- Collapsible Inventory Grid
* `-- WBP_InventoryGrid
* `-- WBP_Item
*
* Name a TextBlock exactly "ContainerLabel" in the Designer, and nest a WBP subclass of
* UInventoryGridWidget named exactly "WBP_InventoryGrid". SetContainerData populates the label
* and forwards the container's items to the nested grid; SetExpanded/ToggleExpanded show or
* collapse it. Wire an expand/collapse button to ToggleExpanded in the Designer/event graph.
*/
UCLASS(abstract)
class UInventoryContainerListWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Inventory")
void SetContainerData(const FInventoryContainerInfo& InContainerData);
UFUNCTION(BlueprintPure, Category = "Inventory")
const FInventoryContainerInfo& GetContainerData() const { return ContainerData; }
UFUNCTION(BlueprintCallable, Category = "Inventory")
void SetExpanded(bool bInExpanded);
UFUNCTION(BlueprintCallable, Category = "Inventory")
void ToggleExpanded();
UFUNCTION(BlueprintPure, Category = "Inventory")
bool IsExpanded() const { return bExpanded; }
protected:
virtual void NativeConstruct() override;
/** Named exactly "ContainerLabel" in the Designer. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> ContainerLabel;
/** Nested WBP_InventoryGrid instance - named exactly "WBP_InventoryGrid" in the Designer tree, reparented to a UInventoryGridWidget subclass. Collapsed/shown by SetExpanded. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UInventoryGridWidget> WBP_InventoryGrid;
UPROPERTY(BlueprintReadOnly, Category = "Inventory")
FInventoryContainerInfo ContainerData;
UPROPERTY(EditAnywhere, Category = "Inventory")
bool bExpanded = true;
};
@@ -0,0 +1,35 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InventoryGridWidget.h"
#include "UI/Inventory/InventoryItemListEntry.h"
#include "Components/TileView.h"
void UInventoryGridWidget::SetItems(const TArray<FItemData>& InItems)
{
Items = InItems;
PopulateGrid();
}
void UInventoryGridWidget::PopulateGrid()
{
if (!ItemsGrid)
{
return;
}
ItemsGrid->ClearListItems();
ListItems.Reset(Items.Num());
TArray<UObject*> ItemObjects;
ItemObjects.Reserve(Items.Num());
for (const FItemData& Item : Items)
{
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
Entry->Item = Item;
ListItems.Add(Entry);
ItemObjects.Add(Entry);
}
ItemsGrid->SetListItems(ItemObjects);
}
@@ -0,0 +1,49 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Inventory/InventoryTypes.h"
#include "InventoryGridWidget.generated.h"
class UInventoryItemListEntry;
class UTileView;
/**
* A UI grid of items. This is purely a tiled layout - every item occupies the same fixed-size
* 2x2 tile no matter its Mass/Volume, and items have no spatial position of their own (no
* physical/Tetris-style grid placement). Carries no container/label concept of its own - that's
* WBP_InventoryContainerList's job when a labeled, collapsible section is needed.
*
* Name a TileView exactly "ItemsGrid" in the Designer, with its Entry Widget Class set to a WBP
* subclass of UInventoryItemWidget, and population is handled here in C++.
*/
UCLASS(abstract)
class UInventoryGridWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Inventory")
void SetItems(const TArray<FItemData>& InItems);
UFUNCTION(BlueprintPure, Category = "Inventory")
const TArray<FItemData>& GetItems() const { return Items; }
protected:
void PopulateGrid();
/** Named exactly "ItemsGrid" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTileView> ItemsGrid;
UPROPERTY(BlueprintReadOnly, Category = "Inventory")
TArray<FItemData> Items;
/** UObject wrappers backing ItemsGrid - kept alive here so the TileView's UObjects don't get GC'd. */
UPROPERTY()
TArray<TObjectPtr<UInventoryItemListEntry>> ListItems;
};
@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "Inventory/InventoryTypes.h"
#include "InventoryItemListEntry.generated.h"
/**
* Plain UObject wrapper around one FItemData - UListView/UTileView items must be UObjects, not
* structs. Used by WBP_ItemList, WBP_InventoryGrid and WBP_QuickSlotList before calling
* SetListItems, so each row's UInventoryItemWidget receives one of these via
* IUserObjectListEntry.
*/
UCLASS()
class UInventoryItemListEntry : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = "Item")
FItemData Item;
};
@@ -0,0 +1,29 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InventoryItemListWidget.h"
#include "UI/Inventory/InventoryItemListEntry.h"
#include "Components/ListView.h"
void UInventoryItemListWidget::SetItems(const TArray<FItemData>& InItems)
{
if (!ItemsList)
{
return;
}
ItemsList->ClearListItems();
ListItems.Reset(InItems.Num());
TArray<UObject*> ItemObjects;
ItemObjects.Reserve(InItems.Num());
for (const FItemData& Item : InItems)
{
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
Entry->Item = Item;
ListItems.Add(Entry);
ItemObjects.Add(Entry);
}
ItemsList->SetListItems(ItemObjects);
}
@@ -0,0 +1,41 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Inventory/InventoryTypes.h"
#include "InventoryItemListWidget.generated.h"
class UInventoryItemListEntry;
class UListView;
/**
* A simple vertical list of items - not a physical/spatial grid, just an ordered list. Used as
* the list-view alternative to UInventoryGridWidget inside WBP_EquipmentInventoryTAB's Inventory
* View (see UEquipmentInventoryTabWidget::ShowItemListView), and reusable standalone anywhere
* else that only needs a plain item list (a crafting recipe's ingredients, a shop's stock, ...).
*
* Name a ListView exactly "ItemsList" in the Designer, with its Entry Widget Class set to a WBP
* subclass of UInventoryItemWidget, and population is handled here in C++.
*/
UCLASS(abstract)
class UInventoryItemListWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Item List")
void SetItems(const TArray<FItemData>& InItems);
protected:
/** Named exactly "ItemsList" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UListView> ItemsList;
/** UObject wrappers backing ItemsList - kept alive here so the ListView's UObjects don't get GC'd. */
UPROPERTY()
TArray<TObjectPtr<UInventoryItemListEntry>> ListItems;
};
@@ -0,0 +1,75 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InventoryItemWidget.h"
#include "UI/Inventory/InventoryItemListEntry.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
void UInventoryItemWidget::SetItemData(const FItemData& InItemData)
{
ItemData = InItemData;
ApplyItemVisuals();
}
void UInventoryItemWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
{
if (const UInventoryItemListEntry* ListItem = Cast<UInventoryItemListEntry>(ListItemObject))
{
SetItemData(ListItem->Item);
}
}
void UInventoryItemWidget::ApplyItemVisuals()
{
if (Icon)
{
if (!ItemData.IsEmpty() && ItemData.Icon)
{
Icon->SetBrushFromTexture(ItemData.Icon);
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
Icon->SetVisibility(ESlateVisibility::Collapsed);
}
}
if (ItemNameText)
{
if (!ItemData.IsEmpty())
{
ItemNameText->SetText(ItemData.DisplayName);
ItemNameText->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
ItemNameText->SetVisibility(ESlateVisibility::Collapsed);
}
}
if (QuantityText)
{
if (!ItemData.IsEmpty() && ItemData.Quantity > 1)
{
QuantityText->SetText(FText::AsNumber(ItemData.Quantity));
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
QuantityText->SetVisibility(ESlateVisibility::Collapsed);
}
}
if (StateText)
{
if (!ItemData.IsEmpty() && !ItemData.StateText.IsEmpty())
{
StateText->SetText(ItemData.StateText);
StateText->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
StateText->SetVisibility(ESlateVisibility::Collapsed);
}
}
}
@@ -0,0 +1,63 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/IUserObjectListEntry.h"
#include "Inventory/InventoryTypes.h"
#include "InventoryItemWidget.generated.h"
class UImage;
class UTextBlock;
/**
* A single inventory/equipment/quick-slot item. Always renders at the same fixed UI size
* regardless of the item's Mass/Volume - physical footprint is inventory-property data, not a
* UI dimension. Reused as the entry widget class for WBP_ItemList, WBP_InventoryGrid and
* WBP_QuickSlotList (via IUserObjectListEntry), and can also be placed directly and driven with
* SetItemData.
*
* Name your widgets exactly "Icon" (Image), "ItemNameText" (TextBlock), "QuantityText"
* (TextBlock) and "StateText" (TextBlock) in the Designer and population is handled here in
* C++; no BP event graph logic needed.
*/
UCLASS(abstract)
class UInventoryItemWidget : public UUserWidget, public IUserObjectListEntry
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Item")
void SetItemData(const FItemData& InItemData);
UFUNCTION(BlueprintPure, Category = "Item")
const FItemData& GetItemData() const { return ItemData; }
protected:
/** IUserObjectListEntry - called by the owning ListView/TileView when this row is assigned an UInventoryItemListEntry. */
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
void ApplyItemVisuals();
/** Item icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the item slot is empty. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UImage> Icon;
/** Item name - bind a TextBlock named exactly "ItemNameText" in the Designer. Hidden automatically when the item slot is empty. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> ItemNameText;
/** Stack count - bind a TextBlock named exactly "QuantityText" in the Designer. Hidden automatically when Quantity <= 1. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> QuantityText;
/** Basic item state (e.g. "Broken", "New") - bind a TextBlock named exactly "StateText" in the Designer. Hidden automatically when the item has no StateText set. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> StateText;
UPROPERTY(BlueprintReadOnly, Category = "Item")
FItemData ItemData;
};
@@ -0,0 +1,35 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "QuickSlotListWidget.h"
#include "UI/Inventory/InventoryItemListEntry.h"
#include "Components/TileView.h"
void UQuickSlotListWidget::SetQuickSlots(const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots)
{
PopulateSlots(BigQuickSlotsView, InBigQuickSlots, BigListItems);
PopulateSlots(SmallQuickSlotsView, InSmallQuickSlots, SmallListItems);
}
void UQuickSlotListWidget::PopulateSlots(UTileView* TargetView, const TArray<FItemData>& InSlots, TArray<TObjectPtr<UInventoryItemListEntry>>& OutListItems)
{
if (!TargetView)
{
return;
}
TargetView->ClearListItems();
OutListItems.Reset(InSlots.Num());
TArray<UObject*> ItemObjects;
ItemObjects.Reserve(InSlots.Num());
for (const FItemData& Item : InSlots)
{
UInventoryItemListEntry* Entry = NewObject<UInventoryItemListEntry>(this);
Entry->Item = Item;
OutListItems.Add(Entry);
ItemObjects.Add(Entry);
}
TargetView->SetListItems(ItemObjects);
}
@@ -0,0 +1,55 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Inventory/InventoryTypes.h"
#include "QuickSlotListWidget.generated.h"
class UInventoryItemListEntry;
class UTileView;
/**
* The player's quick-access hotbar: a row of "big" slots (primary, keybound actions) and a row
* of "small" slots (secondary/utility). Both still render items at the standard fixed 2x2 size -
* "big"/"small" describes the slot's role and on-screen footprint, not item icon scaling. Pass
* an entry with an empty FItemData (see FItemData::IsEmpty) for slots that have nothing
* assigned. The slot arrays may include slots granted by equipped items (e.g. a backpack) -
* that's the inventory/equipment system's decision, this widget just displays whatever it's
* given.
*
* Name two TileViews exactly "BigQuickSlotsView" and "SmallQuickSlotsView" in the Designer, both
* with their Entry Widget Class set to a WBP subclass of UInventoryItemWidget (the same class
* used by WBP_ItemList/WBP_InventoryGrid), and population is handled here in C++.
*/
UCLASS(abstract)
class UQuickSlotListWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Quick Slots")
void SetQuickSlots(const TArray<FItemData>& InBigQuickSlots, const TArray<FItemData>& InSmallQuickSlots);
protected:
void PopulateSlots(UTileView* TargetView, const TArray<FItemData>& InSlots, TArray<TObjectPtr<UInventoryItemListEntry>>& OutListItems);
/** Named exactly "BigQuickSlotsView" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTileView> BigQuickSlotsView;
/** Named exactly "SmallQuickSlotsView" in the Designer, Entry Widget Class set to a WBP subclass of UInventoryItemWidget. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTileView> SmallQuickSlotsView;
/** UObject wrappers backing BigQuickSlotsView - kept alive here so the TileView's UObjects don't get GC'd. */
UPROPERTY()
TArray<TObjectPtr<UInventoryItemListEntry>> BigListItems;
/** UObject wrappers backing SmallQuickSlotsView - kept alive here so the TileView's UObjects don't get GC'd. */
UPROPERTY()
TArray<TObjectPtr<UInventoryItemListEntry>> SmallListItems;
};