Auto.js编写游戏脚本入门

1 介绍

Auto.js is a UiAutomator on android, does not need root access(安卓平台上的JavaScript自动化工具)

看作者的github ID hyb1996应该是个90后大佬;由于Auto.js很适合用来写安卓脚本,被国内各种灰产滥用是少不了的,毕竟很多人靠这个赚钱。
我自己比较喜欢偶尔写一些游戏脚本用用,之前一直用按键精灵写脚本,找图找色功能比较好用,但需要设备有root权限,而且用久了发现有些功能实现起来还比较麻烦,故这次打算找个新平台尝试一下。Autojs由于是基于安卓的无障碍服务,只有个别命令需要root权限,此外还支持对控件的操作,功能看上去还挺强大。

因为我的需求是能够写游戏脚本,不像一般的APP可以直接操作控件,而只能通过基于坐标的操作完成。所以Autojs里关于控件的部分可以略过不看(除了多账号登录时的登录界面可能会用到一点控件操作,其他基本用不到),大致的学习流程:

  1. 软件安装及环境搭建(版本选择、运行测试)
  2. JavaScript基础语法学习(变量类型、流程操作...)
  3. Auto.js分模块学习,如系统交互(文件读写、设备信息、打开应用...)、找图找色(游戏脚本的核心)、UI界面、多线程...

2 安装及环境配置

我用的是最新的Pro版,去淘宝官方店买就行,也不贵;app安装好了就可以在手机上编脚本了,但由于效率低大多数情况都是在电脑上编连接手机(或模拟器)进行测试,官方是通过VSCODE的一个插件来实现这一交互的,虽然对像我这样的非VSCODE用户(Vim党)来说不够友好,但好像只有这一条路可走。
安装好后把Auto.js-Pro-Ext这个插件装上(Vim用户再装个vscodevim),随便创建个.js文件会发现右边有个绿色的小安卓图标,点击后(或者通过快捷键Ctrl+Shift+p)发现可以通过输入IP(和电脑同一网络)、adb(插USB)两种方式连接,手机的话一般连接不会有什么问题,连接成功的话右下角会有一行提示。
要注意的是如果使用的是模拟器,这时候的网络配置就有点麻烦了。比如我用的雷电模拟器,需要开启网络桥接模式、并设置好静态IP才能连接上,具体操作可以看这篇文章
(非Vim用户直接跳过就行)Vim用户可能需要配置下自己习惯了的常用快捷键,直接在插件的设置里打开settings.json文件改就行了,范例如下:

{
  "vim.easymotion": true,
  "vim.incsearch": true,
  "vim.useSystemClipboard": true,
  "vim.useCtrlKeys": true,
  "vim.hlsearch": true,
  "vim.insertModeKeyBindings": [
    {
      "before": [
        "<C-h>"
      ],
      "after": [
        "<left>"
      ]
    },
    {
      "before": [
        "<C-l>"
      ],
      "after": [
        "<right>"
      ]
    },
    {
      "before": [
        "<C-j>"
      ],
      "after": [
        "<down>"
      ]
    },
    {
      "before": [
        "<C-k>"
      ],
      "after": [
        "<up>"
      ]
    },
    {
      "before": [
        "<C-d>"
      ],
      "after": [
        "<Delete>"
      ]
    },
  ],
  "vim.normalModeKeyBindings": [
    {
      "before": [
        "E"
      ],
      "after": [
        "$"
      ]
    },
    {
      "before": [
        "H"
      ],
      "after": [
        "^"
      ]
    },
  ],
  "vim.visualModeKeyBindings": [
    {
      "before": [
        "E"
      ],
      "after": [
        "$"
      ]
    },
    {
      "before": [
        "H"
      ],
      "after": [
        "^"
      ]
    },
  ],
  "editor.formatOnSave": true,
}

连接上了之后当然是必不可少的hello world测试,常用的输出方式有3种:

toast() //输出到手机屏幕
log()   //输出到日志
toastLog()  //前两者之和
console.log()   //输出到控制台,console模块的一个成员函数,可以通过console.show()在手机上显示

