Python開發(fā)游戲自動化后臺腳本的實現(xiàn)
前言
前段時間沉迷豬場一夢江湖,由于實在太肝便萌生出用腳本做日常的想法,寫了第一個test.py,隨著后來各種功能的逐步添加,腳本也從前臺變成了支持后臺靜默運行,功能漸漸完善,包括了常用的
1.鼠標左鍵單擊指定坐標
2.識別并單擊指定圖像
3.識別圖像中文字
4.后臺截取程序畫面以供識別
5.鼠標滾輪上下滾動
6.鼠標左鍵范圍點擊以防檢測
7.程序中的鍵盤控制
8.程序中字符的輸入
說明
獲取窗口句柄
尋找標題為title的窗口,激活該窗口并置于x_coor, y_coor處,title可利用visual studio的spy++.exe查看;SWP_NOSIZE指定了窗口大小不變
def get_winds(self, title: str): ? ? """ ? ? @description : 獲取游戲句柄 ,并把游戲窗口置頂并激活窗口 ? ? --------- ? ? @param : 窗口名 ? ? ------- ? ? @Returns : 窗口句柄 ? ? ------- ? ? """ ? ? # self.__handle = win32gui.FindWindowEx(0, 0, "Qt5QWindowIcon", "MuMu模擬器") ? ? self.__handle = windll.user32.FindWindowW(None, title) ? ? self.__classname = win32gui.GetClassName(self.__handle) ? ? # print(self.__classname) ? ? if self.__classname == 'Qt5QWindowIcon': ? ? ? ? self.__mainhandle = win32gui.FindWindowEx(self.__handle, 0, "Qt5QWindowIcon", "MainWindowWindow") ? ? ? ? # print(self.__mainhandle) ? ? ? ? self.__centerhandle = win32gui.FindWindowEx(self.__mainhandle, 0, "Qt5QWindowIcon", "CenterWidgetWindow") ? ? ? ? # print(self.__centerhandle) ? ? ? ? self.__renderhandle = win32gui.FindWindowEx(self.__centerhandle, 0, "Qt5QWindowIcon", "RenderWindowWindow") ? ? ? ? # print(self.__renderhandle) ? ? ? ? self.__clickhandle = self.__renderhandle ? ? else: ? ? ? ? self.__clickhandle = self.__handle ? ? # self.__subhandle = win32gui.FindWindowEx(self.__renderhandle, 0, "subWin", "sub") ? ? # print(self.__subhandle) ? ? # self.__subsubhandle = win32gui.FindWindowEx(self.__subhandle, 0, "subWin", "sub") ? ? # print(self.__subsubhandle) ? ? # win32gui.ShowWindow(hwnd1, win32con.SW_RESTORE) ? ? # print(win32gui.GetWindowRect(hwnd1)) ? ? win32gui.SetWindowPos(self.__handle, win32con.HWND_TOP, x_coor, y_coor, 0, 0, win32con.SWP_SHOWWINDOW | win32con.SWP_NOSIZE) ? ? print(self.__clickhandle) ? ? return self.__handle
獲得后臺窗口截圖
窗口上方有39個像素的邊框,左、右、下則有8個像素的邊框
def get_src(self):
? ? """
? ? @description : 獲得后臺窗口截圖
? ? ---------
? ? @param : None
? ? -------
? ? @Returns : None
? ? -------
? ? """
? ? left, top, right, bot = win32gui.GetWindowRect(self.__handle)
? ? #Remove border around window (8 pixels on each side)
? ? bl = 8
? ? #Remove border on top
? ? bt = 39
? ? width = int((right - left + 1) * scale) - 2 * bl
? ? height = int((bot - top + 1) * scale) - bt - bl
? ? # 返回句柄窗口的設備環(huán)境,覆蓋整個窗口,包括非客戶區(qū),標題欄,菜單,邊框
? ? hWndDC = win32gui.GetWindowDC(self.__handle)
? ? # 創(chuàng)建設備描述表
? ? mfcDC = win32ui.CreateDCFromHandle(hWndDC)
? ? # 創(chuàng)建內(nèi)存設備描述表
? ? saveDC = mfcDC.CreateCompatibleDC()
? ? # 創(chuàng)建位圖對象準備保存圖片
? ? saveBitMap = win32ui.CreateBitmap()
? ? # 為bitmap開辟存儲空間
? ? saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
? ? # 將截圖保存到saveBitMap中
? ? saveDC.SelectObject(saveBitMap)
? ? # 保存bitmap到內(nèi)存設備描述表
? ? saveDC.BitBlt((0, 0), (width, height), mfcDC, (bl, bt), win32con.SRCCOPY)
? ? ###獲取位圖信息
? ? bmpinfo = saveBitMap.GetInfo()
? ? bmpstr = saveBitMap.GetBitmapBits(True)
? ? ###生成圖像
? ? im_PIL = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
? ? # 內(nèi)存釋放
? ? win32gui.DeleteObject(saveBitMap.GetHandle())
? ? saveDC.DeleteDC()
? ? mfcDC.DeleteDC()
? ? win32gui.ReleaseDC(self.__handle, hWndDC)
? ? ###PrintWindow成功,保存到文件,顯示到屏幕
? ? im_PIL.save("src.jpg") ?# 保存
? ? # im_PIL.show() ?# 顯示數(shù)字識別
依賴項——Tesseract OCR
下載并添加至系統(tǒng)環(huán)境變量
注意:這里將ocr識別范圍限定為0-9的數(shù)字以提高準確率
截取范圍為src.jpg中左上(x1,y1)到右下(x2,y2)的矩形區(qū)域
def get_num(self, x1, y1, x2, y2):
? ? """
? ? @description : 獲取屏幕截圖中的數(shù)字
? ? ---------
? ? @param : 截圖中需要截取的含數(shù)字部分邊界
? ? -------
? ? @Returns : num:int
? ? -------
? ? """
? ? ? ??
? ? img = Image.open("src.jpg")
? ? num_img = img.crop((x1, y1, x2, y2))
? ? num_img = ImageOps.invert(num_img)
? ? num = pytesseract.image_to_string(num_img, lang="eng",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? config='--psm 6 --oem 3 -c tessedit_char_whitelist=0123456789').strip()
? ? # num = pytesseract.image_to_string(num_img, lang="eng")
? ? try:
? ? ? ? print("數(shù)量為", int(num))
? ? ? ? return int(num)
? ? except:
? ? ? ? print("未檢測到數(shù)字")
? ? ? ? return 0識別并點擊圖片位置
所需識別的圖片模板事先準備好并放在同一目錄下,輸入圖片文件路徑
這里confidence設置為0.9
def mouse_click_image(self, name : str, times = 0.5):
? ? """
? ? @Description : 鼠標左鍵點擊識別到的圖片位置
? ? ---------
? ? @Args : name:輸入圖片名; times:單擊后延時
? ? -------
? ? @Returns : None
? ? -------
? ? """
? ? try:
? ? ? ? result = self.recognize(name)
? ? ? ? if result is None or result['confidence'] < 0.9:
? ? ? ? ? ? print("No results!")
? ? ? ? else:
? ? ? ? ? ? print(result['result'][0] + x_coor * scale + 8, " ",result['result'][1] + y_coor * scale + 39)
? ? ? ? ? ? self.mouse_click(result['result'][0] + x_coor * scale + 8, result['result'][1] + y_coor * scale + 39)
? ? except:
? ? ? ? raise Exception("error")后臺文字輸入
def type_str(self, msg: str):
"""
@Description : 打字
---------
@Args : msg:目標字符
-------
@Returns : None
-------
"""
for i in msg:
self.__PostMessageW(self.__handle, win32con.WM_CHAR, ord(i), 0)
完整代碼
GITEE網(wǎng)址: 項目-AutoClick.
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : AutoClick.py
@Time : 2021/10/09 15:10:01
@Author : Yaadon
'''
# here put the import lib
import win32con
import win32gui
import win32ui
import time
# import threading
import numpy as np
import os
from PIL import Image
from PIL import ImageOps
import aircv as ac
import pytesseract
from ctypes import windll, byref
from ctypes.wintypes import HWND, POINT
import string
# import sys
# import cv2
# from memory_pic import *
# import win32api
# import autopy
# from PIL import ImageGrab
scale = 1.25 # 電腦的縮放比例
radius = 5 # 隨機半徑
x_coor = 10 # 窗口位置
y_coor = 10 # 窗口位置
class AutoClick():
"""
@description :自動點擊類,包含后臺截圖、圖像匹配
---------
@param :
-------
@Returns :
-------
"""
__PostMessageW = windll.user32.PostMessageW
__SendMessageW = windll.user32.SendMessageW
__MapVirtualKeyW = windll.user32.MapVirtualKeyW
__VkKeyScanA = windll.user32.VkKeyScanA
__ClientToScreen = windll.user32.ClientToScreen
__WM_KEYDOWN = 0x100
__WM_KEYUP = 0x101
__WM_MOUSEMOVE = 0x0200
__WM_LBUTTONDOWN = 0x0201
__WM_LBUTTONUP = 0x202
__WM_MOUSEWHEEL = 0x020A
__WHEEL_DELTA = 120
__WM_SETCURSOR = 0x20
__WM_MOUSEACTIVATE = 0x21
__HTCLIENT = 1
__MA_ACTIVATE = 1
__VkCode = {
"back": 0x08,
"tab": 0x09,
"return": 0x0D,
"shift": 0x10,
"control": 0x11,
"menu": 0x12,
"pause": 0x13,
"capital": 0x14,
"escape": 0x1B,
"space": 0x20,
"end": 0x23,
"home": 0x24,
"left": 0x25,
"up": 0x26,
"right": 0x27,
"down": 0x28,
"print": 0x2A,
"snapshot": 0x2C,
"insert": 0x2D,
"delete": 0x2E,
"lwin": 0x5B,
"rwin": 0x5C,
"numpad0": 0x60,
"numpad1": 0x61,
"numpad2": 0x62,
"numpad3": 0x63,
"numpad4": 0x64,
"numpad5": 0x65,
"numpad6": 0x66,
"numpad7": 0x67,
"numpad8": 0x68,
"numpad9": 0x69,
"multiply": 0x6A,
"add": 0x6B,
"separator": 0x6C,
"subtract": 0x6D,
"decimal": 0x6E,
"divide": 0x6F,
"f1": 0x70,
"f2": 0x71,
"f3": 0x72,
"f4": 0x73,
"f5": 0x74,
"f6": 0x75,
"f7": 0x76,
"f8": 0x77,
"f9": 0x78,
"f10": 0x79,
"f11": 0x7A,
"f12": 0x7B,
"numlock": 0x90,
"scroll": 0x91,
"lshift": 0xA0,
"rshift": 0xA1,
"lcontrol": 0xA2,
"rcontrol": 0xA3,
"lmenu": 0xA4,
"rmenu": 0XA5
}
def __get_virtual_keycode(self, key: str):
"""根據(jù)按鍵名獲取虛擬按鍵碼
Args:
key (str): 按鍵名
Returns:
int: 虛擬按鍵碼
"""
if len(key) == 1 and key in string.printable:
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana
return self.__VkKeyScanA(ord(key)) & 0xff
else:
return self.__VkCode[key]
def __key_down(self, handle: HWND, key: str):
"""按下指定按鍵
Args:
handle (HWND): 窗口句柄
key (str): 按鍵名
"""
vk_code = self.__get_virtual_keycode(key)
scan_code = self.__MapVirtualKeyW(vk_code, 0)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
wparam = vk_code
lparam = (scan_code << 16) | 1
self.__PostMessageW(handle, self.__WM_KEYDOWN, wparam, lparam)
def __key_up(self, handle: HWND, key: str):
"""放開指定按鍵
Args:
handle (HWND): 窗口句柄
key (str): 按鍵名
"""
vk_code = self.__get_virtual_keycode(key)
scan_code = self.__MapVirtualKeyW(vk_code, 0)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup
wparam = vk_code
lparam = (scan_code << 16) | 0XC0000001
self.__PostMessageW(handle, self.__WM_KEYUP, wparam, lparam)
def __activate_mouse(self, handle: HWND):
"""
@Description : 激活窗口接受鼠標消息
---------
@Args : handle (HWND): 窗口句柄
-------
@Returns :
-------
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate
lparam = (self.__WM_LBUTTONDOWN << 16) | self.__HTCLIENT
self.__SendMessageW(handle, self.__WM_MOUSEACTIVATE, self.__handle, lparam)
def __set_cursor(self, handle: HWND, msg):
"""
@Description : Sent to a window if the mouse causes the cursor to move within a window and mouse input is not captured
---------
@Args : handle (HWND): 窗口句柄, msg : setcursor消息
-------
@Returns :
-------
"""
# https://docs.microsoft.com/en-us/windows/win32/menurc/wm-setcursor
lparam = (msg << 16) | self.__HTCLIENT
self.__SendMessageW(handle, self.__WM_SETCURSOR, handle, lparam)
def __move_to(self, handle: HWND, x: int, y: int):
"""移動鼠標到坐標(x, y)
Args:
handle (HWND): 窗口句柄
x (int): 橫坐標
y (int): 縱坐標
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousemove
wparam = 0
lparam = y << 16 | x
self.__PostMessageW(handle, self.__WM_MOUSEMOVE, wparam, lparam)
def __left_down(self, handle: HWND, x: int, y: int):
"""在坐標(x, y)按下鼠標左鍵
Args:
handle (HWND): 窗口句柄
x (int): 橫坐標
y (int): 縱坐標
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
wparam = 0x001 # MK_LBUTTON
lparam = y << 16 | x
self.__PostMessageW(handle, self.__WM_LBUTTONDOWN, wparam, lparam)
def __left_up(self, handle: HWND, x: int, y: int):
"""在坐標(x, y)放開鼠標左鍵
Args:
handle (HWND): 窗口句柄
x (int): 橫坐標
y (int): 縱坐標
"""
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup
wparam = 0
lparam = y << 16 | x
self.__PostMessageW(handle, self.__WM_LBUTTONUP, wparam, lparam)
def __scroll(self, handle: HWND, delta: int, x: int, y: int):
"""在坐標(x, y)滾動鼠標滾輪
Args:
handle (HWND): 窗口句柄
delta (int): 為正向上滾動,為負向下滾動
x (int): 橫坐標
y (int): 縱坐標
"""
self.__move_to(handle, x, y)
# https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel
wparam = delta << 16
p = POINT(x, y)
self.__ClientToScreen(handle, byref(p))
lparam = p.y << 16 | p.x
self.__PostMessageW(handle, self.__WM_MOUSEWHEEL, wparam, lparam)
def __scroll_up(self, handle: HWND, x: int, y: int):
"""在坐標(x, y)向上滾動鼠標滾輪
Args:
handle (HWND): 窗口句柄
x (int): 橫坐標
y (int): 縱坐標
"""
self.__scroll(handle, self.__WHEEL_DELTA, x, y)
def __scroll_down(self, handle: HWND, x: int, y: int):
"""在坐標(x, y)向下滾動鼠標滾輪
Args:
handle (HWND): 窗口句柄
x (int): 橫坐標
y (int): 縱坐標
"""
self.__scroll(handle, -self.__WHEEL_DELTA, x, y)
def get_winds(self, title: str):
"""
@description : 獲取游戲句柄 ,并把游戲窗口置頂并激活窗口
---------
@param : 窗口名
-------
@Returns : 窗口句柄
-------
"""
# self.__handle = win32gui.FindWindowEx(0, 0, "Qt5QWindowIcon", "MuMu模擬器")
self.__handle = windll.user32.FindWindowW(None, title)
self.__classname = win32gui.GetClassName(self.__handle)
# print(self.__classname)
if self.__classname == 'Qt5QWindowIcon':
self.__mainhandle = win32gui.FindWindowEx(self.__handle, 0, "Qt5QWindowIcon", "MainWindowWindow")
# print(self.__mainhandle)
self.__centerhandle = win32gui.FindWindowEx(self.__mainhandle, 0, "Qt5QWindowIcon", "CenterWidgetWindow")
# print(self.__centerhandle)
self.__renderhandle = win32gui.FindWindowEx(self.__centerhandle, 0, "Qt5QWindowIcon", "RenderWindowWindow")
# print(self.__renderhandle)
self.__clickhandle = self.__renderhandle
else:
self.__clickhandle = self.__handle
# self.__subhandle = win32gui.FindWindowEx(self.__renderhandle, 0, "subWin", "sub")
# print(self.__subhandle)
# self.__subsubhandle = win32gui.FindWindowEx(self.__subhandle, 0, "subWin", "sub")
# print(self.__subsubhandle)
# win32gui.ShowWindow(hwnd1, win32con.SW_RESTORE)
# print(win32gui.GetWindowRect(hwnd1))
win32gui.SetWindowPos(self.__handle, win32con.HWND_TOP, x_coor, y_coor, 0, 0, win32con.SWP_SHOWWINDOW | win32con.SWP_NOSIZE)
print(self.__clickhandle)
return self.__handle
def get_src(self):
"""
@description : 獲得后臺窗口截圖
---------
@param : None
-------
@Returns : None
-------
"""
left, top, right, bot = win32gui.GetWindowRect(self.__handle)
#Remove border around window (8 pixels on each side)
bl = 8
#Remove border on top
bt = 39
width = int((right - left + 1) * scale) - 2 * bl
height = int((bot - top + 1) * scale) - bt - bl
# 返回句柄窗口的設備環(huán)境,覆蓋整個窗口,包括非客戶區(qū),標題欄,菜單,邊框
hWndDC = win32gui.GetWindowDC(self.__handle)
# 創(chuàng)建設備描述表
mfcDC = win32ui.CreateDCFromHandle(hWndDC)
# 創(chuàng)建內(nèi)存設備描述表
saveDC = mfcDC.CreateCompatibleDC()
# 創(chuàng)建位圖對象準備保存圖片
saveBitMap = win32ui.CreateBitmap()
# 為bitmap開辟存儲空間
saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
# 將截圖保存到saveBitMap中
saveDC.SelectObject(saveBitMap)
# 保存bitmap到內(nèi)存設備描述表
saveDC.BitBlt((0, 0), (width, height), mfcDC, (bl, bt), win32con.SRCCOPY)
###獲取位圖信息
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
###生成圖像
im_PIL = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
# 內(nèi)存釋放
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(self.__handle, hWndDC)
###PrintWindow成功,保存到文件,顯示到屏幕
im_PIL.save("src.jpg") # 保存
# im_PIL.show() # 顯示
def recognize(self, objs):
"""
@description : 圖像識別之模板匹配
---------
@param : 需要匹配的模板名
-------
@Returns : 將傳進來的圖片和全屏截圖匹配如果找到就返回圖像在屏幕的坐標 否則返回None
-------
"""
imobj = ac.imread(objs)
imsrc = ac.imread('%s\\src.jpg' % os.getcwd())
pos = ac.find_template(imsrc, imobj, 0.5)
return pos
def mouse_click(self, x, y, times=0.5):
"""
@description : 單擊左鍵
---------
@param : 位置坐標x,y 單擊后延時times(s)
-------
@Returns :
-------
"""
# self.__set_cursor(self.__clickhandle, self.__WM_MOUSEACTIVATE)
# self.__move_to(self.__clickhandle, int(x / scale), int(y / scale))
# self.__activate_mouse(self.__clickhandle)
# self.__set_cursor(self.__clickhandle, self.__WM_LBUTTONDOWN)
self.__left_down(self.__clickhandle, int(x / scale), int(y / scale))
self.__move_to(self.__clickhandle, int(x / scale), int(y / scale))
self.__left_up(self.__clickhandle, int(x / scale), int(y / scale))
time.sleep(times)
def mouse_click_image(self, name : str, times = 0.5):
"""
@Description : 鼠標左鍵點擊識別到的圖片位置
---------
@Args : name:輸入圖片名; times:單擊后延時
-------
@Returns : None
-------
"""
try:
result = self.recognize(name)
if result is None or result['confidence'] < 0.9:
print("No results!")
else:
print(result['result'][0] + x_coor * scale + 8, " ",result['result'][1] + y_coor * scale + 39)
self.mouse_click(result['result'][0] + x_coor * scale + 8, result['result'][1] + y_coor * scale + 39)
except:
raise Exception("error")
def mouse_click_radius(self, x, y, times=0.5):
"""
@description : 在范圍內(nèi)隨機位置單擊(防檢測)
---------
@param : 位置坐標x,y 單擊后延時times(s)
-------
@Returns :
-------
"""
random_x = np.random.randint(-radius, radius)
random_y = np.random.randint(-radius, radius)
self.mouse_click(x + random_x, y + random_y)
# self.__left_down(self.__clickhandle, int((x + random_x) / scale), int((y + random_y) / scale))
# time.sleep(0.1)
# self.__left_up(self.__clickhandle, int((x + random_x) / scale), int((y + random_y) / scale))
time.sleep(times)
def push_key(self, key: str, times = 1):
"""
@Description : 按鍵
---------
@Args : key:按鍵 times:按下改鍵后距松開的延時
-------
@Returns : None
-------
"""
self.__key_down(self.__clickhandle, key)
time.sleep(times)
self.__key_up(self.__clickhandle, key)
time.sleep(0.5)
def type_str(self, msg: str):
"""
@Description : 打字
---------
@Args : msg:目標字符
-------
@Returns : None
-------
"""
for i in msg:
self.__PostMessageW(self.__clickhandle, win32con.WM_CHAR, ord(i), 0)
if __name__ == '__main__':
click = AutoClick()
click.get_winds("微信")
click.get_src()
# click.mouse_click(254, 536)
click.mouse_click_image('test.png')
# click.mouse_click(1086, 269) # 輸入框
# click.mouse_click(237, 211) # 輸入框
# click.mouse_click(1228, 201) # 輸入框
# click.type_str("123木頭人abc")
參考
部分參考自: Python開發(fā)游戲自動化腳本(四)后臺鍵鼠操作.
到此這篇關于Python開發(fā)游戲自動化后臺腳本的實現(xiàn)的文章就介紹到這了,更多相關Python 游戲自動化后臺腳本內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決Jupyter Notebook “signal only works&nb
這篇文章主要介紹了解決Jupyter Notebook “signal only works in main thread“問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
編寫同時兼容Python2.x與Python3.x版本的代碼的幾個示例
這篇文章主要介紹了編寫同時兼容Python2.x與Python3.x版本的代碼的幾個示例,在Python2.7.x的更新中由于采用了某些Python3中的代碼編寫特性、使得在有些原本不同之處編寫兼容性代碼成為可能,需要的朋友可以參考下2015-03-03
使用Python的Supervisor進行進程監(jiān)控以及自動啟動
這篇文章主要介紹了使用Python的Supervisor進行進程監(jiān)控以及自動啟動,使用python supervisor實現(xiàn),需要的朋友可以參考下2014-05-05
Python matplotlib繪制圖形實例(包括點,曲線,注釋和箭頭)
這篇文章主要介紹了Python matplotlib繪制圖形實例(包括點,曲線,注釋和箭頭),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04

