文章目录

  • 任务详情的设计
  • 任务表的设计
  • TaskManager类
  • 自动接任务
  • 自动完成任务
  • 自动任务
  • 最后的总结


之前我们已经完成了自动打怪,自动技能和自动吃药的功能,这一次我们来完成自动任务的功能。自动任务的难度要比前三个高很多,涉及到要处理的细节也很多,我这里写的也不是百分百完美的,只是提供一个基本的思路,如果想要实现一个完美的自动任务的脚本,还需要花时间慢慢完善。

首先新建一个类,命名为TaskManager

任务详情的设计

所谓的自动任务,就是将角色从1级到满级所有的任务全部的数据,记录到一张表。然后挨个遍历并执行。这样的话,就要求我们设计表的信息足够多,并且算法足够强大。这样才能够用一套算法去自动完成所有的任务。

首先需要设计一个任务详情结构体,这个结构体需要包含下面的字段:

  1. 任务名字
  2. 任务条件
  3. 接任务的 等级
  4. 需要杀死的怪物数量
  5. 任务的地图
  6. 任务地图的坐标
  7. 交接任务的NPC
  8. 选择的奖励

根据上面的信息,定义出这样一个结构体

//任务详情表
struct _stuTaskInfo
{
	//任务名字
	wstring m_Name;

	//任务ID
	DWORD m_ID;

	//交任务的NPC
	wstring m_FinishNpcName;
};

还需要有一个任务内容,但是任务内容涉及的东西比较多,所以要单独定义一个结构体。

//任务类型
enum TaskTypeEnum
{
	Em_对话,
	Em_打怪,
	Em_收集,
    Em_行为,
};

首先加一个任务类型的结构体,相关的类型后面慢慢添加。

//任务内容
struct _stuTaskWorkInfo
{
	//任务类型
	TaskTypeEnum m_WorkType;

	//需要对话的NPC名字 或者是攻击的怪物的名字或者是收集物品的名字
	wstring m_Name;

	//完成任务需要的数量
	DWORD m_Count;
};

//任务内容
vector<_stuTaskWorkInfo> m_WorkList;

然后完成任务内容的结构体,把这个结构体的数组放到任务详情表里面。然后是任务奖励。

//任务奖励
struct _stuTaskAward
{
   //奖励名称
   wstring m_Name;

   DWORD m_ChoosePos;
};


//任务奖励
vector<_stuTaskAward> m_AwardList;

任务详情表,尽可能加上所有需要的数据。然后再将所有的任务详情定义一个数组,把所有的数据封装进去,就可以通过这个表来完成自动任务了。

上面的是需要我们去完成的任务的结结构体,再加一个可接任务的结构体,当角色没有任务的时候通过这个结构体去接任务。

//可接任务
struct _stuCanGetTaskInfo
{
	//任务名字
	wstring m_Name;

	//任务ID
	DWORD m_ID;

	//接任务的NPC
	wstring m_AcceptNpcName;

	//接任务的等级
	DWORD m_AcceptLevel;
};

可接任务的结构体要比任务详情结构体简单。

任务表的设计

接着准备两张表,一个是已接任务的表,一个是可接任务的表。当前游戏的任务列表如下,这里记录四个

苏醒(一) 去广场拜见村长南苍公
苏醒(二) 去村中集市找阿九
苏醒(三) F9打开背包界面 装备武器木枪
苏醒(四) 去农田打败干枯的稻草人

准备的任务表如下:

//任务表
vector<_stuTaskInfo> g_TaskList=
{
	{/* 任务名字*/L"苏醒(一)",/* 任务ID*/0,/* 交任务的NPC*/L"南苍公",/* 任务内容*/{{Em_对话,L"南苍公",1}},/*是否需要奖励*/FALSE,/* 任务奖励*/{{0}}},
	{/* 任务名字*/L"苏醒(二)",/* 任务ID*/0,/* 交任务的NPC*/L"阿九",/* 任务内容*/{{Em_对话,L"阿九",1}},/*是否需要奖励*/FALSE,/* 任务奖励*/{{0}}},
	{/* 任务名字*/L"苏醒(三)",/* 任务ID*/0,/* 交任务的NPC*/L"阿九",/* 任务内容*/{{Em_行为,L"使用装备",1}},/*是否需要奖励*/FALSE,/* 任务奖励*/{{0}}},
	{/* 任务名字*/L"苏醒(四)",/* 任务ID*/0,/* 交任务的NPC*/L"阿九",/* 任务内容*/{{Em_打怪,L"稻草人",5}},/*是否需要奖励*/FALSE,/* 任务奖励*/{{0}}},
};	


//可接任务表
vector<_stuCanGetTaskInfo> g_CanGetTaskList=
{
	{/* 任务名字*/L"苏醒(一)",/* 任务ID*/0,/* 接任务的NPC*/L"南苍公",/* 接任务的等级*/1},
	{/* 任务名字*/L"苏醒(二)",/* 任务ID*/0,/* 接任务的NPC*/L"南苍公",/* 接任务的等级*/1},
	{/* 任务名字*/L"苏醒(三)",/* 任务ID*/0,/* 接任务的NPC*/L"阿九",/* 接任务的等级*/1},
	{/* 任务名字*/L"苏醒(四)",/* 任务ID*/0,/* 接任务的NPC*/L"阿九",/* 接任务的等级*/1},
};

