文章目录
- 任务详情的设计
- 任务表的设计
- TaskManager类
- 自动接任务
- 自动完成任务
- 自动任务
- 最后的总结
之前我们已经完成了自动打怪,自动技能和自动吃药的功能,这一次我们来完成自动任务的功能。自动任务的难度要比前三个高很多,涉及到要处理的细节也很多,我这里写的也不是百分百完美的,只是提供一个基本的思路,如果想要实现一个完美的自动任务的脚本,还需要花时间慢慢完善。
首先新建一个类,命名为TaskManager
任务详情的设计
所谓的自动任务,就是将角色从1级到满级所有的任务全部的数据,记录到一张表。然后挨个遍历并执行。这样的话,就要求我们设计表的信息足够多,并且算法足够强大。这样才能够用一套算法去自动完成所有的任务。
首先需要设计一个任务详情结构体,这个结构体需要包含下面的字段:
- 任务名字
- 任务条件
- 接任务的 等级
- 需要杀死的怪物数量
- 任务的地图
- 任务地图的坐标
- 交接任务的NPC
- 选择的奖励
根据上面的信息,定义出这样一个结构体
//任务详情表
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游戏通用数据的定位思路,和一个完整的辅助框架的设计流程。当遇到下一个游戏的时候,只需要将准备好的数据,加入到框架的代码里就可以了。如果需要添加新的功能,记得遵循数据和代码逻辑分离的思想,方便后续的复用。
好的,各位,拜了个拜~