서론
이번에는 데이터 테이블과 구조체에 새로운 변수인 killedCnt, RequireCnt를 추가하여
퀘스트를 받고 몬스터를 사냥할 때 카운팅할 수 있도록 해 주고,
퀘스트의 구별을 위해서 int32 자료형의 QuestID 변수를 추가 해 주었습니다.
또한 퀘스트 데이터 테이블의 Accepted를 이용한 퀘스트의 수락을 구현해 보도록 하겠습니다.
데이터 테이블 수정
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "RPGQuestData.generated.h"
USTRUCT(BlueprintType)
struct FRPGQuestData : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
public:
FRPGQuestData()
: QuestName(TEXT(""))
, Description(TEXT(""))
, Accepted(false)
, Completed(false)
, QuestID(0)
{}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Quest)
FString QuestName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Quest)
FString Description;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Quest)
bool Accepted;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Quest)
bool Completed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Quest)
uint32 QuestID;
};
앞서 설명한 대로 데이터 테이블과 구조체 변수를 수정해 줍니다.
데이터 테이블의 경우 엑셀에서 내용을 수정한 경우 데이터 테이블 내부에서 리임포트 해 주어야
변경사항이 업데이트 됩니다.
퀘스트 메인 위젯 변경
퀘스트 메인 위젯을 변경하였습니다.
퀘스트 진행상황을 나타내기 위해 KilledCnt와 RequireCnt 텍스트를 추가로 배치했고
Description 텍스트 위에 퀘스트의 이름을 나타내기 위해 QuestName 텍스트를 추가로 배치했습니다.
퀘스트 슬롯 버튼 클릭으로 퀘스트 정보 나타내기
기본 설계
퀘스트 슬롯의 버튼을 누르면 해당 퀘스트 슬롯의 퀘스트ID를 정보를 반환하여
퀘스트 메인 위젯에서 해당 정보를 바탕으로 퀘스트 정보를 표시하고자 하였습니다.
처음에는 단순히 슬롯 위젯에 메인 위젯의 헤더파일을 추가해서 자신을 소유하고 있는 메인 위젯에게
퀘스트ID 값을 전송하려고 하였으나, 이미 메인 위젯에서 슬롯 위젯을 생성을 위해 슬롯 위젯의 헤더를
포함하고 있어 순환 종속성 에러가 발생하였습니다.
따라서 저는 델리게이트를 사용하여 메인 위젯이 각 슬롯 위젯을 구독하고
슬롯 위젯의 클릭 이벤트 발생 시 델리게이트를 브로드캐스팅하여 퀘스트ID 값을 넘기도록 하겠습니다.
퀘스트 슬롯 위젯 클래스
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "RPGUserWidget.h"
#include "Components/TextBlock.h"
#include "RPGQuestSlotWidget.generated.h"
DECLARE_MULTICAST_DELEGATE_OneParam(FOnQuestButtonClicked, int32 /*QuestID*/);
UCLASS()
class RPG_API URPGQuestSlotWidget : public URPGUserWidget
{
GENERATED_BODY()
public:
void SetQuestName(FString QuestName, int32 ID);
UFUNCTION(BlueprintCallable, Category = "Quest")
void ClickedQuestButton();
protected:
UPROPERTY(BlueprintReadOnly, Category = "Widget")
TObjectPtr<UTextBlock> QuestNameText;
UPROPERTY(BlueprintReadWrite, Category = "Quest")
int32 QuestID;
public:
FOnQuestButtonClicked OnQuestButtonClicked;
};
퀘스트 슬롯에서 하나의 인자를 포함한 델리게이트를 만들어 주었습니다.
QuestID를 전달하므로 int32형식으로 지정하였습니다.
또한 버튼이 클릭 되었을 때 실행시킬 ClickedQuestButton 함수를 선언하고 구현하였습니다.
void URPGQuestSlotWidget::ClickedQuestButton()
{
OnQuestButtonClicked.Broadcast(QuestID);
}
단순히 델리게이트를 브로드캐스팅 하기 위한 함수 입니다. BlueprintCallable로 선언하여 블루프린트의 OnClicked 이벤트에서 사용할 수 있습니다.
퀘스트 슬롯의 블루프린트에서 버튼 클릭시 해당 함수가 실행되어 자신을 구독한 퀘스트 메인 클래스에게
인자와 함께 버튼이 클릭 되었다는 사실을 보내줍니다.
퀘스트 메인 위젯 클래스
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "RPGUserWidget.h"
#include "Engine/DataTable.h"
#include "Components/VerticalBox.h"
#include "Components/TextBlock.h"
#include "RPGQuestData.h"
#include "RPGGameInstance.h"
#include "RPGQuestSlotWidget.h"
#include "RPGQuestMainWidget.generated.h"
/**
*
*/
UCLASS()
class RPG_API URPGQuestMainWidget : public URPGUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
void AddQuest(TSubclassOf<URPGQuestSlotWidget> QuestSlotWidgetClass);
void SelectQuest(int32 ID);
public:
UFUNCTION(BlueprintCallable, Category = "Quest")
void AcceptQuest();
protected:
UPROPERTY(BlueprintReadOnly, Category = "Widget")
TObjectPtr<class URPGQuestSlotWidget> QuestSlotWidget;
UPROPERTY(BlueprintReadOnly, Category = "Widget")
TObjectPtr<UVerticalBox> QuestList;
private:
UPROPERTY()
URPGGameInstance* RPGGameInstance;
int32 SelectedQuestID;
};
퀘스트 메인 위젯 클래스의 헤더에 변경점이 생겼습니다.
퀘스트ID를 저장하기 위해서 SelectedQuestID 변수를 추가하였습니다.
이는 퀘스트 슬롯의 버튼을 클릭하여 SelectQuest 변수 실행 시 매개변수로 받은 QuestID를 저장하는 변수로
현재 화면에 출력 할 퀘스트 정보를 지정하기 위해서 따로 저장합니다.
void URPGQuestMainWidget::AddQuest(TSubclassOf<URPGQuestSlotWidget> QuestSlotWidgetClass)
{
QuestList = Cast<UVerticalBox>(GetWidgetFromName(TEXT("QuestListVerticalBox")));
TArray<FRPGQuestData> QuestTable = RPGGameInstance->QuestTable;
for (FRPGQuestData Quest : QuestTable)
{
QuestSlotWidget = CreateWidget<URPGQuestSlotWidget>(GetWorld(), QuestSlotWidgetClass);
QuestSlotWidget->OnQuestButtonClicked.AddUObject(this, &URPGQuestMainWidget::SelectQuest);
QuestSlotWidget->SetQuestName(Quest.QuestName, Quest.QuestID);
QuestList->AddChild(QuestSlotWidget);
}
}
AddQuest 함수에서는 퀘스트 슬롯 위젯을 생성함과 동시에 해당 위젯의 델리게이트를 구독합니다.
버튼 클릭으로 델리게이트의 신호를 받으면 SelectQuest 함수가 실행될 것 이고 해당 함수는 매개변수로
QuestID를 받게 될 것 입니다.
또한 SetQuestName에 QuestName 뿐만이 아닌 QuestID를 넘겨주는 것 또한 변경점입니다.
void URPGQuestMainWidget::SelectQuest(int32 ID)
{
SelectedQuestID = ID;
UTextBlock* QuestNameText = Cast<UTextBlock>(GetWidgetFromName(TEXT("QuestNameText")));
ensure(QuestNameText);
UTextBlock* DescriptionText = Cast<UTextBlock>(GetWidgetFromName(TEXT("DescriptionText")));
ensure(DescriptionText);
UTextBlock* AcceptText = Cast<UTextBlock>(GetWidgetFromName(TEXT("AcceptText")));
ensure(AcceptText);
UTextBlock* KilledCntText = Cast<UTextBlock>(GetWidgetFromName(TEXT("KilledCntText")));
ensure(KilledCntText);
UTextBlock* RequireCntText = Cast<UTextBlock>(GetWidgetFromName(TEXT("RequireCntText")));
ensure(RequireCntText);
TArray<FRPGQuestData> QuestTable = RPGGameInstance->QuestTable;
for (FRPGQuestData Quest : QuestTable)
{
if (Quest.QuestID == SelectedQuestID)
{
// 퀘스트 이름
QuestNameText->SetText(FText::FromString(Quest.QuestName));
// 퀘스트 설명
DescriptionText->SetText(FText::FromString(Quest.Description));
// 잡은 몬스터 수
FString KilledCnt = FString::Printf(TEXT("%d"), Quest.KilledCnt);
KilledCntText->SetText(FText::FromString(KilledCnt));
// 잡아야할 몬스터 수
FString RequireCnt = FString::Printf(TEXT("%d"), Quest.RequireCnt);
RequireCntText->SetText(FText::FromString(RequireCnt));
// 수락 버튼
if (!Quest.Accepted)
{
AcceptText->SetText(FText::FromString(TEXT("ACCEPT")));
}
else if (Quest.Accepted)
{
AcceptText->SetText(FText::FromString(TEXT("COMPLETE")));
}
break;
}
}
}
퀘스트 슬롯 위젯 클릭 시 호출되는 함수 입니다.
매개변수로 받은 ID를 SelectedQuestID에 저장하고
범위기반 for문을 이용하여 해당 ID와 일치하는 구조체를 찾아냅니다.
만약 찾았다면 그 구조체가 화면에 표시할 구조체이므로 텍스트 블록의 내용을 변경하여
화면에 출력해 줍니다.
출력 과정이 끝났다면 더이상 반복문을 돌릴 필요가 없으므로 break를 이용해 빠져나갑니다.
구현결과
퀘스트 슬롯 클릭으로 표시되는 퀘스트 내용이 바뀌는 모습입니다.
퀘스트 수락하기
void URPGQuestMainWidget::AcceptQuest()
{
TArray<FRPGQuestData>& QuestTable = RPGGameInstance->QuestTable;
for (int32 i = 0; i < QuestTable.Num(); ++i)
{
if (QuestTable[i].QuestID == SelectedQuestID)
{
if (!QuestTable[i].Accepted)
{
QuestTable[i].Accepted = true;
UTextBlock* AcceptText = Cast<UTextBlock>(GetWidgetFromName(TEXT("AcceptText")));
AcceptText->SetText(FText::FromString(TEXT("COMPLETE")));
}
break;
}
}
}
Accept 버튼을 눌러 퀘스트를 수락하고 현재 퀘스트 ID 정보를 가진 구조체의 Accepted 값을 변경하기 위한 함수입니다.
게임 인스턴스의 QuestTable에 대해서 참조형으로 들고와서 구조체 배열을 직접적으로 변경할 수 있게 하였습니다.
이후 반복문을 이용해서 구조체 배열에서 일치하는 퀘스트ID를 가진 구조체를 찾았고
QuestTable[i].Accepted = true를 통해 퀘스트를 수락 처리 해 주었고
텍스트를 바로 업데이트 하기 위해서 SetText를 이용해 COMPLETE로 값을 바꿔 주었습니다.
마지막으로 퀘스트 메인 위젯 블루프린트의 AcceptButton의 클릭 이벤트와 함수를 연결해 주면 기능 구현이 끝나게 됩니다.
구현결과
다음과 같이 퀘스트를 누르고 Accept 버튼을 누르면 Complete로 바뀌는 모습을 볼 수 있습니다.
이제 다음 포스팅에서는 몬스터를 처치하면 카운트를 상승시키고,
목표 카운트를 달성한 후 COMPLETE키를 누른다면 보상으로 스탯 포인트를 지급하는 시스템을 구현해 보도록 하겠습니다.
감사합니다!
'[게임 개발] 개발 일지 > RPG' 카테고리의 다른 글
22. 몬스터 사망모션 캔슬 버그 수정 및 컨텐츠 추가 (0) | 2023.10.06 |
---|---|
21. 퀘스트 시스템 - 카운팅과 보상 (0) | 2023.10.06 |
19. 퀘스트 시스템 - 데이터 테이블 구축 (0) | 2023.10.05 |
18. 스탯 UI 구현하기 - 2 (0) | 2023.10.03 |
17. 스탯 UI 구현하기 - 1 (0) | 2023.10.02 |