自动化脚本编程框架(实战篇)

距离上一篇(代码篇)已经过去一年了,正好最近从零开发了一版脚本,于是借助这个框架来介绍介绍自动化脚本的实战篇。

由于本篇采用的C#编程(与C语言差不多),很难实现(理论篇)中的流程跳转,所以大概在架构上做了以下总结。

  1. 整体脚本框架需要分为主循环、初始化、异常处理三部分。
  2. 主循环,是脚本在达到可执行环境时期的主要工作内容。
  3. 初始化,是脚本从零开始到达到主循环条件的每一个步骤操作。
  4. 异常处理,是当主循环或初始化遇到错误时,能够迅速将脚本流程回归到初始化中的某个步骤上。

如此一来,正常情况下,脚本是在主循环中稳定运行的。当主循环遇到错误时,会进入异常处理流程,通过此流程分析,会将脚本回归到初始化流程中的某个步骤上,接着初始化会将脚本回归到主循环里。从而达到脚本的稳定运行。

整体流程图如下:

下面我以C#框架用大漠插件实现的一个脚本代码做实例说明:

注:本例不开放完整源码,仅拿出部分来做思路讲解。

先简单介绍下大漠插件做脚本的大体思想,大漠综合插件(dm.dll)采用vc6.0编写,是一款集前后台,文字识别,图色,键鼠,窗口,内存,DX,Call等功能于一身的综合插件。(百度百科)

这款插件做脚本的主要操作很简单,先findpic,然后进行keypress或者鼠标click。

早些年间我是用按键精灵做脚本的,但是他的编译器框架实在是,难以评价,结果近几年又突然收了费,像我们这些入网较早而且习惯白嫖的铁公鸡,那肯定是不肯贡献一分钱的,尽管我用了白嫖了他好几年。但做人要有原则!说白嫖就得白嫖到底!

于是,我就将所有脚本全都转移到了C#上,为什么选择C#,那必须是因为我自身知识水平的局限性。

好的,废话讲到这里吧,进入正题:

脚本的主要功能是实现元梦之星农场自动种菜收菜。

那么主流程就来了:自动种菜收菜。

for (int i = 0; i < land_num; i++)//农场所有土地
{
    //移动角色走到土地上
    _dm.MoveTo(SharedData.pixel_direction[0], SharedData.pixel_direction[1]);
    _dm.LeftDown();
    AIrobot.delay(10);
    _dm.MoveR(SharedData.land_every_parameter[i, 0], SharedData.land_every_parameter[i, 1]);
    AIrobot.delay(SharedData.land_every_parameter[i, 2]);
    _dm.LeftUp();
    AIrobot.delay(land_timedelay);

    //判断土地状态,是否需要浇水收割播种,judge_crops_state是判断并处理土地操作
    int dm_ret = judge_crops_state(_dm, crop_index, water_percent, if_attack, if_vip);
    while (dm_ret > 0)//如果dm_ret>0说明需要处理
    {
        if ((if_harvest == 1)&&(dm_ret == 3))
        {
            break;
        }
        _dm.MoveTo(pixel_action[0], pixel_action[1]);
        AIrobot.delay(10);
        _dm.LeftClick();
        AIrobot.delay(2000);
        dm_ret = judge_crops_state(_dm, crop_index, water_percent, if_attack, if_vip);
        AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "land_num = " + land_num + ", judge_crops_state = " + dm_ret);
    }
    //处理完毕,进入下一个循环,处理下一块地
}

注意:此例中的主流程缺少异常判断,因为主流程处理过快,所以为了保证脚本效率,无需在内部做异常判断,可在一轮执行完毕后再做判断。

主流程之外:

while(ture)
{
    //judge_stage_change_user函数用于检测当前脚本流程处于什么阶段
    if (judge_stage_change_user(_dm, false))//false为仅仅判断异常阶段,不执行纠正操作
    {
        judge_stage_change_user(_dm, true);//true为总体初始化流程
    }

    //主循环流程
    main_working();
}

judge_stage_change_user函数实际上是将初始化流程和异常判断流程合并到了一起,通过ture or false进行选择。

judge_stage_change_user中的脚本从零初始化部分流程如下:

//FindPicClick是寻找图片后是否点击,true为找到后点击,false为只查找不点击
if (AIrobot.FindPicClick(_dm, icon_logout, false) >= 0)//初始化步骤1
{
    _dm.MoveTo(pixel_empty[0], pixel_empty[1]);
    AIrobot.delay(10);
    _dm.LeftClick();
    wait_next_stage(_dm, 15, icon_closeonline, icon_closelive, icon_close, icon_farm);
}
for (int i = 0; i < 5; i++)//初始化步骤2
{
    if (AIrobot.FindPicClick(_dm, icon_closeonline, true) >= 0)
    {
        AIrobot.delay(1000);
    }
    if (AIrobot.FindPicClick(_dm, icon_closelive, true) >= 0)
    {
        AIrobot.delay(1000);
    }
    if (AIrobot.FindPicClick(_dm, icon_close, false) >= 0)
    {
        _dm.MoveTo(pixel_notice[0], pixel_notice[1]);
        AIrobot.delay(10);
        _dm.LeftClick();
        AIrobot.delay(500);
        if (AIrobot.FindPicClick(_dm, icon_close, true) >= 0)
        {
            AIrobot.delay(500);
        }
    }

}
if (AIrobot.FindPicClick(_dm, icon_farm, true) >= 0)//初始化步骤3
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "愚蠢的开场动画,重新进入星宝农场");
    wait_next_stage(_dm, 15, icon_money);
}
if (AIrobot.FindPicClick(_dm, icon_square, true) >= 0)//初始化步骤4
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "愚蠢的开场动画,重新进入星宝广场");
    wait_next_stage(_dm, 15, icon_farm);
}
//wait_next_stage为等待下一个阶段的图片被检测到,用于判断下一个阶段是否到达,缩短无判断的死等时间。