3 JavaScript基础

推荐直接去w3school或者runoob过一遍,这里只列举一些我觉得要注意的地方。

  • 直观感受,很多语法和C\C++一模一样(除了声明变量要用var、函数要加function...)。

  • undefined:只声明过但未赋值的变量。

  • =====: 前者是equality、后者为identity,即后者不会进行类型转换,类型不同的结果一定不等。

  • JS的数据类型:

    var length = 7;                             // 数字
    var lastName = "Gates";                      // 字符串
    var cars = ["Porsche", "Volvo", "BMW"];         // 数组
    var x = {firstName:"Bill", lastName:"Gates"};    // 对象
  • null:类型是对象,不存在的事物。

    null === undefined            // false
    null == undefined             // true
  • 对象: 通过关键词 "new" 来声明 JavaScript 变量,则该变量会被创建为对象。

    //也可以通过person.firstName="..."的方式初始化
    var person = {
    firstName: "Bill",
    lastName : "Gates",
    id       : 678,
    fullName : function() {   //成员函数
      return this.firstName + " " + this.lastName;
    }
    };
  • 在字符串中换行,需要加一个反斜杠(非ECMAScript标准):

    document.getElementById("demo").innerHTML = "Hello \
    Kitty!";
  • StringNumber相加时,会将数字转为字符串;StringNumber相减时,会将字符串转为数字;

  • 数组添加元素: array.push("value")

  • 常用对象: Date() (获取时间)、 Math() (常用数学工具)

  • use strict;: 定义 JavaScript 代码应该以“严格模式”执行。

  • JavaScript的对象通过引用来寻址:

    //person是个对象
    var x = person;  // 这不会创建 person 的副本,x相当于person的别名,即引用

4 Auto.js常用命令及模块

下面分几个部分把编写游戏脚本时可能用到的模块全部讲一下吧,顺便对自己也是个学习完善的过程,全部学完花的时间可能比较多,自己也还有别的事要做,尽量每天写一点吧。
官方文档地址:https://hyb1996.github.io/AutoJs-Docs/#/

4.1 系统读写等操作

4.1.1 App、Device

  1. 启动应用,有两种方式:
  • app.launchApp(appName),通过应用名称启动,即图标下显示的名称;
  • app.launch(packageName),通过包名启动,关于如何获取应用的包名后面会讲到,举个栗子:app.launch("com.bilibili.priconne")
  1. 获取包名: app.getPackageName(appName),与此相对的还有根据包名获取应用名,举个栗子:

    appName = app.getPackageName("公主连结R")
    toastLog(appName)
    //输出:com.bilibili.priconne
  2. 读写系统剪切板

    setClip("将剪切板设置为这段话")
    //getClip()返回剪切板内容
    toastLog("当前剪切板:"+getClip())
  3. 等待应用界面出现,waitForPackage(package[, period = 200])

  4. 停止脚本: exit()

  5. 设置脚本需要的安卓版本号

    requiresApi(24)// 需要至少Android 7.0
    requiresAutojsVersion("3.0.0")//指定Autojs最低版本号
    //会自动检测当前版本号,若过低会报错
  6. 获取屏幕分辨率

    var Width=device.width;
    var Height=device.height;
    toastLog(Width+"x"+Height);
  7. 获取设备识别码
    Autojs里常用的设备标识是IMEIANDROID_IDIMEI(International Mobile Equipment Identity)是15位数字组成的国际移动设备身份码,写在主板上和硬件绑定,就算刷机也没法改变。AndroidId 是一串64位的编码(十六进制字符串),是随机生成的设备的第一个引导,通过它可以知道设备的寿命;在设备恢复出厂设置或刷机后,该值可能会改变,故和系统绑定。
    当脚本要商业化的时候,可以通过设备码区分不同用户;如果脚本写出来只是一个人用,获取设备码应该也没啥用。

    var deviceId=device.getIMEI();
    var androidId=device.getAndroidId();
    toastLog(deviceId+"\n"+ androidId);
  8. Device模块有很多功能,比如调整音量、屏幕亮度、获取内存和电量...但感觉写游戏脚本都用不到所以直接跳过吧。

