UE求职Demo开发日志#23 线性任务系统数据层实现
1 按上期设计创建数据结构(做了一些修改)
USTRUCT(BlueprintType)
struct ARPG_CPLUS_API FQuestNode
{
GENERATED_USTRUCT_BODY()
// 记录前置节点ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNode")
TArray<int> PredecessorNodeIDs;
// 区分检查前置条件类型(任意一个完成或全部完成)
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNode")
bool bRequireAllPredecessors;
// 记录唯一后置节点ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNode")
int SuccessorNodeID;
// 记录当前节点ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNode")
int NodeID;
// 标记是否完成
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNode")
bool bIsCompleted;
};
// 任务条件检查结构
USTRUCT(BlueprintType)
struct ARPG_CPLUS_API FQuestCondition
{
GENERATED_USTRUCT_BODY()
// 当前值
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CurrentValue;
// 需要达到的值
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float NeededValue;
// 物品ID,0表示空信息
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int ItemID{0};
// 描述文本(可以为空)
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString DescriptionText;
};
USTRUCT(BlueprintType)
struct ARPG_CPLUS_API FQuestNodeData : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
// 当前节点ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNodeData")
int NodeID;
// 任务提示文本
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNodeData")
FString QuestHintText;
// 任务条件检查类型
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNodeData")
EQuestType QuestType{EQuestType::None};
// 任务条件检查结构数组
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestNodeData")
TArray<FQuestCondition> Conditions;
};
USTRUCT(BlueprintType)
struct ARPG_CPLUS_API FQuestStructure : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
// 任务ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestStructure")
int QuestID;
// 当前正在执行的任务节点ID
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestStructure")
int CurrentNodeID;
// 任务节点数组
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestStructure")
TArray<FQuestNode> QuestNodes;
// 结尾任务节点ID数组(任意一个完成即整个任务完成)
UPROPERTY(EditAnywhere, BlueprintReadWrite,Category="QuestStructure")
TArray<int> EndNodeIDs;
};
2 创建对应的DataTable
3 实现任务逻辑
3.1 创建变量记录任务
UPROPERTY(EditDefaultsOnly,BlueprintReadWrite,Category="QuestManagerComponent")
TArray<FQuestStructure> CurrentQuests{};
UPROPERTY(EditDefaultsOnly,BlueprintReadWrite,Category="QuestManagerComponent")
TArray<FQuestNodeData> CurrentQuestNodes{};
UPROPERTY(EditDefaultsOnly,BlueprintReadWrite,Category="QuestManagerComponent")
TArray<FQuestStructure> CompletedQuests{};
3.2 要用到的执行函数
。。太多了请看VCR
// Fill out your copyright notice in the Description page of Project Settings.
#include "Quest/QuestManagerComponent.h"
// Sets default values for this component's properties
UQuestManagerComponent::UQuestManagerComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// 加载 DataTable
static ConstructorHelpers::FObjectFinder<UDataTable> QuestStructureDataTableFinder(TEXT("/Game/Data/DataTable/QuestStructureDataTable.QuestStructureDataTable"));
if (QuestStructureDataTableFinder.Succeeded())
{
QuestStructureDataTable = QuestStructureDataTableFinder.Object;
}
// 加载 DataTable
static ConstructorHelpers::FObjectFinder<UDataTable> QuestNodeDataTableFinder(TEXT("/Game/Data/DataTable/QuestNodeDataTable.QuestNodeDataTable"));
if (QuestNodeDataTableFinder.Succeeded())
{
QuestNodeDataTable = QuestNodeDataTableFinder.Object;
}
// ...
}
// Called when the game starts
void UQuestManagerComponent::BeginPlay()
{
Super::BeginPlay();
LogCurrentQuests();
LogCurrentQuestNodesData();
AcceptQuest(1);
LogCurrentQuests();
LogCurrentQuestNodesData();
if(CurrentQuestNodesData.Num() >= 1)
{
for(auto& t:CurrentQuestNodesData[0].Conditions)
{
t.CurrentValue=t.NeededValue;
}
}
UE_LOG(LogTemp,Warning,TEXT("CheckQuestNodeCompleted(1)==%d"),CheckQuestNodeCompleted(1));
//CompleteQuest(1);
LogCurrentQuests();
LogCurrentQuestNodesData();
// ...
}
// Called every frame
void UQuestManagerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// ...
}
bool UQuestManagerComponent::AcceptQuest(int QuestId)
{
if(!QuestStructureDataTable||!QuestNodeDataTable)return false;//DataTable无效返回false
TArray<FName> QuestStructureRowNames= QuestStructureDataTable->GetRowNames();
for (const auto& RowName : QuestStructureRowNames)
{
FQuestStructure* RowData = QuestStructureDataTable->FindRow<FQuestStructure>(RowName,FString(""),true);
if(RowData->QuestID==QuestId)//找到对应的任务
{
AddToCurrentQuestsWithoutRepeat(*RowData);//添加到现有任务数组
if(RowData->QuestNodes.Num()>0)
{
TArray<FName> QuestNodeDataRowNames= QuestNodeDataTable->GetRowNames();
for(FQuestNode QuestNode : RowData->QuestNodes)//遍历当前任务里的所有任务节点
{
for (const auto& QuestNodeDataRowName : QuestNodeDataRowNames)
{
FQuestNodeData* QuestNodeDataRowData=QuestNodeDataTable->FindRow<FQuestNodeData>(QuestNodeDataRowName,FString(""),true);
if(QuestNodeDataRowData&&QuestNodeDataRowData->NodeID==QuestNode.NodeID)//找到任务节点数据
{
AddToCurrentQuestNodesDataWithoutRepeat(*QuestNodeDataRowData);//添加到任务数据数组
break;
}
}
}
}
return true;
}
}
return false;
}
bool UQuestManagerComponent::AddToCurrentQuestsWithoutRepeat(FQuestStructure StructureToAdd)
{
for(FQuestStructure QuestStructure:CurrentQuests)
{
if(QuestStructure.QuestID==StructureToAdd.QuestID)
{
return false;
}
}
CurrentQuests.Add(StructureToAdd);
return true;
}
bool UQuestManagerComponent::AddToCurrentQuestNodesDataWithoutRepeat(FQuestNodeData NodeDataToAdd)
{
for(FQuestNodeData QuestNodeData : CurrentQuestNodesData)
{
if(QuestNodeData.NodeID==NodeDataToAdd.NodeID)
{
return false;
}
}
CurrentQuestNodesData.Add(NodeDataToAdd);
return true;
}
void UQuestManagerComponent::LogCurrentQuestNodesData()
{
UE_LOG(LogTemp,Warning,TEXT("LogCurrentQuestNodesData-->Start,Num==%d"),CurrentQuestNodesData.Num());
for(FQuestNodeData QuestNodeData:CurrentQuestNodesData)
{
UE_LOG(LogTemp,Warning,TEXT("Quest Node Id: %d"),QuestNodeData.NodeID);
UE_LOG(LogTemp,Warning,TEXT("Quest Node QuestHintText: %s"),*QuestNodeData.QuestHintText);
UE_LOG(LogTemp,Warning,TEXT("Quest Node QuestType: %d"),QuestNodeData.QuestType);
for(FQuestCondition QuestCondition:QuestNodeData.Conditions)
{
UE_LOG(LogTemp,Warning,TEXT("Quest Node Condition:CurrentValue==%f NeededValue==%f itemID==%d DescriptionText==%s"),QuestCondition.CurrentValue,QuestCondition.NeededValue,QuestCondition.ItemID,*QuestCondition.DescriptionText);
}
}
UE_LOG(LogTemp,Warning,TEXT("LogCurrentQuestNodesData-->End"));
}
void UQuestManagerComponent::LogCurrentQuests()
{
UE_LOG(LogTemp,Warning,TEXT("LogCurrentQuests-->Start,Num==%d"),CurrentQuests.Num());
for(FQuestStructure QuestStructure:CurrentQuests)
{
UE_LOG(LogTemp,Warning,TEXT("Quest Id: %d"),QuestStructure.QuestID);
UE_LOG(LogTemp,Warning,TEXT("Quest CurrentNodeID: %d"),QuestStructure.CurrentNodeID);
for(FQuestNode QuestNode:QuestStructure.QuestNodes)
{
UE_LOG(LogTemp,Warning,TEXT("Quest NodeID:%d"),QuestNode.NodeID);
}
for(int endid:QuestStructure.EndNodeIDs)
{
UE_LOG(LogTemp,Warning,TEXT("Quest EndNodeID:%d"),endid);
}
}
UE_LOG(LogTemp,Warning,TEXT("LogCurrentQuests-->End"));
}
bool UQuestManagerComponent::CompleteQuest(int QuestId)
{
for(int index=0;index<CurrentQuests.Num();index++)
{
if(CurrentQuests[index].QuestID==QuestId)
{
CurrentQuests.RemoveAt(index);
//暂时不移除QuestNodesData
return true;
}
}
return false;
}
bool UQuestManagerComponent::CheckQuestNodeCompleted(int QuestNodeID)
{
for(const auto& QuestNodeData:CurrentQuestNodesData)
{
if(QuestNodeData.NodeID==QuestNodeID)
{
for(const auto& QuestCondition:QuestNodeData.Conditions)
{
if(QuestCondition.CurrentValue<QuestCondition.NeededValue||QuestCondition.CurrentValue==0)
{
return false;
}
}
return true;//所有条件均CurrentValue>=NeededValue
}
}
//没找到指定id返回false
return false;
}
4 测试
测试代码:
void UQuestManagerComponent::BeginPlay()
{
Super::BeginPlay();
LogCurrentQuests();
LogCurrentQuestNodesData();
AcceptQuest(1);
LogCurrentQuests();
LogCurrentQuestNodesData();
if(CurrentQuestNodesData.Num() >= 1)
{
for(auto& t:CurrentQuestNodesData[0].Conditions)
{
t.CurrentValue=t.NeededValue;
}
}
UE_LOG(LogTemp,Warning,TEXT("CheckQuestNodeCompleted(1)==%d"),CheckQuestNodeCompleted(1));
CompleteQuest(1);
LogCurrentQuests();
LogCurrentQuestNodesData();
// ...
}
读取前:
接受任务1后:
手动完成任务节点1的任务:
if(CurrentQuestNodesData.Num() >= 1)
{
for(auto& t:CurrentQuestNodesData[0].Conditions)
{
t.CurrentValue=t.NeededValue;
}
}
此时check任务节点1:
UE_LOG(LogTemp,Warning,TEXT("CheckQuestNodeCompleted(1)==%d"),CheckQuestNodeCompleted(1));
之后完成任务,看到已被清除:
这里写的完成任务是最终调用,不能直接调用,要包装条件检查
5 (补充)任务面板数据显示,提交任务测试
//TODO:
//1.任务数据持久化
//2.对外提供修改指定任务节点的Value的接口
//3.完成任务后加入已完成队列,记录所有完成任务信息
//4.按照任务类型细化UI显示,比如item类任务显示item图标等信息
//5.添加任务奖励,并在任务完成时获得物品或者执行其他逻辑
这里的UI设计的是动态的,全部绑定了任务系统的信息,更新信息写在了函数里,手动调用
测试的时候手动把完成条件全部满足了,大部分时间用来实现了,这里就不多写实现过程了,就是获取数据->显示数据。
提交函数:
若通过检查并且是EndNode里的任意一个,完成整个任务
若通过检查但不是EndNode里的,设置CurrentNodeID为后继节点ID
bool UQuestManagerComponent::TryCommitQuestCurrentNode(int QuestId)
{
for(auto& Quest:CurrentQuests)
{
if(Quest.QuestID==QuestId)
{
if(CheckQuestNodeCompleted(Quest.CurrentNodeID))
{
if(Quest.EndNodeIDs.Contains(Quest.CurrentNodeID))
{
UE_LOG(LogTemp,Warning,TEXT("TryCommitQuestCurrentNode-->Success!CompletedId: %d,QuestId:%d Completed"),Quest.CurrentNodeID,QuestId);
CompleteQuest(QuestId);
return true;
}
UE_LOG(LogTemp,Warning,TEXT("TryCommitQuestCurrentNode-->Success!CompletedId: %d"),Quest.CurrentNodeID);
Quest.CurrentNodeID=GetQuestNodeByNodeID(Quest,Quest.CurrentNodeID).SuccessorNodeID;
UE_LOG(LogTemp,Warning,TEXT("NextId: %d"),Quest.CurrentNodeID);
return true;
}
UE_LOG(LogTemp,Warning,TEXT("TryCommitQuestCurrentNode-->Failed Because Check is False."));
return false;
}
}
UE_LOG(LogTemp,Warning,TEXT("TryCommitQuestCurrentNode-->Failed Because QuestID is Not Found."));
return false;
}
初始(代码里接取了任务1,按钮绑定了提交任务1函数):
提交一次后:
提交两次后,因为属于整个任务的结尾Node,因此任务完成:
输出: