在前面的基础功能设计中,我们封装了基础的功能,这些基础的功能组合起来就是游戏的流程,也是游戏层设计需要做的事情。
在这里时候不能像案例分析那样设计了,需要更进一步的细化分解,这里我们再重新看一下打图的流程,然后我们详细看看怎么具体设计这个流程。
注意红色框出来的区域: 这两个区域,一个是角色的HP和MP不足,一个是任务提示的信息,这两个可以认为是角色的状态,那么我们需要进行下面的封装:
// 定义角色的状态
enum PlayerStatus
{
_ok = 0,// 角色状态正常
_hp, // 角色HP异常
_mp // 角色MP异常
};
PlayerStatus get_player_status();
// 定义任务状态
enum TaskStatus
{
_not_task = 0, // 角色没有任务
_task1, // 角色HP异常
_task2 // 角色MP异常
};
// 获取当前的NPC状态和任务状态
TaskStatus get_player_status();
// 重置NPC的状态
bool reset_npc_status();
不同的任务需要不同的道具,往往内部会对道具进行编号,然后来查询是否存在某些道具。
// 定义道具
enum PropType
{
_flags = 0, // 飞行符
_symbol, // 飞行符
_hp_add, // 增加hp道具
_mp_add // 增加mp道具
}
// 查找道具
bool get_find_prop(PropType type, int &x, int &y);
/*
这里一般会将所哟道具的截图和PropType意义对应起来,这样就可以实现任务扩展了。
*/
注意不是每次都可以移动到特定地图,很多地图可能需要走好几次:
这种情况下,最好的办法是将这些地图一一枚举,然后为它们设计如何从A到B,我的设计方案都是确定A ,在这个案例里面A的地点是长安酒店,接下来就是如何从长安酒店到其它所有的地图。
// 定义所有地图
enum PLACE
{
_AoLai = 0, // 傲来国
_DaTanGuo, // 大唐国境
_DaTanWai, // 大唐境外
_DongHai, // 东海湾
_HuaGuo, // 花果山
_JianYe, // 建邺城
_JiangNan, // 江南野外
_NvEr, // 女儿村
_PuTuo, // 普陀山
_ShiTuo, // 狮驼岭
_XiLiang, // 西梁女国
_Changan, // 长安城
_ChangShouCun, // 长寿村
_ChangShouWai, // 长寿郊外
_ZhuZi, // 朱紫国
_HuaSheng, // 化生寺
_DiFu, // 地府
_BeiJu, // 北俱芦洲
_NvWa, // 女蜗神迹
_WuZhang, // 五庄观
_PS, // 盘丝
_Haidi, // 海底迷宫
_Qiling, // 麒麟山
_Bx, // 宝象国
_HGS2, // 花果山门派
_DTGF, // 大唐官府
_MWZ, // 魔王寨
_TJC, // 天机城
_WDD, // 无底洞
_ZSS, // 战神山
_SML, // 神木林
_DHYD, // 东海岩洞
_NVB, // 女魃
_LG, // 龙宫
_XXT, // 小西天
_LBC, // 凌波城
_FCS, // 方寸山
_TG, // 天宫
_Reset
};
enum IMapMode
{
_move = 0, // 直接移动
_npc, // NPC对话
_flags,
_symbol
};
// 地图移动项
typedef struct _tagMapMove
{
int num; // 当前地图编号
POINT end; // 结束点的坐标
IMapMode mode; // 离开方式
int next_num; // 下一个地图的编号
struct _tagMapMove* next;
}IMapMove, *PIMapMove;
typedef struct _tagTempMaps
{
int _ids; // 地图ID
int _x; // 地图左上角起点x
int _y; // 地图左上角起点y
int _map_w; // 地图宽度
int _map_h; // 地图高度
PIMapMove pMove; // 移动方案
}ITempMaps, *PITempMaps;
// 设定地图和移动策略
g_iTempMaps[_AoLai ]._ids = _NvEr;
g_iTempMaps[_AoLai ]._x = 208;
g_iTempMaps[_AoLai ]._y = 173 ;
g_iTempMaps[_AoLai ]._map_w = 127;
g_iTempMaps[_AoLai ]._map_h = 143;
到达指定地图之后,还需要移动到特定的位置:
代码可能如下:
typedef struct _tagMAP_INFO
{
USHORT _start_x;
USHORT _start_y;
USHORT _rect1;
USHORT _rect1;
USHORT _rect2;
USHORT _rect2;
USHORT _scope_w;
USHORT _scope_h;
}IMAP_INFO, *PIMAP_INFO;
// 在上面的图中可以看出,在地图上移动需要输入对应的坐标,这就是 rect1和rect2的数值
bool set_move_to_map(int ids, int x, int y);
// 还需要编写能实时查询当前位置的函数
bool get_player_addr(int ids, int &x, int &y);
这个游戏不是走到NPC对面马上开始PK的,还需要再点击一次,如下图一样我们需要先找到NPC在图上的位置:
// 这里也是使用模板定位的方法,全局搜索即可。
bool find_npc_addr(Mat &src, NPCType type, int &x, int &y);
这里需要使用攻击的方式进入战斗,同时我们还要考虑如何判断进入战斗:
使用判断任务提示框是否存在就能判断是否进入战斗了。
这里其实可以直接设定好,然后调用自动按钮的:
就是左下角的位置,这里要注意,使用模板实时定位会比使用固定位置要好,虽然在这个游戏里面是使用的固定位置,但是使用实时定位会非常灵活。
判断任务完成其实比较简单,还是6里面的任务信息即可。当然在这里,我省略了后续任务进行的情况,因为后续任务和这个大同小异,也是到指定为止进行战斗。
闭环任务是在设计上额外添加的,因为游戏带有不确定性,所以要注意就是闭环任务和状态重置一定要有,它可以让整个任务形成一个闭环。
比如说如果我设定任务的闭环点在长安酒店,那么就是每次战斗借宿,我都会回到长安酒店,这样,我接下来所有的移动的起始点都是在长安酒店,那么位置移动就非常清晰,我只需要整理出从任意地图回到长安酒店的方案以及从长安酒店到任意地图的方案即可,不要去设计N:M的路径,要设定1:M和N:1的路径,这样路径之间的移动就是一个M+N的表格而不是一个MXN的表格。
同时每次闭环任务的时候,我都会清理一下状态,同时补充道具,这样我们设计流程的时候就会非常清晰,特别是重复350次的任务。
在我们之前的那张图中,我们设计了思维中枢、视觉中枢、运动中枢三个部分。但事实上上面的两层设计中,我们几乎没有考虑这个设计点,这是有原因的,因为我们考虑基础功能和游戏功能的时候,往往会直接考虑怎么完成。
比如说拿确定NPC的位置来举例,就是设计这个游戏功能的时候,我们考虑的是如何用基础功能组合出这个功能,但是我们不悔考虑它属于什么中枢。
那确定三个中枢的意义何在?这部分属于冗余代码,但是它可以便于我们从打图辅助扩展到押镖辅助或者扩展到其他游戏,例如天龙八部的其它辅助。
所以分模块设计和分层设计是这样统一起来: 分层设计会面对具体的实现,就是具体的任务A的某一步是如何调用基础功能实现的;分模块设计则是将任务抽象出来,形成一个逻辑上的模块,从而增加中间层,来更好的实现代码重用,这也是这部分冗余代码的用途。分层设计是对功能的具体实现,模块设计则是对功能本身的抽象实现。
我们来实践一下上面这段话:
当完成1~9之后,实际上这个辅助已经完成了,但是我们会发现,我们总能将一个任务分为N个子动作,这个动作流程就是类似于:
根据状态识别->反馈->运动->设置状态,它们反映到代码上就是:
typedef struct tagChildIntem
{
string start_state; // 这个子项的初始状态
string start_pose; // 此时的角色姿势
string start_name; // 要识别的模板名称
string motion; // 动作,例如x=322;y=322;l_c的含义是将鼠标移动到xy的位置并按下左键
string process_name; // 额外执行的流程
string end_name; // 结束的识别模板
string end_pose; // 结束时的角色姿势
string end_state; // 结束之后设置的状态
}ChildIntem;
// 将这个优化之后实现到xml文件就是
...
...
这个时候我们可以发现,任一个辅助的辅助,总是可以分解为N个ChildIntem,同时它们可以通过xml组合起来,于是辅助就实现了可以根据不同的xml的内容来实现不同的任务切换。
那么代码的侧重点就从设计游戏层变到如何实现对xml文件的解析和执行。
bool child_task_run(int type, int &out_type)
{
switch(type)
{
case 0:
{
// 重置状态
out_type = 1;
}
break;
case 1:
{
// 回到长安酒店
out_type = 2;
}
break;
case 2:
{
// 接任务
out_type = 3;
}
break;
case 3:
{
// 解析任务,填充移动参数
out_type = 4;
}
break;
case 4:
{
// 移动到指定地图
out_type = 5;
}
break;
case 5:
{
// 执行任务
out_type = 6;
}
break;
...
}
}
// 此时所谓闭环,就是执行完每次的任务之后,总能将状态切换到0;
这个时候的xml文件如下:
// xml项
// 长安酒店找到NPC接任务
“0”
“id:0;x=512;y=192”
"长安酒店"
""
"move;npc;info=id1+x1+y1"
"move;npc;info=id+x+y"
“id:0;x=512;y=192”
"1"
// 移动到指定地图
“1”
“id:0;x=512;y=192”
"长安酒店"
"move;id;"
""
"move;id1"
“id:id1”
"2"
// 移动到NPC附近
“2”
“id:id;x=0;y=0”
"id"
"move;x1,y1"
""
"move;x1,y1"
“id:id;x=x1;y=y1”
"3"
在这个文档中,我们首先对再次解析了任务的流程,并根据流程来设计了游戏层的接口,然后回到最开始设计,将三个中枢的设计思路引入设计,重新整理了整个项目的流程。