👿 这破小游戏又氪又肝,这不得自己写个"辅助"?

2,858 阅读13分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

0x1、迎接灵感乍现的瞬间

周末和往常一样,摊在沙发上玩手机,王者N连跪后刷起了抖音,然后刷到这类"睿智"小游戏的广告 (原广告是射箭升级,没找着,不过都差不多):

手滑一点,直接跳转微信小程序,直接就开始了:

打怪升级学技能,是男人就过一百关,没看到充钱入口,难道真的如广告说的那样 良心零氪

2333,事实证明还是想太多,新手教程完,一个 6元首冲 直接甩我脸上:

这熟悉的味道,让我想起几年前氪金近千元的"恐龙战记"小游戏,只是后面官方跑路,游戏入口都找不到了。狗才冲,劳资就要 白嫖!!!

旺旺旺,也就六块,游戏公司也要赚钱,不然哪来的钱 买好的服务器,提升玩家体验,对吧?资瓷下没毛病~

上手体验一周,笔者对这款游戏的评价如下:不氪金寸步难行想白嫖先肝死你破服务器卡死人客服鸟都不鸟你。咳咳,当然也不是完全不能玩,这不 沉没成本 ,肝就肝点咯~

说到要做 费阳寿 的日常任务,我猛然想起自己可是 练习时长两年半的自动化阳寿节约者 —— CodingBoy

017991324d7f9ed9797395d4e18083e6 (1).gif

这不得写上一个 做日常的自动化辅助 解放双手?立马安排~


0x2、最小化产品——先写个简易脚本

这个游戏里的做日常,其实就是 刷图,一直刷直到所有体力用完,大概的操作流程如下图:

简化下刷图的核心流程:

  • 如果是技能加点页,点击其中一个技能;
  • 滑动滑块,向前走;
  • 暂停一下,让角色自己打怪;
  • 滑动滑块,继续向前走,直到到达终点;

接着就是循环执行上述流程,随手用 Airtest IDE 写出简陋脚本:

代码非常简单,如果是技能加点,点击第一个技能坐标,不是则向前走两步,运行看看效果:

简单测试了两关好像可以,就挂着没理它搬砖去了,后面摸鱼时发现,会中途退出关卡???

排查一波,发现是 障碍物 堵住了角色前进的道路,卡在这关太久,超时自动退出了:

淦(gan),看到这里可能有些读者会说:你直接 录制一遍你通关的过程不就好了

其实 Airtest支持录制脚本,好像可以?实际并不行,你能想到的,游戏公司也想到了,这些 障碍物是随机生成的,所以得想办法避障,一种比较粗暴的解决方式:

  • 每走两步都记录一下任务的坐标点,然后比较y坐标是否发生改变,没改变说明是遇到障碍物了;
  • 往左走一步,然后再尝试往上走两步,y坐标还是不变的话,再往左;
  • 如果向左x坐标不变说明左侧也遇到障碍物了,此时换成往右走,同样尝试往上走,y坐标不变,再往右;
  • 如果向右x坐标也不变,说明右侧也遇到障碍物了,此时往后退一步,然后重复上述过程。

可以,但太慢了,人物一直进进退退,跟个呆逼一样,得想出更好的办法 避障


0x3、寻找更好的 "避障" 方法

第一时间想到以前读书时做过的编程题——迷宫问题,就下面这样的:

把迷宫 矩阵化,障碍物用1表示,通道用0表示,然后入口走到出口,提前得出要走的路线,一步到位。

那怎么让程序知道图片上哪些点是障碍物呢?立马想到一个思路:

拿到 背景底图,跟 当前截图,比较下每个像素点的色值,相同位置颜色值不一样,说明是障碍物。

① 尝试获取背景底图

行吧,那怎么拿到底吐呢?按照我之前的认知:

小游戏本质上也是小程序,而小程序在第一次运行时,会把部分资源Download到本地。

尝试抓下包,打开微信,把小游戏删除,打开搜索,在建议使用找到小游戏,点击后查看请求:

微信采用ZSTD算法对小程序代码包压缩,不知道素材在不在这,加密了肯定是拿不到的。

对了,我之前折腾小程序写了篇:《我写小程序像菜虚鲲——2、鸡你太美》,里面有记录逆向微信小程序的过程。

准备一台Root安卓鸡,来到小程序源文件存放处:

/data/data/com.tencent.mm/MicroMsg/{UserId}/appbrand/pkg

因为上面删过小游戏,按照创建时间排序,相邻的几个应该就是我们的目标:

导到电脑上,用 wxappUnpacker 直接拆包,最后发现只发现一个图片:

