1. 아이템 구상 (No Pain Potion, Invisibility Cloak, Penetrate Stone)
No Pain Potion - 장애물에 닿아도 죽지않음.
Invisibility Cloak - Zombie Detect에 걸리지않음.
Penetrate Stone - 벽 가까이에 가면 벽 너머를 볼 수 있음.
2. UI 생성
ListView를 이용하여 인벤토리 제작.
세가지 구성.
1. 각 아이템에 대한 정보를 담고있는 클래스
2. 각 아이템의 정보를 받아와서 한 칸을 차지하는 UI
3. 한칸한칸으로 구성된 전체 인벤토리 UI
InventoryItem 블루프린트에서 User Object List Entry 설정을 해줘야합니다. 그래야 Inventory 위젯 블루프린트에서 InventoryItem 위젯 블루프린트를 인식하고 불러올 수 있습니다.
2. InventoryItem 위젯 블루프린트 제작
아이콘 텍스처, 이름, 갯수로 한칸을 구성했습니다.
3. Inventory 위젯 블루프린트 제작
인벤토리창을 꾸며주고,
Entry Widget Class를 세팅해줍니다.
4. 인벤토리 키 입력 추가.
I 키를 눌렀을 때, 인벤토리창을 열고 닫을 수 있게 세팅하였습니다.
void UWidgetMainHUD::InventoryButtonCallback()
{
FInputModeGameOnly GameOnly;
FInputModeGameAndUI GameAndUI;
AMyPlayerController* pController = Cast<AMyPlayerController>(GetOwningPlayer());
switch (Inventory->GetVisibility())
{
case ESlateVisibility::Visible:
Inventory->SetVisibility(ESlateVisibility::Collapsed);
pController->bShowMouseCursor = false;
pController->SetInputMode(GameOnly);
break;
case ESlateVisibility::Collapsed:
Inventory->SetVisibility(ESlateVisibility::Visible);
pController->bShowMouseCursor = true;
pController->SetInputMode(GameAndUI);
break;
}
}
5. InventoryItemData 제작
// InventoryItemData.cpp
#pragma once
#include "GameInfo.h"
#include "UObject/NoExportTypes.h"
#include "InventoryItemData.generated.h"
UCLASS(Blueprintable, BlueprintType)
class ESCAPEGAME_API UInventoryItemData : public UObject
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
int32 Index;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
int32 ItemNumber;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
int32 ItemCount;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
FString strName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
UTexture2D* IconTexture;
public:
int32 GetIndex() const
{
return Index;
}
int32 GetItemNumber() const
{
return ItemNumber;
}
int32 GetItemCount() const
{
return ItemCount;
}
FString GetItemName() const
{
return strName;
}
UTexture2D* GetIconTexture() const
{
return IconTexture;
}
public:
void SetItemIndex(int32 idx)
{
Index = idx;
}
void SetItemNumber(int32 num)
{
ItemNumber = num;
}
void SetItemCount(int32 Count)
{
ItemCount = Count;
}
void SetItemName(const FString& Name)
{
strName = Name;
}
void SetIconTexture(const FString& strPath)
{
IconTexture = LoadObject<UTexture2D>(nullptr, *strPath);
}
void SetIconTexture(UTexture2D* pTex)
{
IconTexture = pTex;
}
};
아이템에 대한 정보를 담고있는 클래스입니다.
6. InventoryItem 제작
void UWidgetInventoryItem::SetData(class UInventoryItemData* Data)
{
Index = Data->GetIndex();
ItemNumber = Data->GetItemNumber();
SetItemCount(Data->GetItemCount());
SetItemName(Data->GetItemName());
SetIconTexture(Data->GetIconTexture());
}
저장된 InventoryItemData의 값을 받아와서 세팅해줍니다.
7. Inventory 제작
// WidgetInventory.cpp
#include "WidgetInventory.h"
#include "WidgetInventoryItem.h"
#include "Components/ListView.h"
#include "InventoryItemData.h"
#include "MyPlayerController.h"
#include "MyPlayerCharacter.h"
#include "EscapeGameInstance.h"
void UWidgetInventory::NativeConstruct()
{
Super::NativeConstruct();
List = Cast<UListView>(GetWidgetFromName(TEXT("InventoryList")));
List->OnItemClicked().AddUObject(this, &UWidgetInventory::ItemClick);
InitList();
}
void UWidgetInventory::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
}
void UWidgetInventory::InitList()
{
UEscapeGameInstance* GameInst = Cast<UEscapeGameInstance>(GetGameInstance());
if (!IsValid(GameInst))
return;
// 리스트 초기화
UInventoryItemData* NoPainPotion = NewObject<UInventoryItemData>(this, UInventoryItemData::StaticClass());
NoPainPotion->SetItemIndex(0);
NoPainPotion->SetItemCount(GameInst->GetInventory(0));
NoPainPotion->SetItemName(TEXT("No Pain\nPotion"));
NoPainPotion->SetIconTexture(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_reagent_12_b.Tex_reagent_12_b'"));
List->AddItem(NoPainPotion);
UInventoryItemData* InvisibleCloak = NewObject<UInventoryItemData>(this, UInventoryItemData::StaticClass());
InvisibleCloak->SetItemIndex(1);
InvisibleCloak->SetItemCount(GameInst->GetInventory(1));
InvisibleCloak->SetItemName(TEXT("Invisible\nCloak"));
InvisibleCloak->SetIconTexture(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_cloth_02_b.Tex_cloth_02_b'"));
List->AddItem(InvisibleCloak);
UInventoryItemData* PenetrateStone = NewObject<UInventoryItemData>(this, UInventoryItemData::StaticClass());
PenetrateStone->SetItemIndex(2);
PenetrateStone->SetItemCount(GameInst->GetInventory(2));
PenetrateStone->SetItemName(TEXT("Penetrate\nStone"));
PenetrateStone->SetIconTexture(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_gemstone_02.Tex_gemstone_02'"));
List->AddItem(PenetrateStone);
}
void UWidgetInventory::ItemClick(UObject* Obj)
{
UInventoryItemData* pData = Cast<UInventoryItemData>(Obj);
if (IsValid(pData))
{
AMyPlayerController* Controller = Cast<AMyPlayerController>(GetWorld()->GetFirstPlayerController());
if (IsValid(Controller))
{
AMyPlayerCharacter* PlayerChar = Cast<AMyPlayerCharacter>(Controller->GetPawn());
if (IsValid(PlayerChar))
{
// 리스트에서 아이템을 클릭하면, 해당 인덱스를 받아와서 플레이어에서 그에 관련한 능력을 시전한다.
PlayerChar->UseInventoryItem(pData->GetIndex());
}
}
}
}
6번에서 제작한 InventoryItem을 List로 불러와주면 Inventory가 완성이 됩니다.
8. 아이템 박스
적절한 애셋을 찾아서, 스태틱 메쉬를 입혀서 월드에 배치할 블루프린트 제작을 해줬습니다.
// ItemBox.cpp
void AItemBox::TriggerBeginOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult)
{
int32 RandomNum = FMath::RandRange(0, 99);
AMainGameMode* GameMode = Cast<AMainGameMode>(GetWorld()->GetAuthGameMode());
if (IsValid(GameMode))
{
UWidgetMainHUD* MainHUD = GameMode->GetWidgetMainHUD();
if (IsValid(MainHUD))
{
Inventory = MainHUD->GetInventory();
}
}
UGameplayStatics::PlaySoundAtLocation(GetWorld(), GetItemSound, GetActorLocation(), 0.5f);
// List에 아이템 세팅
UListView* List = Inventory->GetList();
if (RandomNum < 20)
{
// 무적 포션 20%
ItemIndex = 0;
UInventoryItemData* Data = Cast<UInventoryItemData>(List->GetItemAt(ItemIndex));
Data->SetItemCount(Data->GetItemCount() + 1);
}
else if (RandomNum < 50)
{
// 투명 망토 30%
ItemIndex = 1;
UInventoryItemData* Data = Cast<UInventoryItemData>(List->GetItemAt(ItemIndex));
Data->SetItemCount(Data->GetItemCount() + 1);
}
else
{
// 투시 능력 50%
ItemIndex = 2;
UInventoryItemData* Data = Cast<UInventoryItemData>(List->GetItemAt(ItemIndex));
Data->SetItemCount(Data->GetItemCount() + 1);
}
// List 업데이트
List->RegenerateAllEntries();
}
void AItemBox::TriggerEndOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex)
{
Destroy();
}
박스와 닿으면, 드롭률에 따라 아이템을 리스트에 업데이트 해줬습니다.
박스 Destroy를 비긴오버랩쪽에 해줬었는데, 액터가 가지고 있는 사운드웨이브까지 날려버려서 소리가 나지 않아서, 엔드오버랩쪽에 해줬습니다. 플레이할때 어색하지 않아서 그대로 진행했습니다.
9. 아이템 사용
Inventory의 ItemClick 함수를 호출하면, 해당 인덱스의 아이템을 플레이어에서 사용합니다.
void AMyPlayerCharacter::UseInventoryItem(int32 idx)
{
AMainGameMode* GameMode = Cast<AMainGameMode>(GetWorld()->GetAuthGameMode());
if (!IsValid(GameMode))
return;
UWidgetMainHUD* MainHUD = GameMode->GetWidgetMainHUD();
if (!IsValid(MainHUD))
return;
Inventory = MainHUD->GetInventory();
// List에 아이템 세팅
List = Inventory->GetList();
UInventoryItemData* Data = Cast<UInventoryItemData>(List->GetItemAt(idx));
if (!(Data->GetItemCount() > 0))
{
bItemEnable = false;
}
else
{
bItemEnable = true;
}
if (bItemEnable)
{
UGameplayStatics::PlaySoundAtLocation(GetWorld(), ItemEnableSound, GetActorLocation(), 0.5f);
if (idx == 0)
{
// No Pain Potion
GetCapsuleComponent()->SetCollisionProfileName(TEXT("NoPainPotion"));
GetMesh()->SetCollisionProfileName(TEXT("NoPainPotion"));
Data->SetItemCount(Data->GetItemCount() - 1);
bItemEnable = false;
FString strPath = FString::Printf(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_reagent_12_b.Tex_reagent_12_b'"));
MainHUD->SetImageBuff(strPath);
MainHUD->bBuffTime = true;
}
else if (idx == 1)
{
// Invisible Cloak
GetCapsuleComponent()->SetCollisionProfileName(TEXT("InvisibleCloak"));
GetMesh()->SetCollisionProfileName(TEXT("InvisibleCloak"));
Data->SetItemCount(Data->GetItemCount() - 1);
bItemEnable = false;
FString strPath = FString::Printf(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_cloth_02_b.Tex_cloth_02_b'"));
MainHUD->SetImageBuff(strPath);
MainHUD->bBuffTime = true;
}
else
{
// Penetrate Stone
GetCapsuleComponent()->SetCapsuleRadius(30.f);
Data->SetItemCount(Data->GetItemCount() - 1);
bItemEnable = false;
FString strPath = FString::Printf(TEXT("Texture2D'/Game/CraftResourcesIcons/Textures/Tex_gemstone_02.Tex_gemstone_02'"));
MainHUD->SetImageBuff(strPath);
MainHUD->bBuffTime = true;
}
// List 업데이트
List->RegenerateAllEntries();
// 아이템 지속시간 7초
GetWorldTimerManager().SetTimer(Timer, this,
&AMyPlayerCharacter::ResetPlayerCollision, 7.f, true);
}
else
{
UGameplayStatics::PlaySoundAtLocation(GetWorld(), ItemUnableSound, GetActorLocation(), 0.5f);
}
}
void AMyPlayerCharacter::ResetPlayerCollision()
{
// 아이템 지속시간 후, 원래 상태로.
GetCapsuleComponent()->SetCollisionProfileName(TEXT("Player"));
GetMesh()->SetCollisionProfileName(TEXT("Player"));
GetCapsuleComponent()->SetCapsuleRadius(42.f);
bItemEnable = true;
}
세 가지 아이템 모두 콜리전 변경 사용 효과를 주었습니다. 타이머를 활용해서 지속시간이 끝나면 Reset 함수를 호출해주어서 원래 상태로 되돌렸습니다.
'게임 개발 (언리얼 엔진)' 카테고리의 다른 글
UE4 게임 개발 EscapeGame - 19 : SaveGame (0) | 2020.12.27 |
---|---|
UE4 게임 개발 EscapeGame - 18 : 각종 수정 사항 (0) | 2020.12.27 |
UE4 게임 개발 EscapeGame - 16 : 게임다워지는 과정 (0) | 2020.12.25 |
UE4 게임 개발 EscapeGame - 15 : 미로 맵 제작 (0) | 2020.12.24 |
UE4 게임 개발 EscapeGame - 14 : 각종 효과음 (0) | 2020.12.24 |