4.1.2 Files

稍微复杂一点的脚本可能涉及到日志记录,比如刷了多少次图、有没有error之类,所以对文件的处理还是挺有用的。

  1. 返回路径

    var path1=files.cwd();   //当前工作文件夹绝对路径
    var path2=files.path(".");  //相对路径转绝对路径
    //还有一些不太常用的如files.getSdcardPath()、files.listDir(path[, filter])
  2. 文件(夹)操作(返回bool)

    var path="/sdcard/newFolder/"
    if(!files.create(path)){}   //创建文件或文件夹,若已存在则返回false,(需要上一级文件夹已存在)
    files.createWithDirs("/sdcard/newFolder1/newFolder2")//若不存在会创建一系列文件夹,比上一个命令更强大
    //还有些如删除文件、判断空文件夹等功能、略过。
  3. 文件读写(和python好像- -)

    var path = "/sdcard/1.txt";
    var text = "Hello, AutoJs";
    //以写入模式打开文件
    var file = open(path, "w");
    //open函数还可以指定文件编码
    // "r":读, "w":写 , "a":从文件末尾附加
    file.write(text);   //还可以:file.writeline()、file.writelines()
    // var text=file.read() ,或者file.readlines()
    file.close();

4.1.3 Shell

Autojs有shell函数和shell对象,前者适合执行单次命令,后者由于使用同一个shell进程所以对多条命令效率高:

//shell函数,返回result对象;result.code返回码,成功为0;result.result,运行结果(字符串);result.error,无错误时为空,若未获取到root权限可能返回"Permission denied"。
var result=shell("ls",true);
if(result.code==0){toast("执行成功");}

//shell对象
var sh=new Shell(true); //构造函数
sh.exec("ls");  //执行命令,注意该函数没有返回值!!  并且命令执行是"异步"的,不会等待上一步执行完成。
sh.exit();  //强制退出
sh.exitAndWaitFor();    //等待执行完成并退出

Android常用shell命令

  • Input

    input text 'hello world'
    input keyevent 26  # Powerkey
  • am

    # 感觉对写脚本没太大帮助...
    am start ...
    am kill ...
    am force-stop ...
    am restart ...
  • pm

    pm list packages #列出所有APP包,这个命令感觉有点用
    pm install ...
    pm uninstall ...
  • wm

    wm size #查看屏幕尺寸
    wm size 720x1280 # 修改尺寸
    wm size reset #还原

4.2 游戏脚本核心

4.2.1 触摸模拟(基于坐标)、Keys

  1. sleep(n):暂停运行n毫秒,游戏脚本中最常出现的一个命令;有一些操作必须给它一定的缓冲时间才能正确运行完成。

  2. 随机数: random(min,max)指定区间、random()范围是[0,1);点击坐标时常设置一个随机偏移,防止被检测到(每次都点同一个点还是太明显了)

  3. 分辨率适配问题,setScreenMetrics(1080, 1920);、表示脚本适合的屏幕宽高为1080x1920(编脚本时基于的设备),如果在别的分辨率手机上运行则会自动放缩光标。听上去很好的一个功能,一般游戏脚本必加这一行,但具体效果如何我没有测试过。

  4. click(x,y):点击坐标(无需root权限),返回是否成功,点击过程大约150ms,可能被其他事件中断。更长时间的点击如longClick(x,y)、持续600ms。

  5. press(x,y,duartion):按住坐标,一般超过500ms才被系统认为是长按。

  6. swipe(x1,y1,x2,y2,duration): 从(x1,y1)滑动到(x2,y2),持续duration。

  7. RootAutomator
    上面的几个触摸操作都是免root的,而基于RootAutomator对象的触摸需要root权限,优点是执行没有延迟,明显比click要快。

    var ra=new RootAutomator(); //初始化一个对象
    ra.tap(x,y,id); //id代表不同“手指”,用于多点触摸,不需要时可省略该参数
    ra.swipe(x1,y1,x2,y2,duration,id)
    ra.press(x,y,duration,id)
    // 这些命令组合在一起就能完成复杂的操作了~
    ra.touchDown(x,y,id)
    ra.touchMove(x,y,id)
    ra.touchUp(id)
  8. 模拟按键;(返回bool值)

    if(back()){};    //按下返回键
    home()  //返回桌面
    还有一些需要root权限的,开头字母大写:
    //Home()、Back()、Power()、Menu()、OK()、KeyCode()...

