Author SHA1 Message Date
Dolobarsch a900208273 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.
2026-09-14 16:26:35 +02:00
33 changed files with 1044 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
};