其他都是js,unity相关的东西,搜了一下才知道,微信小程序有一款Unity WebGL转换微信小游戏的插件 minigame-unity-webgl-transform,感情这游戏就是直接转的?怪不得优化得这么辣鸡~

那会不会是那种进去相关页面再加载其他包的套路?尝试点击几下,没有新增的包,说明并不是通过这种方式把素材搞进去的。那就再 抓下包,进入游戏场景后看看:

请求了一大堆这种 ab包,搜了一下发现是Unity里的数据包,使用这里使用拆包工具 AssetStudio 尝试提取一波素材。

试了好几个接口,终于找到地图那个,导出资源包后却发现:

这都啥跟啥?搜了下,好像是一种防止素材被盗用的手段,将原图分割成N个小方块并打乱,通常会定义一个文本用来解析这张图。而抓包那里也缺失看到几个txt,只是并不能正常打开,猜测文件是加密过的。

唉,看来短时间是没办法直接搞到 背景底图,So,只能直接对截图进行处理了~


② 截图处理

细心点可以发现,这个底图其实是 宽11个格子,然后外面再套一层场景图,如森林、沙漠。

所以把关注点放格子上就行,渲染时应该是先画格子,再放置障碍物的。可以明显看到没有放置障碍物的格子有 深、浅两种绿色,将这两种颜色的像素点替换成白色,直接用PIL库写出简单处理代码。

看看处理后的效果:

可以,但还得处理下 空白间的横竖间隔,可能是障碍物的影子投射到格子上了,但它实际上也是格子的区域。

直接遍历每个显示古典,相邻白色间距小于50像素的,看作格子的一部分,填充白色,先处理水平方向:

Copy下代码改改,处理下竖直方向:

接着就是一些悬浮物和干扰项的处理了,如暂停按钮,ping值显示、关卡进度、角色等等,直接AirTest截图拿到固定坐标,同样遍历将像素点设置为白色:

接着尝试把非白色像素全部转换为黑色玩玩:

有点意思,继续做回正事,实际上不需要处理这么大的图片,调整分辨率(1080,2160) → (54,108),转灰度图,并二值化图像为0(障碍物)和1(通道)这样的数组:

灰度前,灰度后,输出数组 结果依次如下:

咦,不对劲,这迷宫走不通的啊。

其实是因为PIL的 convert() 转化为二值图时,会采用 固定阈值127 来实现,灰度高于127为1,灰度低于127为0,而自定义阈值要使用 point() 函数。还有灰度值越高越白,范围0-255,所以这里需要把 阈值提高,调整为230试试看~

运行结果如下:

牛逼,将现实世界的问题抽象成计算机问题来解决,此处应有掌声!!!

千辛万苦拿到迷宫数组,接着就是常规的解迷宫了~


③ 解迷宫

先确定起点和终点,同样通过AirTest拿到,然后除以20,转换下得到两个坐标 (16,29)、(14, 0)。

接着 回溯法 求解迷宫,四个行走方向(上右左下),顺着某个方向探索,能走通继续向前,否则换一个方向继续探索,直到抵达出口位置,求得通路,如果所有通路都探路完,还未能到达出口,说明迷宫没有通路。

使用 递归 实现一波,找路策略:上 → 右 → 左 → 下,具体实现代码如下:

map_source = [
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
# 太多省略了...
]
start = (29, 16)    # 起点
end = (0, 14)   # 终点
direction_list = [(-1, 0), (0, 1), (0, -1), (1, 0)]  # 上右左下
path_list = []  # 存储走的路径

def walk(source, position):
# 赋值2假定路能走
source[position[0]][position[1]] = 2
if position == end:
path_list.append(end)
return True
# 按照四个方向顺序检查
for i in range(4):
next_pos = position[0] + direction_list[i][0], position[1] + direction_list[i][43]
# 判断下个方向能走吗
if source[next_pos[0]][next_pos[1]] == 1:
if walk(source, next_pos):
path_list.append(position)
return True
return False


def print_path(source, path):
# 递归添加到列表的结果是倒序的,所以第一个反而是终点
for i, p in enumerate(path):
if i == 0:
source[p[0]][p[1]] = "E"
elif i == len(path) - 1:
source[p[0]][p[1]] = "S"
else:
source[p[0]][p[1]] = 3
for point_row in source:
for point in point_row:
if point == 3:
print('\033[0;31m' + "☺" + '\033[0m', end="")
elif point == 2:
print("■", end="")
elif point == "E":
print("㊛", end="")
elif point == "S":
print("㊚", end="")
else:
print("□", end="")
print()


if __name__ == '__main__':
walk(map_source, start)
print_path(map_source, path_list)