4.2.2 colors、images

colors

颜色常用十六进制值或RGB值来表示,如蓝色可表示为#0000FF(0,0,255),一般都是#后面带6位十六进制数,分别表示R、G、B,但Autojs是8位,前面多了一个A(Alpha)、表示透明度,即ARGB。

  1. Autojs通过一个16进制整数或一个字符串表示一个颜色,两者可以互相转换

    var myBlue=colors.toString(color.BLUE); //返回#ff0000ff,colors.BLUE代表蓝色,后面必须大写。
    var numBlue=colors.parseColor("#ff0000ff"); //返回-16776961,至于为什么是这个数我也不清楚,平时还是用字符串表示比较好。
  2. colors对象里还有一些判断两个颜色的相似度、返回A、R、G、B通道值的函数,平时也基本上用不上;颜色的用途主要体现在后面的多点找色上。

images

游戏脚本的灵魂所在,images主要有图片处理、找图、找色几个部分;想让脚本识别游戏的某个区域、如果该区域的位置是固定的,通过构造多点比色比较快,而如果位置不固定则常用找图的方式,虽然占用资源比较多但准确性有保障。

  1. images对象使用完后必须回收,防止内存泄漏。

    var img=images.read("./name.png");  //读取图片,错误时返回null
    //var img=images.load(url); //从网址获取图片
    //...图片操作后回收
    img.recycle();
    // 例外:captureScreen()返回的图片无需回收
  2. images对象能对图像进行复制、保存、Base64编码解码、剪切、调整大小、放缩、旋转、拼接、灰度化、阈值化、颜色控件转换、二值化、模糊与平滑处理、滤波...(强是很强大,就是基本上用不上)

  3. 获取截图权限:在找图找色之前往往要先获取当前屏幕的截图,这个截图一般是临时的、不会保存到文件(也可以设置保存)。 截图之前要向系统申请一次截图权限:

    if(!requestScreenCapture()){    //可指定参数true(横屏截图) 或者 false(竖屏截图)
     toast("请求截图失败");
     exit();
    }
  4. 请求截图: captureScreen

    //在此之前记住要请求一次截图权限
    var img=captureScreen();    //可以指定保存路径path
  5. 颜色获取,很重要的一个函数,后面多点找色时可以先用它获取参数值。

    //获取某点的ARGB颜色值
    var color=images.pixel(img,100,200);    //img是之前创建的images对象
  6. 区域找色(一种颜色);findColorfindColorInRegionfindColorEquals

