利用python编写一个pc模拟器明日方舟脚本

前言

首先,我知道通过按键精灵等插件实现类似的效果,但是我主要是无聊了想写一个。。

其次,更关键的是,该脚本除了可以自动刷图+自动嗑体力药剂以外,还能够在体力药剂用完的时候,不会碎石去恢复体力哦!(即脚本自动停止)(这是按键精灵做不到的吧,嘿嘿)

最后另外再废话两句,我写该脚本的另一个目的是想学习下python捕捉窗口,鼠标这些信息的函数,之前从来没有干过类似的事,就顺便学习下了,就算对游戏无兴趣的也可以看看

主要内容
获取模拟器窗口
from win32gui import *
titles = set()
def foo(hwnd,mouse):
	global titles
	if IsWindow(hwnd) and IsWindowEnabled(hwnd) and IsWindowVisible(hwnd):
	titles.add(GetWindowText(hwnd))
  
EnumWindows(foo, 0)
lt = [t for t in titles if t]
lt.sort()
for t in lt:
	if(t.find('MuMu模拟器')) >= 0: #我用的是MuMU模拟器,自己可以根据需求进行修改
		hwnd = win32gui.FindWindow(None, t)
		print(hwnd)

这个hwnd就是我们模拟器窗口的句柄啦,我们可以根据这个句柄对模拟器进行一些操作。
为了方便后面鼠标自动点击的位置的准确性,对窗口进行位置和大小的唯一性调整肯定是必要的

模拟器位置调整
def play_game():
    global titles
    EnumWindows(foo, 0)
    lt = [t for t in titles if t]
    lt.sort()

    for t in lt:
        print(t)
        if(t.find('MuMu模拟器')) >= 0:
            hwnd = win32gui.FindWindow(None, t)
            print(hwnd)
            #重点!!!调整模拟器的位置到左上角,且宽为620,高为360
            #另外几个参数类别居多,我也没有一个个去研究明白,建议保持不变
            #这一部如果失败的话,八成是最小化了模拟器,切记一定不能最小化模拟器!!!建议把模拟器打开放在最外层(意思就是能鼠标直接点到)
            win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST,0,0,620, 360,win32con.SWP_SHOWWINDOW)
            hwnd=win32gui.FindWindow(None, t)
            print(hwnd)
            size = win32gui.GetWindowRect(hwnd)
            print(size)
            return size

下面是执行这段代码的示意图
执行前:
在这里插入图片描述
执行后:
在这里插入图片描述
可以明显的看到,模拟器的位置和大小都发生了改变哈

脚本核心逻辑

其实核心逻辑非常简单,就是不断地在几个点之间来回点就完事了。直接上代码:

import win32gui
import win32api
import win32con
import time
def game():
    size = play_game() 	#上面说到的,调整模拟器到合适的大小和位置哈
    topx, topy = size[0], size[1]

    while True:
   		#把鼠标放在屏幕的这个位置
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])	#叫做点1
        #按下左键!!!
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        #停止一秒,按太快电脑会炸的哦
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])	
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])#点2
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 570, size[1] + 300])#点3
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)

好,细心的同学会留意到上面的这段代码看起来有些冗余。
问题1:因正常来说我们只需要持续来回的点2个点就够了,但为什么上面设计到了三个点?
答:最后结算界面,会出现掉落物品展示,而点1和点2都恰好会点到掉落物品上,如果只有这两个点来回点的话会导致卡在结算界面无法退出,所以引入了第三个点来退出结算界面。

问题2:为什么点1和点2都要点2次?
答:其实最开始这三个点都只点一次,但是因为实际中,可能会出现一个死循环(当点1+点3的次数偶数次时,会卡在吃药水的环节,所以需要将其改成奇数次。。这个感觉讲不清楚,实在是太偏业务了,自己实现的时候就会发现这个问题)。反正最后经过实践,发现按照上面的点击循环设计是一定不会在某个环节卡住的!大家可以自己尝试有没有更好的循环方式,但是建议不要改动。。

重点来了!!!
按照上面的脚本,当你的体力药剂用光的时候,他是会继续吃你的石头(手游充值得到,相当于钻石一类的)来恢复体力继续刷本!!!这显然是我们不想脚本做的事情,所以这时候就需要设计一个有点巧妙的方法来阻止这个事情发生。。
简单来说分为以下几步:
1.找到花完药水和体力,出现消耗源石来补充体力的界面,对这个界面的石头进行截图保存。
2.上面的点击循环的执行过程中,在每一次鼠标点击之前,都确认一下此时的界面是否含有源石(坐标也很关键哦)
3.如果在此时的模拟器上没找到这个石头,则继续刷图,如果有该石头,则强制退出程序。
ps:吃石头的图大概就是这个页面:
在这里插入图片描述
废话不多说了,进入我们的第一步,保存源石的截图

