やあ
UE4 C++でインベントリシステムを作ろう!
Part.1ではアイテムを拾って表示するだけのインベントリを作るよー
正直UE4何もわからないしC++も何もわかってないのでこここうしたほうがいいとか、間違ってるところがあれば教えてくださると非常に助かります。
Part.1の結果
目次
- やあ
- Part.1の結果
- 目次
- 環境
- 概要
- まずはプロジェクトを作る
- 次にプレイヤーの基底クラスを作るよ
- 次はプレイヤーや宝箱がインベントリを持つためのコンポーネントを作るよ
- プレイヤーのインベントリを表示するためのUIをつくるよ
- 拾えるアイテムを作る
- 次
環境
・UE4.24.3
・MSVC2019
概要
インベントリシステムを作るのはとても簡単でこれだけ!
① プレイヤー : アイテムを拾う。
② プレイヤー : インベントリに空きがあれば、アイテムをスロットに追加する。
えぇ!?こんなに簡単に!?
ちょっと詳しく書くとこういうこと!
① プレイヤー : アイテムを拾う。
② アイテム : 拾われた! -> イベント発生!!
③ インベントリコンポーネント(プレイヤー) : ②イベント受信
④ インベントリコンポーネント(プレイヤー) : インベントリの空きスロットを探す。
-> 見つかった ⑤ に
-> 見つからなかった ⑧ に
⑤ インベントリコンポーネント(プレイヤー) : 空きを見つけた!
⑥ インベントリUI : 空きスロットに追加
⑦ インベントリスロット : スロットの情報を更新
⑧ 終了
まずはプロジェクトを作る
できたねー
わかんないことはAnsweHubで解決しよー
作成できたらMSVCに移動して、xx.Build.cs
に"UMG"
を追加してね。
これをやることでコンパイラがUMG_API
を読み込めるようにするよ。
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; public class InventorySystem : ModuleRules { public InventorySystem(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG" }); } }
追加したら、プロジェクトフォルダのuproject
を右クリックしてGenerate Visual Studio project files
を押してね。
次にプレイヤーの基底クラスを作るよ
MyProjectCharacter
クラスを継承して、InventoryCharacterBase
クラスを作成。
InventoryCharacterBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "InventorySystem/InventorySystemCharacter.h" #include "InventoryCharacterBase.generated.h" class UInventoryComponentBase; UCLASS() class INVENTORYSYSTEM_API AInventoryCharacterBase : public AInventorySystemCharacter { GENERATED_BODY() private: AInventoryCharacterBase(); public: UPROPERTY(VisibleAnywhere) UInventoryComponentBase* InventoryComp; };
InventoryCharacterBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Characters/Bases/InventoryCharacterBase.h" #include "Components/Bases/InventoryComponentBase.h" #include "UIs/Widgets/PlayerBelongingsWidget.h" AInventoryCharacterBase::AInventoryCharacterBase() { InventoryComp = CreateDefaultSubobject<UInventoryComponentBase>(TEXT("InventoryComp")); }
次はプレイヤーや宝箱がインベントリを持つためのコンポーネントを作るよ
こんな感じのイメージ
ActorComponent
クラスを継承してInventoryComponentBase
クラスを作成
デフォルトの関数は全部消してね。
コンポーネントの動作を考える
・インベントリの開閉
・インベントリにアイテムを追加
これだけ・・・?
中身を書いていく前に、インベントリUIを作るよ
UserWidget
クラスを継承して、WidgetBase
クラスを作成。
これはこれから作るUI全部の基底クラスだよ。
WidgetBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "WidgetBase.generated.h" UCLASS() class INVENTORYSYSTEM_API UWidgetBase : public UUserWidget { GENERATED_BODY() public: UPROPERTY() UWidgetBase* OwnerUI; protected: virtual void AddDelegate() {}; virtual void RemoveDelegate() {}; UPROPERTY(EditAnywhere) bool bUseAddToViewport = true; UPROPERTY(EditAnywhere) bool bUseInputMode = true; public: UFUNCTION(BlueprintCallable) virtual void OpenUI(); UFUNCTION(BlueprintCallable) virtual void CloseUI(); /* Set input mode */ void SetUIOnly(); void SetGameAndUI(); void SetGameOnly(); /* Set Player controller */ void SetUIController(); void SetGameController(); protected: APlayerController* GetPlayerController(const int& Idx = 0); ACharacter* GetPlayerCharacter(const int& Idx = 0); };
WidgetBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Bases/WidgetBase.h" #include "Kismet/GameplayStatics.h" //////////////////////////////////////////// // Init void UWidgetBase::OpenUI() { AddDelegate(); if (bUseInputMode == true) { SetGameAndUI(); SetUIController(); } if (bUseAddToViewport == true) { AddToViewport(); } } //////////////////////////////////////////// // End void UWidgetBase::CloseUI() { RemoveDelegate(); if (bUseInputMode == true) { SetGameOnly(); SetGameController(); } if (bUseAddToViewport == true) { RemoveFromParent(); } } //////////////////////////////////////////// // Other APlayerController* UWidgetBase::GetPlayerController(const int& Idx) { return UGameplayStatics::GetPlayerController(GetWorld(), Idx); } ACharacter* UWidgetBase::GetPlayerCharacter(const int& Idx) { return UGameplayStatics::GetPlayerCharacter(GetWorld(), Idx); } // Input Mode void UWidgetBase::SetUIOnly() { APlayerController* Controller = GetPlayerController(); if (IsValid(Controller) == true) { Controller->SetInputMode(FInputModeUIOnly()); } } void UWidgetBase::SetGameOnly() { APlayerController* Controller = GetPlayerController(); if (IsValid(Controller) == true) { Controller->SetInputMode(FInputModeGameOnly()); } } void UWidgetBase::SetGameAndUI() { APlayerController* Controller = GetPlayerController(); if (IsValid(Controller) == true) { Controller->SetInputMode(FInputModeGameAndUI()); } } // Player Controller void UWidgetBase::SetUIController() { APlayerController* Controller = GetPlayerController(); if (IsValid(Controller) == true) { Controller->bShowMouseCursor = true; Controller->SetIgnoreMoveInput(true); Controller->SetIgnoreLookInput(true); } } void UWidgetBase::SetGameController() { APlayerController* Controller = GetPlayerController(); if (IsValid(Controller) == true) { Controller->bShowMouseCursor = false; Controller->SetIgnoreMoveInput(false); Controller->SetIgnoreLookInput(false); } }
いきなりいっぱい書いて混乱するかもしれないけど、大したことはしてないね。
インベントリスロット
次にWidgetBase
クラスを継承して、InventorySlotBase
クラスを作るよ。
スロットの動作を考える
・アイテムの情報を保持。
・アイテムの情報をプレイヤーに見えるようにする。
・情報の更新。
これだけ!
書いていく前に。
スロットの情報を保持するための構造体の定義
Object
クラスを継承して、MyStructs
クラスを作るよ。
.cpp
ファイルはいらないから消してね。
ついでにアイテム情報の構造体も定義しておくよ。
MyStructs.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "ptoStructs.generated.h" class UTexture2D; USTRUCT(Blueprintable) struct FItems { GENERATED_USTRUCT_BODY() public: UPROPERTY(EditAnywhere, Category = "Item") FName Name; UPROPERTY(EditAnywhere, Category = "Item") FText Description; UPROPERTY(EditAnywhere, Category = "Item") UTexture2D* Image; UPROPERTY(EditAnywhere, Category = "Item") int MaxStackSize; UPROPERTY(EditAnywhere, Category = "Item") UClass* ItemClass; FItems() : Name(), Description(), Image(), MaxStackSize(0), ItemClass() {}; }; USTRUCT(Blueprintable) struct FInventorySlots { GENERATED_USTRUCT_BODY() public: UPROPERTY(EditAnywhere, Category = "Inventory") FItems ItemInfo; UPROPERTY() int SlotIndex; UPROPERTY(EditAnywhere, Category = "Inventory") int Quantity; UPROPERTY() EItemIsFrom ItemIsFrom; FInventorySlots() : ItemInfo(), Quantity(0) {}; FInventorySlots(const FItems& InItemInfo, const int& InQuantity) : ItemInfo(InItemInfo), Quantity(InQuantity) {}; /* スロットのインデックスは、スロットが作られたとき以外で設定しないので、 コピーコンストラクタで、インデックス以外をコピーするように、 operatorをオーバーロードする。 */ void operator=(const FInventorySlots& In) { ItemInfo = In.ItemInfo; Quantity = In.Quantity; ItemIsFrom = In.ItemIsFrom; }; FName GetItemName() const { return ItemInfo.Name; } FText GetItemDesciption() const { return ItemInfo.Description; } UTexture2D* GetItemImage() const { return ItemInfo.Image; } UClass* GetItemClass() const { return ItemInfo.ItemClass; } int GetMaxStackSize() const { return ItemInfo.MaxStackSize; } };
スロットの動作の実装
InventorySlotBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/WidgetBase.h" #include "Templates/ptoStructs.h" #include "InventorySlotBase.generated.h" class UImage; class UTextBlock; class UTexture2D; class UInventoryComponentBase; UCLASS() class INVENTORYSYSTEM_API UInventorySlotBase : public UWidgetBase { GENERATED_BODY() public: void SetItemImage(UTexture2D* In); void SetQuantity(const int& In); protected: UPROPERTY(meta = (BindWidget)) UImage* ItemImage; UPROPERTY(meta = (BindWidget)) UTextBlock* QuantityText; UPROPERTY() FInventorySlots SlotContents; UPROPERTY() UInventoryComponentBase* InventoryComp; protected: void RefreshSlot(); public: virtual void UpdateSlot(const FInventorySlots& InSlot, const int& Idx); virtual void UpdateSlot(); };
InventorySlotBase.cpp
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Bases/InventorySlotBase.h" #include "Components/Image.h" #include "Components/TextBlock.h" #include "Engine/Texture2D.h" ///////////////////////////////////// // Init void UInventorySlotBase::SetItemImage(UTexture2D* In) { ItemImage->SetBrushFromTexture(In); } void UInventorySlotBase::SetQuantity(const int& In) { QuantityText->SetText(FText::AsNumber(In)); } ///////////////////////////////////// // Action void UInventorySlotBase::RefreshSlot() { SetItemImage(SlotContents.GetItemImage()); SetQuantity(SlotContents.Quantity); } void UInventorySlotBase::UpdateSlot( const FInventorySlots& InSlot, const int& Idx ) { if (InSlot.Quantity <= 0) { SlotContents = FInventorySlots(); } else { SlotContents = InSlot; SlotContents.SlotIndex = Idx; } RefreshSlot(); } void UInventorySlotBase::UpdateSlot() { SlotContents = InventoryComp->Inventory[SlotContents.SlotIndex]; RefreshSlot(); }
インベントリUI
次にWidgetBase
クラスを継承して、InventoryWidgetBase
クラスを作るよ。
インベントリUIの動作を考える
・スロットの作成。
・スロットの情報の保持。
これだけ!
インベントリUIの動作の実装
InventoryWidgetBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/WidgetBase.h" #include "Templates/ptoStructs.h" #include "InventoryWidgetBase.generated.h" class UInventoryComponentBase; class UInventorySlotBase; class UGridPanel; class UTextBlock; UCLASS() class INVENTORYSYSTEM_API UInventoryWidgetBase : public UWidgetBase { GENERATED_BODY() public: UPROPERTY(meta = (BindWidget)) UTextBlock* InventoryName; UPROPERTY(meta = (BindWidget)) UGridPanel* ItemList; virtual void OpenUI() override; UPROPERTY() UInventoryComponentBase* InventoryComp; /* インベントリを何列にするかを決める */ UPROPERTY(EditAnywhere) int GridColumnSize; /* 親インベントリコンポーネントを持たない、 インベントリUIも出てくるのでここで宣言。 */ UPROPERTY(EditAnywhere, Category = "Inventory") TArray<FInventorySlots> Inventory; private: /* UIs */ /* Classes */ UPROPERTY(EditAnywhere) TSubclassOf<UInventorySlotBase> InventorySlotClass; protected: virtual UInventorySlotBase* CreateSlot( const FInventorySlots& InSlot, const int& SlotIdx ); virtual void AddNewItem(UInventorySlotBase*& NewSlot, const int& Idx); public: virtual void CloseUI() override; };
InventoryWidgetBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Bases/InventoryWidgetBase.h" #include "UIs/Bases/InventorySlotBase.h" #include "Components/Bases/InventoryComponentBase.h" #include "Components/GridPanel.h" /////////////////////////////////////////// // Init void UInventoryWidgetBase::OpenUI() { UWidgetBase::OpenUI(); for (int Idx = 0; Idx < InventoryComp->NumberOfSlots; ++Idx) { UInventorySlotBase* NewSlot = CreateSlot(InventoryComp->Inventory[Idx], Idx); NewSlot->OwnerUI = this; NewSlot->InventoryComp = InventoryComp; AddNewItem(NewSlot, Idx); } } /////////////////////////////////////////// // Action UInventorySlotBase* UInventoryWidgetBase::CreateSlot( const FInventorySlots& InSlot, const int& SlotIdx ) { UInventorySlotBase* _Slot = CreateWidget<UInventorySlotBase>( this, InventorySlotClass ); if (IsValid(_Slot) == true) { _Slot->SlotContents = InSlot; _Slot->SlotContents.SlotIndex = SlotIdx; _Slot->RefreshSlot(); } return _Slot; } void UInventoryWidgetBase::AddNewItem(UInventorySlotBase*& NewSlot, const int& Idx) { ItemList->AddChildToGrid( NewSlot, Idx / GridColumnSize, Idx % GridColumnSize ); } //////////////////////////////////////////// // End void UInventoryWidgetBase::CloseUI() { UWidgetBase::CloseUI(); InventoryComp->Inventory = MoveTemp(Inventory); }
コンポーネントの動作の実装
インベントリUIを使いまわしてもいいんだけど、今回は開閉するたびにインベントリUIを作り直すようにするよ。
InventoryComponentBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Templates/ptoStructs.h" #include "InventoryComponentBase.generated.h" class UInventoryWidgetBase; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class INVENTORYSYSTEM_API UInventoryComponentBase : public UActorComponent { GENERATED_BODY() public: /* インベントリUIがいくつのスロットを持つか */ UPROPERTY(EditAnywhere) int NumberOfSlots; UPROPERTY() TArray<FInventorySlots> Inventory; UPROPERTY(BlueprintCallable) virtual void ToggleInventory(); /* UIs */ UPROPERTY() UInventoryWidgetBase* InventoryUI; /* UI Classes */ UPROPERTY(EditAnywhere) TSubclassOf<UInventoryWidgetBase> InventoryUIClass; UFUNCTION(BlueprintCallable, Category = "Inventory") virtual void Init(); };
InventoryComponentBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Components/Bases/InventoryComponentBase.h" #include "UIs/Bases/InventoryWidgetBase.h" /////////////////////////////////////////// // Init void UInventoryComponentBase::Init() { if (Inventory.Num() == 0) { Inventory.Init(FInventorySlots(), NumberOfSlots); } } void UInventoryComponentBase::ToggleInventory() { if (IsValid(InventoryUI) == true) { // インベントリUIが存在するときの処理 InventoryUI->CloseUI(); InventoryUI = nullptr; } else { // インベントリUIが存在しない時の処理 InventoryUI = CreateWidget<UInventoryWidgetBase>(GetWorld(), InventoryUIClass); if (IsValid(InventoryUI) == true) { InventoryUI->InventoryComp = this; InventoryUI->Inventory = Inventory; InventoryUI->OpenUI(); } } }
InventoryCharacterBase.cpp
に追加
void AInventoryCharacterBase::BeginPlay()
{
Super::BeginPlay();
InventoryComp->Init();
}
とりあえずはこんな感じ。
最低限のものはできたからエディタに戻るよ
エディタに戻ったらInventorySlotBase
クラスを継承してWBP_InventorySlot
クラスを作成
作ったら、下の画像みたいにしてね。
Overlayのパディングの値を5にしといてね。
次にInventoryWidgetBase
クラスを継承してWBP_InventoryUI
クラスを作ってね
キャラクターにInventoryコンポーネントをちょっと設定
はいできました。
プレイヤーのインベントリを表示するためのUIをつくるよ
このままじゃ不格好だしプレイヤーさん的にもわかりにくいよね。
なのでインベントリや後々追加する装備UIを表示するためのUIを作るよ。
WidgetBase
クラスを継承して、PlayerBelongingsWidget
クラスを作成
InventoryWidgetBase.cpp
のUWidgetBase::OpenUI()
とUWidgetBase::CloseUI()
を消してね。
PlayerBelongingsWidget.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/WidgetBase.h" #include "PlayerBelongingsWidget.generated.h" class UBorder; class AInventoryCharacterBase; UCLASS() class INVENTORYSYSTEM_API UPlayerBelongingsWidget : public UWidgetBase { GENERATED_BODY() private: UPROPERTY(meta = (BindWidget)) UBorder* InventoryBorder; public: UPROPERTY() AInventoryCharacterBase* _Owner; void OpenUI() override final; void CloseUI() override final; };
PlayerBelongingsWidget.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Widgets/PlayerBelongingsWidget.h" #include "UIs/Bases/InventoryWidgetBase.h" #include "Characters/Bases/InventoryCharacterBase.h" #include "Components/Bases/InventoryComponentBase.h" #include "Components/Border.h" ///////////////////////////////////////// // Init void UPlayerBelongingsWidget::OpenUI() { UWidgetBase::OpenUI(); _Owner->InventoryComp->ToggleInventory(); InventoryBorder->AddChild(_Owner->InventoryComp->InventoryUI); } ///////////////////////////////////////// // End void UPlayerBelongingsWidget::CloseUI() { UWidgetBase::CloseUI(); _Owner->InventoryComp->ToggleInventory(); InventoryBorder->ClearChildren(); }
InventoryCharacterBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "InventorySystem/InventorySystemCharacter.h" #include "InventoryCharacterBase.generated.h" class UPlayerBelongingsWidget; class UInventoryComponentBase; UCLASS() class INVENTORYSYSTEM_API AInventoryCharacterBase : public AInventorySystemCharacter { GENERATED_BODY() private: AInventoryCharacterBase(); public: UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UInventoryComponentBase* InventoryComp; virtual void BeginPlay() override; UFUNCTION(BlueprintCallable) void TogglePlayerBelongingsUI(); /* UIs */ UPROPERTY() UPlayerBelongingsWidget* PlayerBelongingsUI; /* Classes */ UPROPERTY(EditAnywhere) TSubclassOf<UPlayerBelongingsWidget> PlayerBelongingsUIClass; };
InventoryCharacterBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Characters/Bases/InventoryCharacterBase.h" #include "Components/Bases/InventoryComponentBase.h" #include "UIs/Widgets/PlayerBelongingsWidget.h" AInventoryCharacterBase::AInventoryCharacterBase() { InventoryComp = CreateDefaultSubobject<UInventoryComponentBase>(TEXT("InventoryComp")); } void AInventoryCharacterBase::BeginPlay() { Super::BeginPlay(); PlayerBelongingsUI = CreateWidget<UPlayerBelongingsWidget>(GetWorld(), PlayerBelongingsUIClass); PlayerBelongingsUI->_Owner = this; } ///////////////////////////////////// // Action void AInventoryCharacterBase::TogglePlayerBelongingsUI() { if (IsValid(PlayerBelongingsUI) == true) { if (!PlayerBelongingsUI->IsInViewport()) { PlayerBelongingsUI->OpenUI(); } else { PlayerBelongingsUI->CloseUI(); } } }
エディタに戻ってPlayerBelongingsWidget
クラスを継承して、WBP_PlayerBelogngingsUI
を作って。
キャラクターの設定をちょっといじって
できました。
拾えるアイテムを作る
Actor
クラスを継承して、PickUpableItemBase
クラスを作成
アイテムの動作を考える
・拾われた際のイベント発生
これだけ!
実装の前にインベントリにアイテムを追加する処理の実装
InventoryComponentBase.h
に追加
/* スタック可能なスロットのインデックスを返す */ UFUNCTION(BlueprintCallable, Category = "Inventory") virtual bool IsStackable(const FInventorySlots& InSlot, int& StackableIdx); /* インベントリに追加 */ UFUNCTION(BlueprintCallable, Category = "Inventory") virtual bool AddToInventory(const FInventorySlots& InSlot); /* 新しいスタックを作成 */ UFUNCTION(BlueprintCallable, Category = "Inventory") virtual bool CreateStack(const FInventorySlots& InSlot); /* スタックに追加 */ UFUNCTION(BlueprintCallable, Category = "Inventory") virtual bool AddToStack(const FInventorySlots& InSlot, const int& Idx); /* CreateSlotでインベントリに入りきらなかったアイテムや、 DropSlotで捨てたアイテムを生成する */ UFUNCTION(BlueprintCallable, Category = "Inventory") virtual void SpawnRemainItem(const FInventorySlots& InSlot);
InventoryComponentBase.cpp
に追加
#include "Items/Bases/PickUpableItemBase.h" //////////////////////////////////////////// // Action bool UInventoryComponentBase::IsStackable(const FInventorySlots& InSlot, int& StackableIdx) { for (int Idx = 0; Idx < Inventory.Num(); ++Idx) { if (Inventory[Idx].Quantity < InSlot.GetMaxStackSize() && Inventory[Idx].GetItemClass() == InSlot.GetItemClass()) { StackableIdx = Idx; return true; } } return false; } bool UInventoryComponentBase::AddToInventory(const FInventorySlots& InSlot) { int Idx = 0; bool bIsSuccess = false; if (IsStackable(InSlot, Idx) == true) { bIsSuccess = AddToStack(InSlot, Idx); } else { bIsSuccess = CreateStack(InSlot); } return bIsSuccess; } bool UInventoryComponentBase::CreateStack(const FInventorySlots& InSlot) { int _Quantity = InSlot.Quantity; if (_Quantity <= 0) return false; /* アイテムを拾ったとき、スタックできる場所がなければ、新しくスロットを作る */ for (int Index = 0; Index < Inventory.Num(); ++Index) { if (Inventory[Index].Quantity <= 0) { _Quantity = FMath::Clamp(_Quantity, 1, 9999); if (_Quantity > InSlot.GetMaxStackSize()) { Inventory[Index] = FInventorySlots(InSlot.ItemInfo, InSlot.GetMaxStackSize()); CreateStack(FInventorySlots(InSlot.ItemInfo, _Quantity - InSlot.GetMaxStackSize())); } else { Inventory[Index] = InSlot; } return true; } } if (_Quantity > 0) { /* Spawn Item */ FInventorySlots _NewSlotInfo = InSlot; _NewSlotInfo.Quantity = _Quantity; SpawnRemainItem(_NewSlotInfo); } return false; } bool UInventoryComponentBase::AddToStack(const FInventorySlots& InSlot, const int& Idx) { int _NewQuantity = InSlot.Quantity + Inventory[Idx].Quantity; if (_NewQuantity > Inventory[Idx].GetMaxStackSize()) { /* アイテムを拾ったときMaxStackSizeより大きかったら インベントリスロットに最大量を入れる 量が0になるまでAddToInventoryを呼ぶよ */ FInventorySlots _FullSlotInfo = FInventorySlots(InSlot.ItemInfo, InSlot.GetMaxStackSize()); Inventory[Idx] = _FullSlotInfo; _NewQuantity -= InSlot.GetMaxStackSize(); FInventorySlots NewSlot = FInventorySlots(InSlot.ItemInfo, _NewQuantity); AddToInventory(NewSlot); } else { FInventorySlots _NewSlotInfo = FInventorySlots(Inventory[Idx].ItemInfo, _NewQuantity); Inventory[Idx] = _NewSlotInfo; } return true; } void UInventoryComponentBase::SpawnRemainItem(const FInventorySlots& InSlot) { FRotator SpawnRot = { FMath::RandRange(0.f, 360.f), FMath::RandRange(0.f, 360.f), FMath::RandRange(0.f, 360.f) }; APickUpableItemBase* _Item = GetWorld()->SpawnActor<APickUpableItemBase>( InSlot.GetItemClass(), GetOwner()->GetActorLocation(), SpawnRot ); if (IsValid(_Item) == true) { _Item->Quantity = InSlot.Quantity; _Item->ItemInfo = InSlot.ItemInfo; } }
アイテムの動作の実装
今回は、アイテム以外を拾えるように気がありませんので、インターフェースは使いません。
PickUpableItemBase.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Templates/ptoStructs.h" #include "PickUpableItemBase.generated.h" class AInventoryCharacterBase; class UScnectComponent; class UStaticMeshCompnent; UCLASS() class INVENTORYSYSTEM_API APickUpableItemBase : public AActor { GENERATED_BODY() public: APickUpableItemBase(); UPROPERTY(EditAnywhere) int Quantity; UPROPERTY(EditAnywhere) FItems ItemInfo; protected: UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Item") USceneComponent* SceneComp; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Item") UStaticMeshComponent* MeshComp; virtual void BeginPlay() override; public: virtual bool PickUp(AInventoryCharacterBase* In); };
PickUpableItemBase.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Items/Bases/PickUpableItemBase.h" #include "Characters/Bases/InventoryCharacterBase.h" #include "Components/Bases/InventoryComponentBase.h" #include "Components/SceneComponent.h" #include "Components/SkeletalMeshComponent.h" // Sets default values APickUpableItemBase::APickUpableItemBase() { SceneComp = CreateDefaultSubobject<USceneComponent>(TEXT("Scene Comp")); SetRootComponent(SceneComp); MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh Comp")); MeshComp->SetupAttachment(SceneComp); MeshComp->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No; MeshComp->SetCollisionProfileName("Custom"); FCollisionResponseContainer _CollisionRes; _CollisionRes.SetResponse(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); _CollisionRes.SetResponse(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap); MeshComp->SetCollisionResponseToChannels(_CollisionRes); MeshComp->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic); MeshComp->SetSimulatePhysics(true); } // Called when the game starts or when spawned void APickUpableItemBase::BeginPlay() { Super::BeginPlay(); } ///////////////////////////////////////// // Action bool APickUpableItemBase::PickUp(AInventoryCharacterBase* In) { UActorComponent* _Comp = In->GetComponentByClass(UInventoryComponentBase::StaticClass()); if (IsValid(_Comp) == true) { UInventoryComponentBase* _InventoryComp = Cast<UInventoryComponentBase>(_Comp); if (IsValid(_InventoryComp) == true) { Quantity = FMath::Clamp(Quantity, 1, 9999); FInventorySlots NewSlotInfo = FInventorySlots(ItemInfo, Quantity); if (_InventoryComp->AddToInventory(NewSlotInfo) == true) { Destroy(); return true; } } } Destroy(); return false; }
InventoryCharacterBase.h
に追加
protected: virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; UFUNCTION(BlueprintCallable) virtual void PickUp();
InventoryCharacterBase.cpp
に追加
void AInventoryCharacterBase::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { // Set up gameplay key bindings check(PlayerInputComponent); PlayerInputComponent->BindAction("PickUp", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::PickUp); const FInputActionKeyMapping map("PickUp", EKeys::E); GetMutableDefault<UInputSettings>()->AddActionMapping(map); Super::SetupPlayerInputComponent(PlayerInputComponent); } void AInventoryCharacterBase::PickUp() { TArray<AActor*> Out; GetOverlappingActors(Out, APickUpableItemBase::StaticClass()); for (AActor* Actor : Out) { APickUpableItemBase* _Item = Cast<APickUpableItemBase>(Actor); if (IsValid(_Item) == true) { _Item->PickUp(this); break; } } }
エディタに戻ってPickUpableItemBase
クラスを継承してBP_ItemTest
を作成
※ : 今回は簡単にするため、アイテムの情報を手動で登録していますが、実際に使う際はデータテーブルで管理してください。
適当にゲームに追加して、プレイ!
できました。
いろいろ試すとわかるんですけど、インベントリUIを開いた状態でEキーを押すとインベントリUIにアイテムが追加されず、アイテムだけが消えるんですね。
これは、インベントリコンポーネントには追加(更新)処理を実装してるけど、インベントリUIにはなんの追加(更新)処理も実装してないからです。
なのでちょっと修正。
InventoryWidgetBase.h
に追加
public: /* ItemListからIdxの位置にあるスロットを探す */ virtual UInventorySlotBase* GetSlotAtIndex(const int& Idx); virtual void UpdateSlot(const int& Idx);
InventoryWidgetBase.cpp
に追加
UInventorySlotBase* UInventoryWidgetBase::GetSlotAtIndex(const int& Idx) { return Cast<UInventorySlotBase>(ItemList->GetChildAt(Idx)); } void UInventoryWidgetBase::UpdateSlot(const int& Idx) { UInventorySlotBase* _Slot = GetSlotAtIndex(Idx); if (IsValid(_Slot) == true) { _Slot->UpdateSlot(); } }
InventoryComponentBase.cpp
に追加
bool UInventoryComponentBase::CreateStack(const FInventorySlots& InSlot) { int _Quantity = InSlot.Quantity; if (_Quantity <= 0) return false; /* アイテムを拾ったとき、スタックできる場所がなければ、新しくスロットを作る */ for (int Index = 0; Index < Inventory.Num(); ++Index) { if (Inventory[Index].Quantity <= 0) { _Quantity = FMath::Clamp(_Quantity, 1, 9999); if (_Quantity > InSlot.GetMaxStackSize()) { Inventory[Index] = FInventorySlots(InSlot.ItemInfo, InSlot.GetMaxStackSize()); if (IsValid(InventoryUI) == true) { InventoryUI->UpdateSlot(Index); } CreateStack(FInventorySlots(InSlot.ItemInfo, _Quantity - InSlot.GetMaxStackSize())); } else { Inventory[Index] = InSlot; if (IsValid(InventoryUI) == true) { InventoryUI->UpdateSlot(Index); } } return true; } } if (_Quantity > 0) { /* Spawn Item */ FInventorySlots _NewSlotInfo = InSlot; _NewSlotInfo.Quantity = _Quantity; SpawnRemainItem(_NewSlotInfo); } return false; } bool UInventoryComponentBase::AddToStack(const FInventorySlots& InSlot, const int& Idx) { int _NewQuantity = InSlot.Quantity + Inventory[Idx].Quantity; if (_NewQuantity > Inventory[Idx].GetMaxStackSize()) { /* アイテムを拾ったときMaxStackSizeより大きかったら インベントリスロットに最大量を入れる 量が0になるまでAddToInventoryを呼ぶよ */ FInventorySlots _FullSlotInfo = FInventorySlots(InSlot.ItemInfo, InSlot.GetMaxStackSize()); Inventory[Idx] = _FullSlotInfo; if (IsValid(InventoryUI) == true) { InventoryUI->UpdateSlot(Idx); } _NewQuantity -= InSlot.GetMaxStackSize(); FInventorySlots NewSlot = FInventorySlots(InSlot.ItemInfo, _NewQuantity); AddToInventory(NewSlot); } else { FInventorySlots _NewSlotInfo = FInventorySlots(Inventory[Idx].ItemInfo, _NewQuantity); Inventory[Idx] = _NewSlotInfo; if (IsValid(InventoryUI) == true) { InventoryUI->UpdateSlot(Idx); } } return true; }
これで更新できるようになりました。
とりあえず今回はここまで。