为了学习pyautogui这个库的使用,我准备用它做点东西。比如一个自动弹琴的程序。
不过这个琴不是现实里的琴,而是原神里的风物之诗琴。(这里有个网页版模拟器可以试试:风物之诗琴模拟器 (haveyouwantto.github.io))
因为我们要让程序自动弹奏的是游戏里的琴,所以我们这个程序需要基于pyautogui库实现一下几个主要的功能:
识别屏幕上各个琴键的位置
读取以特定格式编好的琴谱
根据琴谱正确按下特定按键
那下面就开始制作呗!
我们要弹的琴的界面是这样的:
简单分析一下:它有低中高(分别对应下中上)三行,每一行都是do-ti。所以比如识别do的话会识别出三个位置,那只需要根据垂直坐标大小进行区分即可。
然后制作一份音调和对应琴键图存储路径的配置表:
截好图:
然后读取配置信息和图片,定位琴键。。。。。。
好,停!
代码测试到一半的时候,我就明白了这个定位方法并不好用。因为我想尽可能适应不一样的游戏窗口大小,所以会适当降低匹配的精度,但是这样就会导致很多匹配的错误,可能匹配某个音匹配了不止三个。。。只要没办法精确区分每个音调的位置,就没办法完成弹奏的任务。
那我们重新分析一下这个弹琴面板:
从中可以看出,最左边一列标志上下相邻的距离,和标志与其右边do音符的距离相等,同时这个距离也和音符键的左右相邻距离很相近。那我们就有了下面这个新策略(假定左上角为坐标原点,向右为x正方形,向下为y正方向):
获取标志1的中心位置L1(x1,y1):
获取标志2的中心位置L2(x2,y2):
计算L1和L2的距离d,则第一行各个音符的点击坐标为(x1+d,y1),(x1+2d,y1),(x1+3d,y1)......
第二行各个音符的点击坐标为(x1+d,y1+d),(x1+2d,y1+d),(x1+3d,y1+d)......
第三行各个音符的点击坐标为(x1+d,y1+2d),(x1+2d,y1+2d),(x1+3d,y1+2d)......
那没必要配置文件了,直接干!
上代码!:
import pyautogui
import math
import time
locations = [] #从左到右从上到下存储各个按键的位置 一共3*7=21个
L1_img = "0.png"
L2_img = "1.png"
#L1 = L2 = d = None
while(True):
input("按回车键开始定位")
L1 = pyautogui.locateCenterOnScreen(L1_img,confidence=0.87)
L2 = pyautogui.locateCenterOnScreen(L2_img,confidence=0.87)
if(L1 !=None and L2 !=None):
print("定位完成!")
break
else:
print("定位失败")
d = L2.y - L1.y
if(d < 0):
print("定位距离异常!")
exit(0)
for i in range(3):
for j in range(1,8):
locations.append(pyautogui.Point(L1.x+d*j,L1.y+d*i))
for l in locations:
pyautogui.click(l)
运行效果:
首先定义一下映射规则:
把各个音和对应的按键位置列表下表映射起来。(比如0mi就是第0行的mi)
然后我们根据规则编个谱子(《晴天》的前一小段)(*表示休止符,空格或回车隔开各个音):
脚本先定位琴键位置,然后读取谱子,然后根据谱子按下对应的按键进行自动弹奏:
import pyautogui
import math
import time
locations = [] #从左到右从上到下存储各个按键的位置 一共3*7=21个
L1_img = "0.png"
L2_img = "1.png"
puzi = "puzi.txt"
#琴键-位置下标 的映射
key_loc_map = {
"0do":0,
"0re":1,
"0mi":2,
"0fa":3,
"0so":4,
"0la":5,
"0ti":6,
"1do":7,
"1re":8,
"1mi":9,
"1fa":10,
"1so":11,
"1la":12,
"1ti":13,
"2do":14,
"2re":15,
"2mi":16,
"2fa":17,
"2so":18,
"2la":19,
"2ti":20,
}
#L1 = L2 = d = None
while(True):
input("按回车键开始定位")
L1 = pyautogui.locateCenterOnScreen(L1_img,confidence=0.9,grayscale=True)
L2 = pyautogui.locateCenterOnScreen(L2_img,confidence=0.9,grayscale=True)
if(L1 !=None and L2 !=None):
print("定位完成!")
print(L1)
print(L2)
break
else:
print("定位失败")
d = L2.y - L1.y
if(d < 0):
print("定位距离异常!")
exit(0)
for i in range(3):
for j in range(1,8):
locations.append(pyautogui.Point(L1.x+d*j,L1.y+d*i))
def Play(pu_path,delta):
p = open(pu_path,"r",encoding="utf-8")
puzi = eval(p.read())
for sound in puzi:
if sound!="*":
pyautogui.click(locations[key_loc_map[sound]])
time.sleep(delta)
p.close()
input("回车开始弹奏")
Play(puzi,0.15)
稍微测试一下(这个编辑器出了点问题视频就放连接了):
https://live.csdn.net/v/270125
运行没问题。
虽然在上面的网页模拟器的测试中运行似乎挺好的,但是在实际应用到游戏中效果其实并不好,缺点如下:
1、首先,游戏中弹琴的时候,琴背景是在时刻变化的,所以导致所要定位的那小块区域的像素和截图的有所出入,导致检测不到或者检测不准。
2、然后编写琴谱太麻烦(我是另外用脚本把键盘谱转化为这里的音谱的)。
3、接着就是,由于这里的弹奏方式是按固定的时间间隔进行弹奏或者休止,所以音乐弹出来很机械,没法体现那种缓急变化感。如果对每个音设置时间间隔的话又导致谱子的编写难度陡增。
4、最后,因为这个弹奏的方式是模拟鼠标点击进行弹奏,虽然能同时适配模拟器的移动端原神和PC版原神,但是做不到同时弹两个音,所以就没办法很好地弹出和弦。
所以,这个程序做出来就是图一乐,再往下去很难有较大的提升空间。想做一个好点的自动弹奏程序的话要考虑新的琴键触发方式和琴谱组织形式。