当前位置: 首页 > article >正文

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,因此任务完成: 

 输出:

 


http://www.kler.cn/a/536970.html

相关文章:

  • 3. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--环境搭建
  • android 适配 api 35(android 15) 遇到的问题
  • 完美解决phpstudy安装后mysql无法启动
  • 【GitLab CI/CD 实践】从 0 到 1 搭建高效自动化部署流程
  • 初窥强大,AI识别技术实现图像转文字(OCR技术)
  • 语言月赛 202311【基因】题解(AC)
  • zephyr devicetree
  • Android 多环境(生产、测试、开发)多域名网络配置
  • 一次报警了解:direct path read、enq: KO - fast object checkpoint
  • 【C语言】文件操作详解 - 从打开到关闭
  • STM32的HAL库开发---高级定时器---输出比较模式实验
  • Java 多线程、线程同步、线程池
  • C# LiteDB 使用教程
  • Qt实现简易音乐播放器
  • 脚手架开发【实战教程】prompts + fs-extra
  • MySQL视图索引操作
  • 【Linux】Ubuntu Linux 系统 ——Android开发环境
  • linux进程通讯-信号处理介绍
  • [开源/教程]使用Ollama+ESP32实现本地对话助手(可接入deepseek等模型)
  • 基于微信平台的报刊订阅小程序的设计与实现ssm+论文源码调试讲解
  • 新注册的域名无法访问,是怎么回事?
  • “AI隐患识别系统,安全多了道“智能护盾”
  • 鸿蒙UI(ArkUI-方舟UI框架)- 设置组件导航和页面路由
  • 青少年编程与数学 02-008 Pyhon语言编程基础 24课题、正则表达式
  • MES系统对于中小型制造企业有什么价值?
  • verilog练习:8bit移位寄存器