任务ID这里暂时不填,另外任务内容,还需要增加一个行为表,来处理苏醒三这种任务。

TaskManager类

这个类需要完成两个函数。首先从当前的已接任务列表中,获取一个任务详情对象。代码如下:

//遍历已接任务列表 获取一个任务详情对象
BOOL TaskManager::GetWorkTask(_stuObjs GetedTaskList, vector<_stuTaskInfo> TaskInfoList, _stuTaskInfo& outTaskInfo, _stuObj& outTaskObj)
{
	//遍历自己设置的任务列表
	for (auto TaskInfo: TaskInfoList)
	{
		_stuObj RoleTask;
		
		//如果名字不为空 通过名字获取任务对象
		if (TaskInfo.m_Name !=L"")
		{
			//在已接任务数据中获取任务对象
			RoleTask = GetedTaskList.GetDataByName(TaskInfo.m_Name);

		}
		//如果ID不为空 通过ID获取任务对象
		if (TaskInfo.m_ID)
		{
			RoleTask = GetedTaskList.GetDataByID(TaskInfo.m_ID);
		}

		//如果没有这个任务 循环下一个
		if (RoleTask.m_ID==0)
		{
			continue;
		}


		//如果有 则返回这个任务的任务详情对象和已接任务对象
		outTaskInfo=TaskInfo;
		outTaskObj = RoleTask;
		return TRUE;
	}
	return FALSE;
}

这样我们就拿到了一个存在于全局任务表里面的已接任务对象了,这代表这个任务是我们当前需要完成的。

接着如果没有任务,则需要从可接任务里获取一个任务对象,代码如下

BOOL TaskManager::GetCanAcceptTask(_stuObjs CanGetTaskList, vector<_stuCanGetTaskInfo> AcceptTaskList, _stuCanGetTaskInfo& outTaskInfo, _stuObj& outTaskObj)
{
	_stuObj CanGetTask;

	//遍历可接任务数组 判断是否需要接任务
	for (auto TaskInfo : AcceptTaskList)
	{
		//如果任务表的ID不为0
		if (TaskInfo.m_ID)
		{
			//则通过ID去可接任务列表中找到任务对象
			CanGetTask = CanGetTaskList.GetDataByID(TaskInfo.m_ID);

		}

		//如果名字不如空
		if (TaskInfo.m_Name != L"")
		{
			//则通过ID去可接任务列表中找到任务对象
			CanGetTask = CanGetTaskList.GetDataByName(TaskInfo.m_Name);
		}



		//如果没有这个任务 循环下一个
		if (CanGetTask.m_ID == 0)
		{
			continue;
		}


		//如果有 则返回这个任务的任务详情对象和已接任务对象
		outTaskInfo = TaskInfo;
		outTaskObj = CanGetTask;
		return TRUE;

	}

	return FALSE;
}

自动接任务

接着来编写我们的自动任务逻辑

//自动任务的线程函数
DWORD AutoTaskProc(LPARAM lParam)
{
	while (true)
	{ 

		//获取当前可接任务列表
		auto CanGetTaskList= GetCanGetTaskData();

		//任务管理器
		TaskManager taskManager;
		//可接任务详情
		_stuCanGetTaskInfo taskinfo;

		//任务对象
		_stuObj taskobj;

		//获取一个可接任务
		BOOL IsGetTask = taskManager.GetCanAcceptTask(CanGetTaskList, g_CanGetTaskList, taskinfo, taskobj);

		//如果获取到了可接任务
		if (IsGetTask)
		{
			//开始去接任务(寻路到NPC的位置 接任务)
		}


	}
}

自动任务的逻辑如上,首先需要获取到可接任务,如果获取到了可接任务,就要去NPC的位置去接任务,那么这里就还需要添加一个NPC的数据表。

//NPC数据表
map<wstring, _stuPos> g_NpcDataList =
{
	{L"南苍公",{0,0,0}},
	{L"阿九",{0,0,0}},
	{L"XXX",{0,0,0}},
};

这里用map的数据来存储所有的NPC的结构,NPC的坐标可以通过周围遍历去获取,这里逐个记录就可以了。

然后实现一个寻路到NPC的call

//寻路到NPC
void Fn_RunToNpc(wstring NpcName, map<wstring, _stuPos> NpcList)
{
	//开始去接任务(寻路到NPC的位置 接任务)
	auto NpcData = NpcList.find(NpcName);

	if (NpcData != end(g_NpcDataList))
	{
		//调用寻路Call 走到NPC附近
		Fn_RunToTarget(NpcData->second.x, NpcData->second.y, 1);

		//等待角色寻路完成 判断角色坐标是否不再产生变化
		_stuPos LastPos;
		while (true)
		{
			//获取角色信息
			_stuObj RoleData = GetRoleData();
			//判断最后一次的坐标是否和角色坐标相等 只要有一个不相等 说明角色在寻路
			if (RoleData.m_Obj_Pos.x!= LastPos.x||RoleData.m_Obj_Pos.y != LastPos.y||RoleData.m_Obj_Pos.z != LastPos.z)
			{
				//角色在寻路 记录上一次坐标
				LastPos.x = RoleData.m_Obj_Pos.x;
				LastPos.y = RoleData.m_Obj_Pos.y;
				LastPos.z = RoleData.m_Obj_Pos.z;
			}
			else
			{
				//如果三个坐标都相等 则退出循环
				break;
			}
	
			Sleep(3000);
		}
	}
}