from PIL import Image, ImageGrab
save_path='E:/python_code/little tool/srk scrpit/imgs/stone_img.jpg'
'''
这里有个坑注意一下,Image函数所用的坐标,均为win32坐标的2倍。
即win32认为的点(0,0,100,100)在Image函数中为(0,0,200,200)。
其实Image才是真正以分辨率为坐标,win32的坐标等于分辨率/2!!!!
'''
ImageGrab.grab((topx + 680, topy + 250, topx + 850, topy + 400)).save(save_path)

保存的图片大概长这样:
在这里插入图片描述
接下来是第二步,就是每次点击行为之前,都判断一下此时在坐标(topx + 680, topy + 250, topx + 850, topy + 400)是否有这个石头,我们可以用imagehash来做一个简单的判断:

def is_using_stone(topx, topy):
	now_pic_path = 'E:/python_code/little tool/srk scrpit/imgs/now_img.jpg'
	#把该坐标当前的图当然也得先保存下来。。
    ImageGrab.grab((topx + 680, topy + 250, topx + 850, topy + 400)).save(now_pic_path)
    hash_stone = imagehash.average_hash(Image.open(save_path), hash_size=6)
    hash_now = imagehash.average_hash(Image.open(now_pic_path), hash_size=6)
    #计算源石的图片和当前位置图片的一种hash值,表示图片的相识度
    hash_diff = 1 - (hash_now - hash_stone) / len(hash_now.hash) ** 2
    print(hash_diff)
    #如果该值很大(>0.9),说明这两张图片很相似,返回true,否则返回false。
    if(hash_diff >= 0.9):
        return True
    else:
        return False

第三部就是把这个函数(is_using_stone)和循环点击的函数合一起就完事啦

def game():
    size = play_game()
    topx, topy = size[0], size[1]
	
    while True:
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])
        #嗯 真正做到每次点击行为之前都判断一下当前界面是不是使用源石的界面哦!
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 570, size[1] + 300])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
完整代码
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from win32gui import *
import win32gui
import win32api
import win32con
import time
from pygame import event
import pygame
import imagehash
from PIL import Image, ImageGrab

pygame.init()
titles = set()
hash_stone = imagehash.average_hash(Image.open('E:/python_code/little tool/srk scrpit/imgs/stone_img.jpg'), hash_size=6)
now_pic_path = 'E:/python_code/little tool/srk scrpit/imgs/now_img.jpg'

def foo(hwnd,mouse):
 #去掉下面这句就所有都输出了,但是我不需要那么多
 if IsWindow(hwnd) and IsWindowEnabled(hwnd) and IsWindowVisible(hwnd):
  titles.add(GetWindowText(hwnd))
def play_game():
    global titles
    EnumWindows(foo, 0)
    lt = [t for t in titles if t]
    lt.sort()

    for t in lt:
        print(t)
        if(t.find('MuMu模拟器')) >= 0:
            hwnd = win32gui.FindWindow(None, t)
            print(hwnd)
            win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST,0,0,620, 360,win32con.SWP_SHOWWINDOW)
            hwnd=win32gui.FindWindow(None, t)
            print(hwnd)
            size = win32gui.GetWindowRect(hwnd)
            print(size)
            return size

def is_using_stone(topx, topy):
    ImageGrab.grab((topx + 680, topy + 250, topx + 850, topy + 400)).save(now_pic_path)
    hash_now = imagehash.average_hash(Image.open(now_pic_path), hash_size=6)
    hash_diff = 1 - (hash_now - hash_stone) / len(hash_now.hash) ** 2
    print(hash_diff)
    if(hash_diff >= 0.9):
        return True
    else:
        return False

def game():
    size = play_game()
    topx, topy = size[0], size[1]

    while True:
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 550, size[1] + 300])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 495, size[1] + 260])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
        win32api.SetCursorPos([size[0] + 570, size[1] + 300])
        if is_using_stone(topx, topy):
            break
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP | win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
        time.sleep(1)
if __name__ == '__main__':
    game()
写在最后

本篇文章还是以记录自己学习制作脚本的过程为主吧。
所以如果真的有小伙伴有脚本需求但是又不会写代码,可以在评论去留言哦。

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值