UE4 C++ InventorySystem Part.4 HotBar
やあ
UE4 C++でインベントリシステムを作ろう!
Part.4では、ホットバーを作るよ。
前回
Part.4の結果
目次
- やあ
- 前回
- Part.4の結果
- 目次
- ホットバーの動作を考える
- ホットバーの動作の実装
- ホットバーの動作の実装
- ホットバー用に新しくスロットを作る
- ホットバーから他のインベントリへの交換処理
- ExchangeInventoryの表示を直す
- プレイヤーがキーを押した時の処理
- 次
ホットバーの動作を考える
・スロットの情報の保持
->Part.1参照
・特定のキーが押されたら特定のスロットのアイテムを使用する
->ゲームによってはスキルを登録したり装備を登録したり
ホットバーの動作の実装
今回は簡単にするため、キー1~5が押されたらスロット0~4のアイテムの数を一つ減らすだけにするよ。
細かい処理は作る物によって変わるだろうしね。
キーをプレイヤーが任意のものに変えられるようにしたい、って人はUE4 key config
で検索するといいかもしれない。
Part.1で作ったInventoryWidgetBase
クラスを継承して、HotBar
クラスを作成。
ホットバーを表示するためのUIを作成
これには、Part.3で作ったExchangeInventory
もいれるよ。
イメージ図
WidgetBase
クラスを継承して、InentoryHUD
クラスを作成。
InventoryHUD.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/WidgetBase.h" #include "InventoryHUD.generated.h" class UExchangeInventoryBase; class UBorder; UCLASS() class INVENTORYSYSTEM_API UInventoryHUD : public UWidgetBase { GENERATED_BODY() public: UPROPERTY(meta = (BindWidget)) UBorder* ExchangeBorder; UPROPERTY(meta = (BindWidget)) UBorder* HotBarBorder; void OpenUI() override; void CloseUI() override; };
InventoryHUD.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/InventoryHUD.h" #include "Components/Border.h" ////////////////////////////////// // Init void UInventoryHUD::OpenUI() { AddToViewport(); ExchangeBorder->SetRenderOpacity(0.f); } ////////////////////////////////// // End void UInventoryHUD::CloseUI() { RemoveFromParent(); }
これに伴って、WBP_ExchangeInventory
を少し修正
これはプレイヤーのBeginPlay
で作って表示するようにするよ。
InventoryCharacterBase.h
class UInventoryHUD; class INVENTORYSYSTEM_API AInventoryCharacterBase : public AInventorySystemCharacter { public: /* UIs */ UPROPERTY() UInventoryHUD* InventoryHUD; private: /* Classes */ UPROPERTY(EditAnywhere) TSubclassOf<UInventoryHUD> InventoryHUDClass; };
InventoryCharacterBase.cpp
#include "UIs/InventoryHUD.h" void AInventoryCharacterBase::BeginPlay() { Super::BeginPlay(); InventoryHUD = CreateWidget<UInventoryHUD>(GetWorld(), InventoryHUDClass); InventoryHUD->AddToViewport(); }
これでホットバーを表示するUIができたね。
ホットバーの動作の実装
HotBar.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/InventoryWidgetBase.h" #include "HotBar.generated.h" UCLASS() class INVENTORYSYSTEM_API UHotBar : public UInventoryWidgetBase { GENERATED_BODY() public: void OpenUI() override; void CloseUI() override {}; };
HotBar.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Widgets/HotBar.h" #include "UIs/Bases/InventorySlotBase.h" ///////////////////////////////////////// // Init void UHotBar::OpenUI() { if (Inventory.Num() == 0) { Inventory.Init(FInventorySlots(), GridColumnSize); } for (int Idx = 0; Idx < GridColumnSize; ++Idx) { UInventorySlotBase* _Slot = UInventoryWidgetBase::CreateSlot(Inventory[Idx], Idx); _Slot->OwnerUI = this; AddNewItem(_Slot, Idx); } }
この状態でいったんテストしてみます。
エディタに戻って。
InventoryHUD
クラスを継承して、WBP_InventoryHUD
を作成。
HotBar
クラスを継承して、WBP_HotBar
を作成。
Character
にインベントリHUDのクラスとホットバーのクラスを設定。
この状態でプレイします。
※プレイしても、アイテムをホットバーにドロップしないでください。クラッシュします
見た目上はうまい具合にできてるね。
ホットバーにアイテムをドロップするとクラッシュするのはホットバーにインベントリコンポーネントを渡していないからだよ。
インベントリコンポーネントを渡せばいいじゃないかと思うよね。
次の動画を見てほしい。
?????????????
なんでこんなことになるかというと、インベントリUI(インベントリコンポーネント)のインベントリとホットバーのインベントリが競合してしまっているからだよ。
※ホットバー用のコンポーネントを増やすというのも考えられますが、できるだけコンポーネントを増やしたくないのでやってません。
なので
ホットバー用に新しくスロットを作る
InventorySlotBase
クラスを継承して、HotSlot
クラスを作成
コンポーネントとUI間でのスロットの情報交換処理の作成
InventorySlotBase.h
に追加
protected: /* コンポーネントからコンポーネントへドロップされたときの処理 */ virtual void ExchangeSlot(UInventorySlotBase*& From); /* コンポーネントからUIへドロップされたときの処理 */ virtual void ExchangeSlotForCompToUI(UInventorySlotBase*& From); /* UIからコンポーネントへドロップされたときの処理 */ virtual void ExchangeSlotForUIToComp(UInventorySlotBase*& From); /* UIからUIへドロップされたときの処理 */ virtual void ExchangeSlotForUIToUI(UInventorySlotBase*& From);
InventorySlotBase.cpp
に追加
void UInventorySlotBase::ExchangeSlot(UInventorySlotBase*& From) { int FromIdx = From->SlotContents.SlotIndex; int ToIdx = SlotContents.SlotIndex; FInventorySlots FromTemp = From->InventoryComp->Inventory[FromIdx]; /* Fromスロットのインベントリコンポーネントのインベントリを更新 */ From->InventoryComp->Inventory[FromIdx] = MoveTemp(InventoryComp->Inventory[ToIdx]); /* インベントリUIのインベントリを更新 */ Cast<UInventoryWidgetBase>(From->OwnerUI)->Inventory[FromIdx] = From->InventoryComp->Inventory[FromIdx]; /* このスロットのインベントリコンポーネントのインベントリを更新 */ InventoryComp->Inventory[ToIdx] = MoveTemp(FromTemp); /* インベントリUIのインベントリを更新 */ Cast<UInventoryWidgetBase>(OwnerUI)->Inventory[ToIdx] = InventoryComp->Inventory[ToIdx]; /* スロットの見た目の更新 */ From->UpdateSlot(); UpdateSlot(); } void UInventorySlotBase::ExchangeSlotForCompToUI(UInventorySlotBase*& From) { int FromIdx = From->SlotContents.SlotIndex; int ToIdx = SlotContents.SlotIndex; FInventorySlots FromTemp = From->InventoryComp->Inventory[FromIdx]; UInventoryWidgetBase* _ToOwner = Cast<UInventoryWidgetBase>(OwnerUI); /* Fromスロットのインベントリコンポーネントのインベントリを更新 */ From->InventoryComp->Inventory[FromIdx] = MoveTemp(_ToOwner->Inventory[ToIdx]); /* Fromスロットのオーナーのインベントリを更新 */ Cast<UInventoryWidgetBase>(From->OwnerUI)->Inventory[FromIdx] = From->InventoryComp->Inventory[FromIdx]; /* このスロットのオーナーのインベントリを更新 */ _ToOwner->Inventory[ToIdx] = MoveTemp(FromTemp); /* このスロットの情報を更新 */ SlotContents = _ToOwner->Inventory[ToIdx]; From->UpdateSlot(); RefreshSlot(); } void UInventorySlotBase::ExchangeSlotForUIToComp(UInventorySlotBase*& From) { int FromIdx = From->SlotContents.SlotIndex; int ToIdx = SlotContents.SlotIndex; UInventoryWidgetBase* _FromOwner = Cast<UInventoryWidgetBase>(From->OwnerUI); FInventorySlots FromTemp = _FromOwner->Inventory[FromIdx]; /* Fromスロットのオーナーのインベントリを更新 */ _FromOwner->Inventory[FromIdx] = MoveTemp(InventoryComp->Inventory[ToIdx]); /* Fromスロットの情報の更新 */ From->SlotContents = _FromOwner->Inventory[FromIdx]; /* このスロットのインベントリコンポーネントのインベントリを更新 */ InventoryComp->Inventory[ToIdx] = MoveTemp(FromTemp); Cast<UInventoryWidgetBase>(OwnerUI)->Inventory[ToIdx] = InventoryComp->Inventory[ToIdx]; From->RefreshSlot(); UpdateSlot(); } void UInventorySlotBase::ExchangeSlotForUIToUI(UInventorySlotBase*& From) { int FromIdx = From->SlotContents.SlotIndex; int ToIdx = SlotContents.SlotIndex; UInventoryWidgetBase* _FromOwner = Cast<UInventoryWidgetBase>(From->OwnerUI); UInventoryWidgetBase* _ToOwner = Cast<UInventoryWidgetBase>(OwnerUI); FInventorySlots FromTemp = _FromOwner->Inventory[FromIdx]; /* Fromスロットのオーナーのインベントリを更新 */ _FromOwner->Inventory[FromIdx] = MoveTemp(_ToOwner->Inventory[ToIdx]); /* Fromスロットの情報の更新 */ From->SlotContents = _FromOwner->Inventory[FromIdx]; /* このスロットのオーナーのインベントリを更新 */ _ToOwner->Inventory[ToIdx] = MoveTemp(FromTemp); /* このスロットの情報を更新 */ SlotContents = _ToOwner->Inventory[ToIdx]; From->RefreshSlot(); RefreshSlot(); }
これで、コンポーネントからコンポーネント、コンポーネントからUI、UIからコンポーネント、UIからUIへの情報の交換ができるようになりました。
他のインベントリからホットバーへの交換処理
Part.2参照
HotSlot.h
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UIs/Bases/InventorySlotBase.h" #include "HotSlot.generated.h" UCLASS() class INVENTORYSYSTEM_API UHotSlot : public UInventorySlotBase { GENERATED_BODY() private: virtual bool Initialize() override; bool NativeOnDrop( const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation ) override final; };
UHotSlot.cpp
に追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "UIs/Widgets/HotSlot.h" #include "Operations/ItemDragDropOperation.h" ///////////////////////////////////// // Init bool UHotSlot::Initialize() { bool Res = UUserWidget::Initialize(); ItemIsFrom = EItemIsFrom::HotBar; return Res; } //////////////////////////////////// // Action bool UHotSlot::NativeOnDrop( const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation ) { /* DroppedItem info exists is in ItemOperation */ UItemDragDropOperation* ItemOperation = Cast<UItemDragDropOperation>(InOperation); if (IsValid(ItemOperation) == true) { EItemIsFrom _ItemIsFrom = ItemOperation->GetItemIsFrom(); if (_ItemIsFrom == ItemIsFrom || _ItemIsFrom == EItemIsFrom::Equip) { /* From : HotBar, To : HotBar */ UInventorySlotBase::ExchangeSlotForUIToUI(ItemOperation->InventorySlotUI); } else if (_ItemIsFrom == EItemIsFrom::Inventory || _ItemIsFrom == EItemIsFrom::Chest) { /* From : Inventory/Chest, To HotBar */ UInventorySlotBase::ExchangeSlotForCompToUI(ItemOperation->InventorySlotUI); } InOperation = Cast<UDragDropOperation>(ItemOperation); } UInventorySlotBase::NativeOnDrop(InGeometry, InDragDropEvent, InOperation); return true; }
これでおーけー
ホットバーから他のインベントリへの交換処理
これで他のインベントリからホットバーへの移動はできるようになったね。
だけどホットバーから他のインベントリへの移動はできないよ。
理由はわかるよね。
他のインベントリへのドロップ処理を書き換えてないからね。
InventorySlot.cpp
に追加
bool UInventorySlot::NativeOnDrop( const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation ) { /* DroppedItem info exists is in ItemOperation */ UItemDragDropOperation* ItemOperation = Cast<UItemDragDropOperation>(InOperation); if (IsValid(ItemOperation) == true) { EItemIsFrom _ItemIsFrom = ItemOperation->GetItemIsFrom(); if (_ItemIsFrom == ItemIsFrom || _ItemIsFrom == EItemIsFrom::Chest) { /* From : Inventory, To : Inventory */ UInventorySlotBase::ExchangeSlot(ItemOperation->InventorySlotUI); } else if (_ItemIsFrom == EItemIsFrom::HotBar || _ItemIsFrom == EItemIsFrom::Equip) { UInventorySlotBase::ExchangeSlotForUIToComp(ItemOperation->InventorySlotUI); } InOperation = Cast<UDragDropOperation>(ItemOperation); } UInventorySlotBase::NativeOnDrop(InGeometry, InDragDropEvent, InOperation); return true; }
できました。
ExchangeInventoryの表示を直す
ExchangeInventoryBase.cpp
に追加
void UExchangeInventoryBase::OpenUI() { bUseAddToViewport = false; UWidgetBase::OpenUI(); // 省略 }
ChestItemBase.cpp
に追加
bool AChestItemBase::PickUp(AInventoryCharacterBase* In) { if (InventoryComp->Inventory.Num() == 0) { InventoryComp->Init(); } if (IsValid(ExchangeInventoryUI) == true) { /* Close */ ExchangeInventoryUI->CloseUI(); In->InventoryHUD->ExchangeBorder->SetRenderOpacity(0.f); In->InventoryHUD->ExchangeBorder->ClearChildren(); ExchangeInventoryUI = nullptr; } else { ExchangeInventoryUI = CreateWidget<UExchangeInventoryBase>( GetWorld(), ExchangeInventoryUIClass ); if (IsValid(ExchangeInventoryUI) == true) { ExchangeInventoryUI->_TalkTarget = In; ExchangeInventoryUI->_OwnerComp = InventoryComp; ExchangeInventoryUI->OpenUI(); In->InventoryHUD->ExchangeBorder->SetRenderOpacity(1.f); In->InventoryHUD->ExchangeBorder->AddChild(ExchangeInventoryUI); } } return true; }
おーけー
プレイヤーがキーを押した時の処理
今回は説明を簡単にするためにこう書いてるけど、実際に使うときはデータテーブルでアクションマッピングなどの管理をしてね。
キーをプレイヤーが任意のものに変えられるようにしたい、って人はUE4 key config
で検索するといいかもしれない。
InventoryCharacterBase.h
に追加
private: void AddActionMapping(const FName& InName, const FKey& InKey); void UseHotBarItem1(); void UseHotBarItem2(); void UseHotBarItem3(); void UseHotBarItem4(); void UseHotBarItem5();
InventoryCharacterBase.cpp
に追加
void AInventoryCharacterBase::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { // Set up gameplay key bindings check(PlayerInputComponent); PlayerInputComponent->BindAction("PickUp", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::PickUp); AddActionMapping("PickUp", EKeys::E); PlayerInputComponent->BindAction("UseHotBarItem1", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::UseHotBarItem1); AddActionMapping("UseHotBarItem1", EKeys::One); PlayerInputComponent->BindAction("UseHotBarItem2", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::UseHotBarItem2); AddActionMapping("UseHotBarItem2", EKeys::Two); PlayerInputComponent->BindAction("UseHotBarItem3", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::UseHotBarItem3); AddActionMapping("UseHotBarItem3", EKeys::Three); PlayerInputComponent->BindAction("UseHotBarItem4", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::UseHotBarItem4); AddActionMapping("UseHotBarItem4", EKeys::Four); PlayerInputComponent->BindAction("UseHotBarItem5", EInputEvent::IE_Pressed, this, &AInventoryCharacterBase::UseHotBarItem5); AddActionMapping("UseHotBarItem5", EKeys::Five); Super::SetupPlayerInputComponent(PlayerInputComponent); } void AInventoryCharacterBase::AddActionMapping(const FName& InName, const FKey& InKey) { const FInputActionKeyMapping map(InName, InKey); GetMutableDefault<UInputSettings>()->AddActionMapping(map); } ///////////////////////////////////// // Action void AInventoryCharacterBase::UseHotBarItem1() { HotBar->UseItemAt(0); } void AInventoryCharacterBase::UseHotBarItem2() { HotBar->UseItemAt(1); } void AInventoryCharacterBase::UseHotBarItem3() { HotBar->UseItemAt(2); } void AInventoryCharacterBase::UseHotBarItem4() { HotBar->UseItemAt(3); } void AInventoryCharacterBase::UseHotBarItem5() { HotBar->UseItemAt(4); }