这样就可以实现接任务的逻辑了。

//如果获取到了可接任务
		if (IsGetTask)
		{
			//寻路到NPC
			Fn_RunToNpc(taskinfo.m_AcceptNpcName, g_NpcDataList);

			//接任务
			Fn_AcceptTask(taskinfo.m_ID);

		}

自动接取任务,逻辑不复杂,但是整个流程如果要自己一个一个去踩坑,就会比较麻烦了。

自动完成任务

接下来就是自动完成任务的逻辑

//获取已接任务列表
		auto GetedTaskList = GetGetedTaskData();

		//需要做的任务详情
		_stuTaskInfo WorkTaskinfo;

		//需要做的任务对象
		_stuObj WorkTaskobj;


		//获取一个需要做的任务
		BOOL IsGetWorkTask = taskManager.GetNeedWorkTask(GetedTaskList, g_TaskList, WorkTaskinfo, WorkTaskobj);

		//如果获取到了需要做的任务
		if (IsGetWorkTask)
		{
			//判断任务是否已经完成
			if (taskobj.m_Task_Finish)
			{
				//寻路到NPC
				Fn_RunToNpc(WorkTaskinfo.m_FinishNpcName, g_NpcDataList);

				//交任务
				Fn_CompleteTask(WorkTaskinfo.m_ID);

			}
			else
			{
				//任务没有完成 需要去做这个任务 
				int index = 0;
				//遍历存储任务详情的vector
				for (auto Work : WorkTaskinfo.m_WorkList)
				{
					switch (Work.m_WorkType)
					{
					case Em_对话:
						break;
					case Em_打怪:
						break;
					case Em_收集:
						break;
					case Em_行为:
						break;
					default:
						break;
					}
				}
			}
		}

自动任务

完整的自动任务逻辑如下:

DWORD AutoTaskProc(LPARAM lParam)
{
	while (true)
	{ 
		//获取当前可接任务列表
		auto CanGetTaskList= GetCanGetTaskData();

		//任务管理器
		TaskManager taskManager;
		//可接任务详情
		_stuCanGetTaskInfo taskinfo;

		//任务对象
		_stuObj taskobj;

		//获取一个可接任务
		BOOL IsGetTask = taskManager.GetCanAcceptTask(CanGetTaskList, g_CanGetTaskList, taskinfo, taskobj);

		//如果获取到了可接任务
		if (IsGetTask)
		{
			//寻路到NPC
			Fn_RunToNpc(taskinfo.m_AcceptNpcName, g_NpcDataList);

			//接任务
			Fn_AcceptTask(taskinfo.m_ID);
		}

		//然后开始去做任务

		//获取已接任务列表
		auto GetedTaskList = GetGetedTaskData();

		//需要做的任务详情
		_stuTaskInfo WorkTaskinfo;

		//需要做的任务对象
		_stuObj WorkTaskobj;


		//获取一个需要做的任务
		BOOL IsGetWorkTask = taskManager.GetNeedWorkTask(GetedTaskList, g_TaskList, WorkTaskinfo, WorkTaskobj);

		//如果获取到了需要做的任务
		if (IsGetWorkTask)
		{
			//判断任务是否已经完成
			if (taskobj.m_Task_Finish)
			{
				//寻路到NPC
				Fn_RunToNpc(WorkTaskinfo.m_FinishNpcName, g_NpcDataList);

				//交任务
				Fn_CompleteTask(WorkTaskinfo.m_ID);

			}
			else
			{
				//任务没有完成 需要去做这个任务 
				int index = 0;
				//遍历存储任务详情的vector
				for (auto Work : WorkTaskinfo.m_WorkList)
				{
					switch (Work.m_WorkType)
					{
					case Em_对话:
						break;
					case Em_打怪:
						break;
					case Em_收集:
						break;
					case Em_行为:
						break;
					default:
						break;
					}
				}
			}
		}

	}
}

到这里整个自动任务的框架就已经完成了,我没有去填充实际的数据和做具体的功能测试(其实是因为调试器到期了),代码框架大体就是这样。各位如果想要完成一个完整可用的自动任务的功能,还需要慢慢往这个框架里增加数据和逻辑,并且处理好过程中可能会遇到的问题。

最后的总结

这个系列的文章到这里就已经全部结束了,这个系列的文章给各位提供了RPG游戏通用数据的定位思路,和一个完整的辅助框架的设计流程。当遇到下一个游戏的时候,只需要将准备好的数据,加入到框架的代码里就可以了。如果需要添加新的功能,记得遵循数据和代码逻辑分离的思想,方便后续的复用。

好的,各位,拜了个拜~