1. データ構造の設計
インベントリは「内部データ」と「表示データ」の二層構造で構成します。
1.1 内部データ(高速検索用)
// 星屑ID → 総保有量
TMap<FName, int32> TotalAmounts;
// 星屑ID → 使用中スロット数
TMap<FName, int32> SlotUsage;
TMapを採用した理由は、UEのガベージコレクションと相性が良く、Find関数を使えば存在チェックも高速に行えるためです。
1.2 表示データ(UI連携用)
USTRUCT(BlueprintType)
struct FInventorySlot
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName ItemId;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 StackCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaxStack = 99;
bool IsEmpty() const { return StackCount <= 0; }
bool IsFull() const { return StackCount >= MaxStack; }
int32 RemainingSpace() const { return MaxStack - StackCount; }
};
UCLASS(BlueprintType)
class UInventoryGrid : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TArray<FInventorySlot> Slots;
};
1.3 容量管理
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Inventory")
int32 MaxSlots = 48;
UFUNCTION(BlueprintCallable)
void ResizeInventory(int32 NewSize)
{
Slots.SetNum(NewSize);
MaxSlots = NewSize;
}
2. 検索機能
2.1 総保有量の取得
UFUNCTION(BlueprintCallable)
int32 GetTotalAmount(FName ItemId) const
{
const int32* Found = TotalAmounts.Find(ItemId);
return Found ? *Found : 0;
}
2.2 追加可能量の計算
UFUNCTION(BlueprintCallable)
int32 GetAddableAmount(FName ItemId, int32 RequestAmount) const
{
int32 TotalSpace = 0;
const FStardustData* Data = GetStardustData(ItemId);
if (!Data) return 0;
// 既存スロットの空き容量
for (const FInventorySlot& Slot : Slots)
{
if (Slot.ItemId == ItemId && !Slot.IsFull())
{
TotalSpace += Slot.RemainingSpace();
}
}
// 空スロットの容量
int32 EmptySlots = 0;
for (const FInventorySlot& Slot : Slots)
{
if (Slot.IsEmpty()) EmptySlots++;
}
TotalSpace += EmptySlots * Data->MaxStack;
return FMath::Min(TotalSpace, RequestAmount);
}
3. 更新処理
3.1 単一スロットの更新
UFUNCTION(BlueprintCallable)
void UpdateSlot(int32 Index, FName ItemId, int32 Amount)
{
if (!Slots.IsValidIndex(Index)) return;
FInventorySlot& Slot = Slots[Index];
// 古いデータを減算
if (!Slot.IsEmpty())
{
TotalAmounts[Slot.ItemId] -= Slot.StackCount;
SlotUsage[Slot.ItemId]--;
}
// 新しいデータを設定
if (Amount > 0)
{
Slot.ItemId = ItemId;
Slot.StackCount = Amount;
Slot.MaxStack = GetStardustData(ItemId)->MaxStack;
TotalAmounts.FindOrAdd(ItemId) += Amount;
SlotUsage.FindOrAdd(ItemId)++;
}
else
{
Slot = FInventorySlot(); // 空スロットにリセット
}
OnInventoryUpdated.Broadcast();
}
3.2 自動整頓
UFUNCTION(BlueprintCallable)
void CompactInventory()
{
TArray<FInventorySlot> NonEmptySlots;
for (const FInventorySlot& Slot : Slots)
{
if (!Slot.IsEmpty())
{
NonEmptySlots.Add(Slot);
}
}
// スタック可能なアイテムを結合
for (int32 i = 0; i < NonEmptySlots.Num(); ++i)
{
for (int32 j = i + 1; j < NonEmptySlots.Num(); ++j)
{
if (NonEmptySlots[i].ItemId == NonEmptySlots[j].ItemId)
{
int32 TransferAmount = FMath::Min(
NonEmptySlots[i].RemainingSpace(),
NonEmptySlots[j].StackCount
);
NonEmptySlots[i].StackCount += TransferAmount;
NonEmptySlots[j].StackCount -= TransferAmount;
if (NonEmptySlots[j].IsEmpty())
{
NonEmptySlots.RemoveAt(j);
j--;
}
}
}
}
// 配列を詰める
Slots.SetNum(MaxSlots);
for (int32 i = 0; i < MaxSlots; ++i)
{
if (i < NonEmptySlots.Num())
{
Slots[i] = NonEmptySlots[i];
}
else
{
Slots[i] = FInventorySlot();
}
}
OnInventoryUpdated.Broadcast();
}
4. アイテム追加・削除
4.1 優先スタック追加
UFUNCTION(BlueprintCallable)
bool TryAddItem(FName ItemId, int32 Amount, int32& Remaining)
{
Remaining = Amount;
const FStardustData* Data = GetStardustData(ItemId);
if (!Data) return false;
// 既存スロットに追加
for (FInventorySlot& Slot : Slots)
{
if (Slot.ItemId == ItemId && !Slot.IsFull())
{
int32 AddAmount = FMath::Min(Slot.RemainingSpace(), Remaining);
Slot.StackCount += AddAmount;
Remaining -= AddAmount;
TotalAmounts.FindOrAdd(ItemId) += AddAmount;
if (Remaining <= 0)
{
OnInventoryUpdated.Broadcast();
return true;
}
}
}
// 空スロットに新規追加
for (FInventorySlot& Slot : Slots)
{
if (Slot.IsEmpty())
{
int32 AddAmount = FMath::Min(Data->MaxStack, Remaining);
Slot.ItemId = ItemId;
Slot.StackCount = AddAmount;
Slot.MaxStack = Data->MaxStack;
Remaining -= AddAmount;
TotalAmounts.FindOrAdd(ItemId) += AddAmount;
SlotUsage.FindOrAdd(ItemId)++;
if (Remaining <= 0)
{
OnInventoryUpdated.Broadcast();
return true;
}
}
}
OnInventoryUpdated.Broadcast();
return Remaining <= 0;
}
4.2 削除処理
UFUNCTION(BlueprintCallable)
bool TryRemoveItem(FName ItemId, int32 Amount)
{
int32 CurrentTotal = GetTotalAmount(ItemId);
if (CurrentTotal < Amount) return false;
int32 Remaining = Amount;
// 後ろから削除(UIの整合性維持のため)
for (int32 i = Slots.Num() - 1; i >= 0 && Remaining > 0; --i)
{
FInventorySlot& Slot = Slots[i];
if (Slot.ItemId == ItemId)
{
int32 RemoveAmount = FMath::Min(Slot.StackCount, Remaining);
Slot.StackCount -= RemoveAmount;
Remaining -= RemoveAmount;
TotalAmounts[ItemId] -= RemoveAmount;
if (Slot.IsEmpty())
{
Slot = FInventorySlot();
SlotUsage[ItemId]--;
}
}
}
OnInventoryUpdated.Broadcast();
return true;
}
5. UI連携
5.1 動的マルチキャストデリゲート
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FFInventoryUpdateSignature);
UPROPERTY(BlueprintAssignable)
FFInventoryUpdateSignature OnInventoryUpdated;
5.2 UIバインディング例
// インベントリウィジェット
UFUNCTION()
void RefreshGrid()
{
for (int32 i = 0; i < InventoryComponent->Slots.Num(); ++i)
{
if (SlotWidgets.IsValidIndex(i))
{
SlotWidgets[i]->UpdateSlot(InventoryComponent->Slots[i]);
}
}
}
// 初期化時
InventoryComponent->OnInventoryUpdated.AddDynamic(this, &UInventoryWidget::RefreshGrid);