疫情开不了学,在家无事,遂写脚本以用促学、练习python

一 脚本开发

游戏:手游 公主连接
环境:雷电模拟器
控制:Adb

游戏分析:

1 游戏模式:
战斗方式:选择角色编为队伍,己方队伍与NPC或其他玩家队伍自动战斗;
角色获取:可通过充值货币抽奖,或者游戏内货币逐步兑换
角色提升:通过游戏内货币强化技能,或者与NPC战斗掉落装备强化
该游戏中只有点击一种操作方式

2 游戏模块:
七个一级模块(页面):主页、角色、剧情、冒险、NPC公会、扭蛋、主菜单;七个模块的入口在游戏工具栏,在任何页面中工具栏始终保持显示。一级模块(页面)中有其他子模块的入口。

一级模块

子模块

主页

资源购买、游戏货币商店、玩家公会、任务奖励、礼物领取

角色

角色信息、角色强化、角色预组队

剧情

内置动画

冒险

游戏战斗;NPC战斗分为:主线、探索、地下城、团队战四种,玩家间战斗分为:竞技场、公主竞技场两种。

NPC公会

领取奖励

扭蛋

抽取角色、领取奖励

主菜单

游戏设置

脚本开发与使用:
1 工具类:

知识点:
ADB

游戏的实际控制要通过adb指令实现,将要用到的Adb指令都封装为函数,打包成Utils模块。
每个业务模块类创建一个工具类成员变量。通过调用函数来进行操作而不是直接传递Adb指令。

2 账号控制:

有多个游戏账号要控制,每个账号要进行的任务可以不同。在主程序中建立账号和账号信息的字典,账号信息中存储账号密码、任务列表,遍历该列表即可。

3 业务模块:

为每个一级菜单(模块)在项目中创建一个对应文件夹,菜单那下每个子模块对应一个类。内容最多的是主页模块、冒险模块。

主页模块 mainpage:
主页中一部分按钮直接提供功能,一部分按钮是子模块的入口
主页界面功能: 购买体力、购买Mana
主页子模块: 商店模块ShopsManager、公会(外部)模块OrganizationManager、任务奖励模块TaskManager、礼物模块GiftManager

冒险模块 adventure :
战斗场景子模块: MainlineManager、ExploreManager、DungeonsManager、OrganizationBattleManager、ArenaManager、PrincessArenaManager、ActivityManager
战斗方式子模块: ManualBattleManager、AutoBattleManager、TeamManager
战斗场景类创建手动/自动战斗类成员变量,通过手动/自动战斗类进行战斗。

游戏战斗方式有手动和自动两种;主线、探索场景中,再次挑战已通关的关卡时可使用自动战斗,其他所有情况只能手动战斗。手动战斗中,可以临时编组队伍,或调用预编组队伍。所谓手动和自动区别只在于是否手动选择角色,另外手动会显示战斗过程、自动会省略过程,其实过程仍是自动进行。

4 python脚本发布为exe

知识点:
游戏脚本中的python_python打包为exe

5 window定时任务

知识点:
Windows定时任务

每天定时执行脚本exe文件

难点:
1 关卡选择:

关卡选择分两步:一是识别当前关卡,二是进入目标关卡。
对于识别关卡,起初打算调用大漠或乐玩DLL的识字功能识别关卡的编号,经测试效果很差;后来,考虑到每个关卡在地图上位置是固定的,因此可以直接得到关卡与坐标的映射。每个战斗场景的类负责记录该场景下关卡与坐标的映射。
每次进入游戏时,都默认停在上次的关卡处;由于无法直接识别当前关卡号,每次进入游戏后手动回到第一关,以此为基准选择关卡。

二 第一次迭代

1.修改:工具类

上版: 每个业务类都会实例化一个工具类作为成员变量;浪费内存
改进: 所有业务类共享一个工具类实例
实现: 主程序中创建工具类的实例对象,当主程序中创建业务类对象时,将工具类对象的引用作为参数传入,成为业务类的成员变量。当业务类创建其他下级业务类时也同理。