//首先说下region和threshold这两个参数,后面的找色函数options里都要用到:
//region、找色区域,默认全图、指定[x,y]代表左上角点,从(x,y)到右下角;指定[x,y,width,height]则代表从(x,y)到(x+width,y+height)。
//threshold、相似度临界值,0~255,默认为4;similarity=1-threshold/255,可以算出默认相似度达到了0.98,觉得太严了可以适当增大threshold
var point = images.findColor(img, "#ff0000", { region: [100, 200], threshold: 10 });
//如果找到则返回一个点,如:{463.0, 1242.0};找不到返回null。
//这里颜色值是6位,8位也行不过会忽略A通道(透明度)。
// findColorInRegion,功能和findColor一样,只是优化了下参数表示。
var point=images.findColorInRegion(img,"#ff0000",100,200,1080,1920,10);
// findColorEquals,要求颜色完全相等,相当于findColor的threshold参数设为0
var point=images.findColorEquals(img,"#ff0000",100,200,1080,1920);
  1. 多点找色:findMultiColors,先定位第一个点的颜色、根据(x,y)偏移获取并对比第二个点的颜色...以此类推,命令很麻烦,通常需要写一个函数来构造颜色列。

    var point = images.findMultiColors(img, "#ff949fc7",    //第一个点
     [[60, 60, "#ffe6efe6"], //颜色Array,
     [60, -60, "#ffeef3e6"],
     [-60, 60, "#ffe6efe6"],
     [-60, -60, "#ffeef3e6"]],
     { region: [1548, 803, 140, 140] })  //指定区域
  2. 检测某坐标颜色:前几个命令都是根据颜色找坐标,这个是给坐标、比较颜色

    if(images.detectsColor(img,"#fed9a8",100,200,16,"diff")){}
    //最后两个参数可省略,代表threshold和匹配算法;x=100,y=200
  3. 找图:有时候找颜色会匹配到一些奇怪的地方去,还得用找图来实现,有images.findImageimages.findImageInRegionimages.matchTemplate

    var temp1=images.read(pathToImage);
    var point=images.findImage(img,temp1,{ region: [100, 200], threshold: 10 });
    //同样findImageInRegion只是优化了下参数排列
    //matchTemplate可以同时返回找到的多个位置,通过max控制最大的结果数量
    var result=images.matchTemplate(img,temp1,{ region: [100, 200], threshold: 10 ,max:5});
    //返回类型是一个MatchingResult对象,有point和similarity这两个数据成员。

4.3 脚本结构管理

4.3.1 module

在一个文件里通过module.exports =...;把某个对象导出,从而可以在另一个文件通过var name=require('file.js');导入;相当于把整个文件当做一个函数,把exports的东西当做返回值。感觉用起来也不太方便,我选择不用这个功能。

4.3.2 Threads

  1. 启动一个子线程,threads.start

    //启动一个无限循环的线程
    var thread = threads.start(function(){  //用thread对象可以控制线程运行状态,如果不需要操作可以改为:
    //threads.start(function(){
     while(true){
         log("子线程运行中...");
         sleep(1000);
     }
    });
    sleep(5000);
    thread.interrupt();
  2. threads.shutDownAll(): 停止所有通过threads.start()启动的子线程

  3. 等待线程开始执行(一般start后需要一段时间):thread.waitFor();(这里thread是前面创建的thread变量)

  4. 等待线程执行完成:thread.join();,参数可以指定一个等待时间

  5. 中断线程运行:thread.interrupt();

  6. 注意多线程中的变量问题,涉及到线程安全,文档里说的很详细

  7. 线程间的通信与传递变量,通过var connect = threads.disposable();实现;发送结果:connect.setAndNotify(s);,接收结果:connect.blockedGet(s);

4.4 交互界面

4.4.1 Events、Dialogs

  1. Events模块主要用来监听按键、触摸、通知等,但放在单线程里可能会因为程序其他部分而无法及时执行,造成非预期的结果,常常和多线程Threads模块一起使用,如音量键关闭脚本的例子:

    auto();
    threads.start(function(){ //在子线程中调用observeKey()从而使按键事件处理在子线程执行
     events.observeKey();    //启用按键监听
     events.on("key_down", function(keyCode, events){
     //常用事件有key、key_down、key_up、exit、toast、notification、touch(触摸某点)
         if(keyCode == keys.volume_up){  //音量上键关闭脚本
             exit();
         }
     });
    });
    events.on("exit", function(){   //脚本停止运行时会触发exit事件
     toast("脚本已结束");
    });
    while(true){
     log("脚本运行中...");
     sleep(2000);
    }
  2. Dialogs

4.4.2 Floaty、Console

4.4.3 UI



未完...

Author: zcp
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source zcp !
 Current
Auto.js编写游戏脚本入门
1 介绍 Auto.js is a UiAutomator on android, does not need root access(安卓平台上的JavaScript自动化工具) 看作者的github ID hyb1996应
Next 
log4cpp和gdb学习
最近的状态:感觉什么都没干但时间过得飞快。 好多要学的课程要写的代码,毕设进度也还不够,加上偶尔打打游戏不想学习,自然花在blog上的时间就更少了。 1 log4cpp 介绍 最近开始准备写些C++的东西,因为是在lin
  TOC