注意这里一个细节:下一个初始化步骤无需用上一个初始化步骤做先决条件,允许上一步没检测到,而直接执行下一步。

这么做的好处便是,你可以从任意一个阶段切入到初始化流程中,而无需从零开始一步一步判断先决条件。

judge_stage_change_user中的脚本异常处理部分流程如下:

if (AIrobot.FindPicClick(_dm, icon_logout, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到角色注销按钮!");
    return 3;
}
if (AIrobot.FindPicClick(_dm, icon_permit, true) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到允许登陆按钮!");
    return 11;
}
if (AIrobot.FindPicClick(_dm, icon_QQlog, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到QQ登录按钮!");
    return 4;
}
if (AIrobot.FindPicClick(_dm, icon_agree, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到同意登录按钮!");
    return 5;
}
if (AIrobot.FindPicClick(_dm, icon_closeonline, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到锦标赛在线观看关闭按钮!");
    return 12;
}
if (AIrobot.FindPicClick(_dm, icon_closelive, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到直播关闭按钮!");
    return 12;
}
if (AIrobot.FindPicClick(_dm, icon_close, false) >= 0)
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,检测到广告关闭按钮!");
    return 6;
}

注意:这里的大多数处理都是只对图片检测,而不做点击。因为点击按钮后会导致异常流程进入到下一个步骤里,但你又无法确保这个步骤一定成功,那就有可能继续诱发异常。

因此,异常处理流程里只做异常检测,返回检测到的状态,之后回归到初始化流程中,有初始化流程做下一步按钮点击,因为初始化流程中每一步都配带了下一个阶段的图片检测流程。

当然,异常处理中肯定也要有未知状态,因为环境复杂,你不可能检测到所有异常状态而且还都有对应的处理方案,所以,必须要有一个未知状态值,用于脚本返回到从零开始的步骤。

//判断到未知状态
if (AIrobot.FindPicClick(_dm, icon_money, false) >= 0)
{
    return 0;
}
else
{
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "角色异常,角色处于未知状态,等待1秒重新检测");
    AIrobot.delay(1000);
    return 0xff;
}



//在另一个线程中检测未知状态累计次数
if ((times >= time_limit) || (dm_ret_crash >= 0) || (dm_ret_exit >= 0) || (judge_stage_unknown_times >= 20))
{
    if (times >= time_limit)
    {
        AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "检测到屏幕卡死次数超过" + time_limit + "次,重启客户端!");
    }
    if (judge_stage_unknown_times >= 20)
    {
        AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "检测到连续相同未知状态次数" + judge_stage_unknown_times + "次,重启客户端!");
    }
    AIrobot.SaveScreen(dm_farm[1], hwnd_farm[0], "windows_dead");
    dm_farm[1].UnBindWindow();
    AIrobot.KillProcess(input_memu_title.Text);
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "杀死客户端!等待30秒,让游戏角色自动退出!");
    for (int i = 29; i>= 0; i--)
    {
        AIrobot.delay(1000);
        AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "杀死客户端等待剩余" + i + "秒");
    }
    AIrobot.StartProcess(Tencent_exe_path, input_tencent_parameter.Text);
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "启动客户端!等待30秒。");
    for (int i = 29; i >= 0; i--)
    {
        AIrobot.delay(1000);
        AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "启动客户端等待剩余" + i + "秒");
    }

    hwnd_clear();
    AIrobot.WriteLog(LEVEL_TYPE.DEBUG_TYPE, "重新获取客户端句柄!");
}
//进程全部杀死后,重新启动,并回归初始化流程

这里需要注意一点,不要检测到未知状态就立刻杀死进程,需要统计连续未知状态的次数。

因为你的脚本多数是用于网络游戏,(单机游戏有修改器,谁写脚本这破玩意),所以在你每个步骤中都可能存在网络延迟,会导致你的初始化流程检测不到下一步,或者卡在某个未知界面里好几秒,从而导致你检测自己到达了未知状态。

因此,未知状态是允许的,但是不允许连续多次。

以上,便是这次在实战篇中要讲的脚本框架三大部分。

ps:以我目前的实战经验来看,只能总结到这个地步了,或许过几年,对脚本有更深层次的理解之后,会回来把这些理论全部推翻。

即使可能会被自己推翻,但这个时刻这个背景下,我对脚本框架的理解,便是如此。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值