备注:

  1. 工具类只干了两件事,一是:记录要使用的模拟器索引,供Adb指令使用 ,二是:封装Adb操作;全局的模拟器索引是唯一的,封装后的函数是通用的,所有业务类共享一个工具类就够了。
  2. 要实现全局共享一个实例,首先想到的是单例模式,但是单例模式指的是:一个文件中某个类多次实例化只创建一个对象,而这里是要多个.py文件中某个类只创建一个对象,两种情景不同。
  3. 怎么使多个文件中使用同一个对象?想到了Spring中的依赖注入。依赖注入时,是把容器中已存在的对象赋值给变量了,那类似的,这里应该采用将一个文件(类)中创建的对象传给其他文件(类)的方式,想到了在初始化时作为参数传入的方式。
2.增加:配置文件、配置文件解析类

知识点:
游戏脚本中的python_配置文件、json文件、序列化、反序列化

上版: 每个战斗场景类的成员变量中写死了关卡与坐标的映射,每个账号的任务列表也是写死的;无法修改
改进: 账号信息、按钮坐标都放到配置文件中
实现: 用 json文件作为配置文件,配置文件解析类将配置文件反序列化为对应的信息类,业务类初始化时传入信息类,从中获取成员变量的值。

上版使用中发现的问题:
1.账号任务无法修改:比如,打算今天刷1-5关,明天刷6-10关;再比如,今天不练小号
2.坐标映射无法修改:比如,有的关卡坐标写错了
每次进行上述修改,都必须改源码后重新打包程序为exe文件。【改进后只要修改配置文件,并重启脚本即可】

备注:

  1. 配置文件反序列化后有两种使用方式,一是:数据转为字典中的元素,二是:数据转为类的属性。作为类属性,在编程时会有自动提示,比较方便。
  2. 目前,为每一个业务类创建一个信息类,信息类的层级结构对应业务类的层级,如:冒险信息类、主线地图类、地下城地图类等。这样使用起来方便,但是信息类构建时显得很繁琐,考虑能不能精简层级,将主线信息、地下城信息直接写在冒险信息类中,不再创建主线地图类、地下城地图类。

三 第二次迭代

1.修改:工具类

知识点:
游戏脚本中的python_实例方法、类方法、静态方法

上版: 业务类初始化时传入工具类作为参数;
问题: 业务类在语义上很工具类没有直接的关系,将其作为创建时的参数感觉上很别扭;

改进: 工具类中方法设为静态方法,业务类中通过类名调用;
实现: 工具类中将模拟器索引设为类属性,所有方法设为静态方法。主程序中为工具类的模拟器索引属性赋值,业务类通过类名调用工具类的静态方法。

备注:

  1. 工具类中方法也可以设为类方法。感觉上python中类方法、静态方法相差不大,仅在于对类属性的使用方式上。
2.增加:日志文件、日志文件解析类

知识点:
游戏脚本中的python_目录、文件、路径操作
游戏脚本中的python_文件读写、编码与解码
游戏脚本中的python_python时间操作

上版: 脚本固定从头到尾运行;
问题: 当人为中断或Bug中断后重新启动时,脚本会重新执行已完成的任务,而有些任务每天只能执行一次,导致误触或空操作浪费时间。、

改进: 使用日志文件记录已完成的模块,启动时读取日志,跳过已完成的模块;
实现: 由于有多个账号且每个账号要执行多个任务模块,采用每个账号在日志文件中占一行的方式,每行包括:时间、账号、已完成模块。每个账号最后一个模块执行完成后负责换行。

四 第三次迭代

1.修改:配置文件、配置文件解析类

知识点:
游戏脚本中的python_实例方法、类方法、静态方法

上版: 游戏模式设置、账号任务设置、按钮位置映射全部写在一个配置文件中;
问题: 配置文件庞杂、结构混乱,要修改某个配置不方便;

改进: 拆分为三个配置文件:游戏模式、账号任务、按钮位置。且将按钮位置解析类的属性设为类属性。
实现: 所有按钮位置映射类,如:MainlineMap、DungeonsMap,用类属性记录配置文件中的信息,业务类通过类名、属性名直接访问;解析类不需要实例化。

2.修改:日志工具类

上版: 由每个账号最后一个模块负责换行;
问题: 当人为中断或Bug中断存在时,若还未执行到最后一个模块就发生了中断,重启脚本后会接着上次末尾写入日志,导致日志混乱;
改进: 每次账号登录就换行,不再由业务模块负责换行;
实现: 读取日志时,将整个日志中同账号的执行记录汇总,作为已完成任务清单;