运行输出结果如下 (红色笑脸就是要走的路径):


④ 走迷宫

好的,知道路径,接着就要转化为具体的行走指令了,往哪个方向走,走几步,非常简单,直接给出代码:

def get_walk_direction(path):
walk_list = []  # 行走列表
temp_position = None  # 当前坐标点
cur_direction = None  # 当前方向
direction_count = 0  # 步数
# 倒序遍历列表
for i in range(len(path) - 1, -1, -1):
p = path[i]
# 为None说明是起点
if temp_position is None:
temp_position = p
else:
if p[0] - temp_position[0] < 0:
temp_direction = "上"
elif p[0] - temp_position[0] > 0:
temp_direction = "下"
elif p[1] - temp_position[1] < 0:
temp_direction = "左"
else:
temp_direction = "右"
# 指向下个坐标点
temp_position = p
# 如果没有方位,指向当前方位
if cur_direction is None:
cur_direction = temp_direction
direction_count = 0
else:
if cur_direction != temp_direction:
# 步数除以2向上取证,不然走起来不连贯,跟抽搐一样
walk_list.append((cur_direction, math.ceil(direction_count / 2)))
direction_count = 1
cur_direction = temp_direction
else:
direction_count += 1
# 还要处理抵达终点的情况(要减去终点1)
if i == 0:
walk_list.append((cur_direction, math.ceil((direction_count - 1) / 2)))
return walk_list

运行输出结果如下:

接着AirTest扣个滑块图标,定义四个方向的拖拽方法:

运行看看效果:

em...因为有坐骑,走得飞快,上面那个除以2得改成6才行。最后把所有代码整合到一起,看看效果:

游戏界面:

哈哈,虽然看着有些智障,但已经是自动化无需人工干预的了,当然还是存在问题的,有下面这些:

  • 障碍物容易误判,比如两只跟着的宠物;
  • 有些障碍物其实是可以通过的,如草丛,水潭等等;
  • 格子颜色判定可以从单一颜色匹配改为颜色区间判断,不然得定义一堆色值,而且还容易漏;
  • 人物行走速度,因为有坐骑,实际上走一步等于迷宫走几步,得细化;
  • 更准确地找到人物起点与终点;
  • 地图比较长,一屏显示不完,得分段处理;
  • 图片处理时间有点长了,主要是多次循环遍历了,可以想办法优化下;
  • 可以结合OCR,让技能选择也自动化,等等...

当然,上面这些事情就是后话了,关于这个自动化辅助暂且研究到这吧~


0x4、浅谈辅助

有些读者看到这里估计会喷我,这TM也叫 辅助,这不是 自动化 吗?

是的,但自动化也属于辅助的一种,编写起来比较容易,门槛较低,常常配合OCR、图片处理来编写一些自动化操作,如游戏里的跑图、做日常、技能连招等等,就是让它帮我们完成一些重复性的劳动。

可以看出这种辅助局限性比较强,只能固定地去做一些事情,所以再往下走是 机器学习自动辅助,通过喂养大量的样本去训练模型,让它只能地去完成一些操作。比如:上面这个智能识别障碍物进行躲避,识别敌人进行攻击,躲避技能等等。感兴趣的看下这些 (笔者没有显卡就先告辞了~):

好的,接着说说你们想要的辅助:锁血、飞天,一刀999999,无限元宝,这种是 内存挂。游戏角色的数据大部分会放在内存中,数据计算也是在本机进行,最后上传到后台,所以这种挂就是 对特定内存地址进行读取和更改。常见的内存修改工具:PC端(PE,Cheat Engine),Android端(GG修改器,GameGuardian),太极 (免root),网上教程很多,就不再展开讲了~


0x5、小结

本文记录了笔者从突发灵感到落地,实现刷图自动化辅助的过程,比起正经写代码好玩多了。不过当辅助写出来了,却不想再玩这游戏了,可能是发现了比游戏更有趣的事吧。

不过,不得不说这类小游戏是真TM 赚钱,套一个大概的框架模型(升级、状态、宠物),然后根据游戏风格:石器时代、传奇、仙侠等配置对应的文案,贴图差不多就行,再配个沙雕广告,广撒网,短视频、公众号平台啥的都投一投。运营可以不要,就死命出坑钱活动就行,爆率调低点,或者索性就不出,只能氪金。客服也可以不要,直接弄几个QQ机器人,偶尔发发公告啥的就好,要也可以,SSSVIP充钱大户提供一对一客服。闲着没事就开新服,要是现在没有限制游戏版本号,我都想自己搞一个了,23333。

行吧,就说到这,如果你觉得本文有点意思,欢迎三连一波,感谢~