话不多说先上代码,后面有一些注意事项也请仔细阅读。
脚本代码
Dim hwnd
Dim LTX, LTY, RBX, RBY, screenWidth, screenHeight
Dim position, playing
Sub locate_screen
hwnd = find_hwnd("MuMu模拟器12", "nemudisplay")
screenRect = Plugin.Window.GetClientRect(hwnd)
LTX = split(screenRect, "|")(0)
LTY = split(screenRect, "|")(1)
RBX = split(screenRect, "|")(2)
RBY = split(screenRect, "|")(3)
screenWidth = RBX - LTX
screenHeight = RBY - LTY
End Sub
Sub start_fishing
MoveTo LTX + (CInt(screenWidth) * 9 / 10), LTY + (CInt(screenHeight) * 2 / 3)
LeftClick 1
Delay 500
MoveTo LTX + (CInt(screenWidth) / 2), LTY + (CInt(screenHeight) * 9 / 10)
LeftClick 1
playing = 1
End Sub
Call locate_screen
Function find_hwnd(fatherTitle, title)
fatherHwnd = Plugin.Window.Find(0, fatherTitle)
If fatherTitle = title Then
find_hwnd = fatherHwnd
Else
tempHwnd = 0
While tempHwnd = 0
tempHwnd = Plugin.Window.FindEx(fatherHwnd, 0, 0, title)
If tempHwnd = 0 Then
fatherHwnd = Plugin.Window.FindEx(fatherHwnd, 0, 0, 0)
End If
Wend
End If
find_hwnd = tempHwnd
End Function
While True
Call start_fishing
Delay 4000
While playing = 1
tempx1 = CInt(LTX) + CInt(screenWidth) * 8 / 30
tempx2 = CInt(LTX) + CInt(screenWidth) * 22 / 30
tempy = CInt(LTY) + CInt(screenHeight) * 4.2 / 17
iNum1 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "ADCB10", 1)
iNum2 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "FFFFFF", 1)
iNum3 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "6361FF", 1)
fishX = CInt(split(split(iNum2, "|")(0), ",")(0))
rightLimit = CInt(split(split(iNum1, "|")(0), ",")(0))
If rightLimit <> CInt(-1) Then
tempBlock = split(iNum1, "|")
leftLimit = split(tempBlock(UBound(tempBlock)), ",")(0)
Else
rightLimit = split(split(iNum3, "|")(0), ",")(0)
tempBlock = split(iNum3, "|")
leftLimit = split(tempBlock(UBound(tempBlock)), ",")(0)
End If
position = (CInt(leftLimit) + CInt(RightLimit)) / 2
If position < fishX Then
LeftDown 1
Else
LeftUp 1
End If
If fishX = - 1 Then
playing = 0
Delay 3000
LeftClick 1
End If
Wend
Wend
在按键精灵中新建脚本,将上述代码复制粘贴保存后即可运行。运行前,请在MuMu模拟器12中打开灵魂潮汐并进入到家园的钓鱼界面。准备好足够多的鱼竿和鱼饵之后,在按键精灵中点击启动即可。脚本会不断自动钓鱼,直到鱼竿或鱼饵耗尽为止。另外根据我的测试,在鱼竿和鱼饵充足的前提下,脚本每小时可产出5000~6000的硬币,搬空商店仅需每月15000硬币(不包括工坊券),因此我并不认为需要额外添加自动购买鱼竿和鱼饵的功能,也请各位人偶师大人合理安排凝炼石的投入。
根据我的测试,按键精灵2014并不能用后台点击的方式操作游戏,因此只能做成前台脚本。开启脚本之前请将可能遮住模拟器界面的窗口关闭或拖到一边,开启脚本后尽量不要动鼠标。我推荐如下图的窗口安排,既可以欣赏萨塔妮娅大人的人间真实时刻又可以刷硬币,
下面讲解一下脚本的结构。
结构分析
第一步,寻找游戏窗口,并读取窗口位置、窗口宽度、窗口高度等信息;
子程序locate_screen调用find_hwnd函数找到灵魂潮汐游戏所在的子窗口。
据观察,游戏所在的子窗口与其父窗口的关系如图:
我们事先知道了最高级的父窗口标题为“MuMu模拟器12”,目标窗口标题为“nemudisplay”。于是编写函数,通过递归查找的方式一层一层深入窗口的父子关系,直到找到目标窗口并获取其句柄。
你也许已经发现了,游戏的窗口其实固定在第三层,因此调用三次FindEx方法就可以实现同样的功能,我为什么要写那么一大坨东西,来预防并不存在的普遍性问题呢?事实上,我一开始认为我可以尝试做一个遍历树的算法,但是后来发现自己的技术力不够,只能写成这种样子了。唉好吧,我承认我就是菜,菜所以想多练。好了别说了别说了我这就去学数据结构。
GetClientRect方法接受一个句柄作为输入,返回句柄对应的窗口的左上角、左下角坐标,用“|”分开,利用split方法将其分开并记录到LTX等变量中。
第二步,分析钓鱼过程,确定程序主要结构
根据游戏的规则,一个完整的钓鱼过程包括以下步骤:
- 点击“钓鱼”按钮
- 点击“开始钓鱼”按钮
- 等待游戏内动画结束
- 钓鱼。
- 等待成功钓鱼动画结束
- 跳出结算画面
- 回到1
其中钓鱼过程中,屏幕上方会跳出一个长条,一个代表鱼的标志沿着长条随机左右运动。玩家操纵一条线段,只要鱼标志处在线段之内,线段便呈现绿色,且下方代表钓鱼进度的进度条前进。反之,若鱼在线段之外,线段呈现红色,且进度条倒退。当进度条达到100%时,钓鱼成功。
前两次点击可以使用LeftClick 1命令模拟。主要的难点是找到按钮的位置。
等待可以用Delay命令对应
钓鱼过程,需要检测鱼的位置,线段的位置,并对二者的关系进行判断,根据判断的结果模拟玩家的操作。
脚本需要判断钓鱼是否成功,成功后离开结算界面,再重新开始。
那么下面开始编写脚本代码。
第三步,开始钓鱼
start_fishing子程序承担了第1、第2步。使用MoveTo方法将光标移动到对应的按钮上,点击即可。
Sub start_fishing
MoveTo LTX + (CInt(screenWidth) * 9 / 10), LTY + (CInt(screenHeight) * 2 / 3)
LeftClick 1
Delay 500
MoveTo LTX + (CInt(screenWidth) / 2), LTY + (CInt(screenHeight) * 9 / 10)
LeftClick 1
playing = 1
End Sub
在这里需要注意一点,游戏每个操作之后都需要一定的时间播放包装动画,因此中间暂停了500毫秒让程序有一点反应时间。
另外,程序通过playing的值确定此时是否进入了钓鱼阶段。在这里初始化其为1,表示开始钓鱼。
该子程序执行结束后游戏会播放一段时长3~4秒的动画,因此随后暂停4000毫秒。即第3步。
接下来进入第4步钓鱼。start_fishing的初始化此时并不需要进行,因此钓鱼过程被放入一个新的while循环中,用playing决定是否结束循环。
tempx1 = CInt(LTX) + CInt(screenWidth) * 8 / 30
tempx2 = CInt(LTX) + CInt(screenWidth) * 22 / 30
tempy = CInt(LTY) + CInt(screenHeight) * 4.2 / 17
tempx1、tempx2、tempy确定了钓鱼长条的左右端点。
在这里值得一提的是,我一开始是通过hard_coding的方式,即加减数值来调整位置的。我很快发现这样编写的程序一旦游戏窗口进行了缩放就有可能失效,因此采用了比例。那些数字是一个一个试出来的,先用尺子量一下确定大概的数值,然后反复调试增减数值。
iNum1 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "ADCB10", 1)
iNum2 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "FFFFFF", 1)
iNum3 = Plugin.Color.GetBlockRange(tempx1, tempy, tempx2, tempy, "6361FF", 1)
fishX = CInt(split(split(iNum2, "|")(0), ",")(0))
rightLimit = CInt(split(split(iNum1, "|")(0), ",")(0))
If rightLimit <> CInt(-1) Then
tempBlock = split(iNum1, "|")
leftLimit = split(tempBlock(UBound(tempBlock)), ",")(0)
Else
rightLimit = split(split(iNum3, "|")(0), ",")(0)
tempBlock = split(iNum3, "|")
leftLimit = split(tempBlock(UBound(tempBlock)), ",")(0)
End If
position = (CInt(leftLimit) + CInt(RightLimit)) / 2
GetBlockRange方法可以读取特定区域内的颜色块,并返回颜色块的坐标。坐标内部XY用“,”隔开,若有多个坐标则用“|”分开。iNum1读取绿色,iNum2读取白色,iNum3读取红色。
注意到钓鱼条中只有鱼身上有白色,且左右分散的宽度明显小于玩家操作的线段长度,直接用读取到的第一个坐标的X分量作为鱼的位置。
默认读取绿色的坐标,如果没有绿色,GetBlockRange方法会返回-1,此时转到iNum3即可。由于需要得到线段的左右坐标,又注意到GetBlockRange方法返回的坐标X从大到小分布,故使用tempBlock作为中间量读取左侧坐标。注意这里不能使用hard_coding的方法,因为iNum1和iNum3的长度是不固定的。
position是线段中点的X坐标,此脚本以position作为与鱼比较的对象。
If position < fishX Then
LeftDown 1
Else
LeftUp 1
End If
根据游戏规则,按住鼠标则线段右移,松开鼠标则线段左移,故如上模拟玩家动作。
If fishX = - 1 Then
playing = 0
Delay 3000
LeftClick 1
End If
上面已经说过,只有鱼身上有白色色块。当脚本检测不到白色色块时,说明游戏已经跳出了结算画面,钓鱼结束,故令playing为0跳出while循环。等待动画播放完毕即可单击退出,然后开始新一轮的钓鱼。
最后用一个无限循环将整个钓鱼过程包起来即可。
补充:想在其他模拟器上运行 ?
由于此脚本在提取游戏画面时使用的是游戏本身的画面,因此如果想要让此脚本运行在MuMu模拟器以外的平台,并不需要对代码进行过多的修改。您只需要:
- 打开模拟器,进入游戏。同时打开按键精灵。
- 单击脚本进入脚本编辑界面,选择“源文件”,使用按键精灵上方工具栏中的“抓抓”功能,选择句柄选项卡,将准星拖到游戏画面后松开
- “当前窗口”区域中有一个“标题”输入框,将输入框中的内容复制,取代脚本代码中第6行的nemudisplay,注意双引号不能去掉。
- 将准星拖动到模拟器左上角后松开,同样将“标题”输入框中的内容复制,取代第6行“MuMu模拟器12”
此时你已经完成了所需的修改,保存脚本,启动即可!