上版: 每个模块写入日志时时,末尾带空格,开头不带空格;
问题: 当人为修改日志时(例如为了该次跳过某个任务),若忘了添加空格,重启脚本后会接着日志末尾写入,导致两次模块的记录连在一起,从而记录失效;
改进: 每个模块写入log时,开头末尾均带空格。

备注:

  1. 可见在写入文件时,不应以文件中格式正常为前提,这里就是默认为以上次写入后已做好分割了,应该无论文档格式是否正常,均保证本次写入数据有效。
3.增加:战斗功能接口

上版: 将具体战斗功能(手动和自动)抽离作为工具类,各战斗场景类直接调用;
问题: 各战斗场景中手动战斗细节有区别,调用手动战斗工具类时要将场景名作为参数传入,手动战斗工具类中用大量if来根据场景名选择要执行的代码;显得臃肿
改进: 创建一个战斗场景接口,各场景类继承自该接口,接口中实现完全相同的方法,抽象定义手动战斗方法,由各战斗场景类自己实现;
实现: 创建BattleScene接口;取消之前的ManualBattleManager、AutoBattleManager;

备注:

  1. 接口类的好处:1.代码复用 2.规范了各战斗场景类的方法名 3.反映各战斗场景类之间的关系;

五 第四次迭代

之前的版本中只有脚本向游戏发送指令,没有脚本从游戏中获取信息的过程,也就是说信息是单向的;此情况下,通过重复执行操作、操作后预留足够长时间,来保证操作能切实进行且没有相互干预,也就是实现脚本和游戏的同步。这种控制方式显然过于僵硬,要想实时控制必须让脚本接收游戏中的信息,也就是识图、识色、识字。

识图、识字在开发最开始就尝试过。大漠插件、乐玩插件识字功能远没有预想中好用,主要是范围稍大就无法识别出有文字。而python中图像和文字ocr处理模块分别对应cv2、Tesseract-OCR库。
实际使用中,还是会发现cv2、Tesseract-OCR的识图、识色、识字并不稳定,如对同一个界面截图识别指定区域内的RGB平均值,多次操作结果有波动并不完全相同;又如识字建立字库后还是会出现识别不到字。
综上,还是以硬编码为主,实时识别为辅。

1.增加:颜色识别工具类

游戏脚本中的python_opencv识图、识色
ADB

上版: 脚本不从游戏中提取信息,通过冗余执行操作、预留足够长时间,实现脚本和游戏的同步;
问题: 控制方式过于僵硬,浪费时间,若网络卡顿仍会导致操作错位;
改进: 各节点场景均有特有标志,识别特有标志是否存在以判断是否处于该场景。
实现: 上述判断为二分类判断,即只需判断场景中有无指定标志,而不是判断场景中有什么东西。且该游戏中指定标志均含有纯色部分,所以只需截图后截取该部分并判断平均颜色值是否为指定颜色即可。由于识别时有波动,可以取色后,以上下25%的波动为阈值。

备注:

  1. 微信截图工具有取色器功能;
  2. 截图只作为临时文件,不需要像log那样保存。
2.修改:战斗功能接口

游戏脚本中的python_python中的数据结构库pythonds

上版: 没有对战斗失败的处理,无论战斗成功或失败均点击该关卡指定次数;
问题: 一些战斗场景中,关卡失败会导致后面关卡无法进行;
改进: 战斗失败后修改执行队伍并重新加入栈顶,再次执行,失败三次后跳过该关卡;
实现: 对于失败的任务,可以失败后立刻再次执行,也可以全部任务完成后再统一执行,逻辑上前者更合适,因此改用栈存储任务清单,失败的任务压入栈顶。【之前是用列表存储任务清单,由于列表迭代过程中无法修改,失败任务必须用另一个列表存储,远不如栈合适】

上版: 关卡以手动还是自动模式进行需要在配置文件中设置;
改进: 使用识色工具,自动识别关卡能否以自动战斗模式进行,若能则自动,否则手动;
实现: 利用识色工具识别可手动战斗情况下的特有标志即可。