[toc]
Python玩网易阴阳师百鬼夜行
目标
- 使用Python实现网易阴阳师百鬼夜行环节的自动撒豆
原因
- 撒逗环节又伤屏幕,又伤手指,还无聊
- 累积了太多次数后不玩又觉得浪费
预备
- Android 手机 (用于安装和运行游戏)
- 网易阴阳师游戏
- Linux (用于安装 adb 及开发和运行脚本)
- VIM (用于编辑脚本)
- adb (用于电脑与Android连接交互)
- Python3
- cv2 (用于匹配按钮图片及位置)
原理
- 通过 adb 截取游戏中关键位置按钮图片,并保存为样本图片
- 通过 python 控制 adb 实时截取 Android 手机运行游戏时的桌面
- 通过 python cv2 模块对截取的 Andorid 手机运行游戏时的桌面图片与样本图片进行匹配, 获取按钮的位置
- 通过 python 调用 adb 对按钮位置进行模拟点击操作
操作
- Andorid 手机登录和安装游戏,并登录运行
- Linux电脑安装 adb
- Android 手机与 Linux电脑通过数据线连接
- Android 手机启动开发者模式
一般为在设置页面点击手机版本号6至7次即可进入
一般为在设置页面点击进入开发人员选项,选择开启, 启动 USB调试, 启动允许USB调试修改权限或模拟点击
在获取部分位置坐标时,建议开启 指针位置 选项,可便于查看坐标信息
- 在游戏中的每个场景切换页面获取相应固定的桌面截图, 使用 adb shell screencap -p /sdcard/tmp.png 来截取游戏页面
- 使用 adb pull /sdcard/tmp.png ./ 从 andorid 手机 将图片传送到 Linux电脑上
- 通过截图软件将游戏页面图片中用于识别场景或是按钮的部分进行截图做为模块,如进入游戏页面的进入按钮图片, 开始游戏的开始按钮图片,用于识别正在游戏中的图片,及用于识别结束游戏进入统计页面的图片等。
实现
#!/usr/bin/env python3
#coding:utf-8
import os
import subprocess
import time
import cv2
import random
''' 截图命令备忘
adb shell screencap -p /sdcard/tmp.png
adb pull /sdcard/tmp.png ./
'''
# TODO: 百鬼夜行样本
## TODO: 进入游戏按钮样本
bgyx_attendin_png = cv2.imread('./template/bgyx/attendin.png')
## TODO: 结束游戏识别样本
bgyx_bgqys_png = cv2.imread('./template/bgyx/bgqys.png')
## TODO: 开始游戏按钮
bgyx_startup_png = cv2.imread('./template/bgyx/startup.png')
## TODO: 正在游戏识别样本
bgyx_game_png = cv2.imread('./template/bgyx/game.png')
# TODO: 截取Android手机游戏时桌面并将图片接取到Linux电脑上
def pull_screenshot():
tmp_png = './tmp/tmp.png'
android_tmp_png = '/sdcard/tmp.png'
screenCapCmd = 'adb shell screencap -p {}'.format(android_tmp_png)
screenPullCmd = 'adb pull {} {}'.format(android_tmp_png, tmp_png)
FNULL = open(os.devnull, 'w')
subprocess.check_call(screenCapCmd, shell=True, stdout=FNULL, stderr=subprocess.STDOUT)
subprocess.check_call(screenPullCmd, shell=True, stdout=FNULL, stderr=subprocess.STDOUT)
# TODO: 查找截取的Andorid手机游戏时桌面中样本图片出现的位置
def find_button(target, template):
theight, twidth = target.shape[:2]
# 进行图片匹配
result = cv2.matchTemplate(target, template, cv2.TM_SQDIFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
# 如果匹配度小于90%, 就认为没有找到
if min_val > 0.1:
return None
strmin_val = str(min_val)
# 获取点击位置
x = min_loc[0] + twidth//3
y = min_loc[1] + theight//3
return (x,y)
# TODO: 模块点击
def click(x, y):
clickCmd = 'adb shell input tap {} {}'.format(x, y)
os.system(clickCmd)
# TODO: 游戏流程
'''
1. 获取游戏页面截图
2. 检测游戏页面内容
3. 当页面存在进入游戏按钮时,则点击进入按钮
4. 当页面存在开始按钮时,则选择中间位置的鬼王,然后点击开始按钮
5. 当页面存在游戏中的标识时,则随机在中间位置进入40次点击操作
6. 当页面存在游戏结果统计页面标识时,则点击返回游戏进入页面
'''
def play_baiguiyexing():
print('>_ 读取显示页面')
pull_screenshot()
temp_png = cv2.imread('./tmp/tmp.png')
# TODO: 检测是否有进入按钮
attendin_button_pos = find_button(bgyx_attendin_png, temp_png)
if attendin_button_pos:
print('>_ 点击进入按钮')
click(attendin_button_pos[0], attendin_button_pos[1])
# TODO: 检测是否有开始按钮
startup_button_pos = find_button(bgyx_startup_png, temp_png)
if startup_button_pos:
print('>_ 选择鬼王')
click(1190, 751)
click(startup_button_pos[0], startup_button_pos[1])
# TODO: 检测是否在游戏中
game_button_pos = find_button(bgyx_game_png, temp_png)
if game_button_pos:
print('>_ 开始撒豆')
total_play_count = 40
for play_count in range(total_play_count):
click_x = random.randint(1100, 1519)
click_y = random.randint(650, 655)
click(click_x, click_y)
print('>_ 倒数 {}'.format(total_play_count - play_count))
# TODO: 检测是否结束
byqys_button_pos = find_button(bgyx_bgqys_png, temp_png)
if byqys_button_pos:
print('>_ 返回页面')
click(2020, 140)
if __name__ == "__main__":
while 1:
play_baiguiyexing()
约束
- 游戏过程比较机械,选择鬼王时只是默认选择中间位置
- 撒豆过程只是在中间位置随机点击,并没有进行跟踪点击等
扩展
- 通过机器学习或是解析请求可以实现更精确的游戏过程,但只是个菜鸟玩个随时会卸载的游戏就不浪费这时间了。