用Python写一个自动连点器脚本
写在前面
事情的起因是我在玩游戏时发现有的游戏对话长 ,不太想过剧情的时候,要一直按空格和鼠标左键,希望能有一个脚本实现这个功能,碰巧在B站上看到一篇写Python写自动点击脚本的文章,上面还有源码,于是乎搭建了Python环境准备试试,试了之后发现可行,于是乎准备给源代码进行一些简单的修饰,好让它更好用,然后不知不觉就搞了一天,中间坑也踩了不少,因此写下此文记录一下这段经历。
环境
| 名称 | 版本 | 供应商 |
|---|---|---|
| Windows 10 专业版 | 22H2 | Microsoft |
| Python | 3.12 | |
| PyCharm | 2023.2.4 (Community Edition) |
一、安装Python
推荐下载windos直装的版本
二、安装PyCharm
下载社区版
汉化插件
文件-->设置-->插件-->下载插件
三、脚本
源码
"""经典的Gui类的写法,使用面向对象的方法"""
import os
import sys
import time
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
from typing import TypedDict
import pyautogui
from pynput import keyboard
import threading
import pygame
# 变量定义子类
class ApplicationProps(TypedDict):
app_alive: bool
auto_click_on: bool
curpos: StringVar
state: StringVar
xpos: StringVar
ypos: StringVar
spacer: StringVar
# 空间子类
class ApplicationComponents(TypedDict):
label_pos: Label
mouse_pos: Label
label_xpos: Label
xpos_entry: Entry
label_yset: Label
ypos_entry: Entry
label_spacer: Label
time_entry: Entry
# label_brief: Label
class Application(Frame):
children: ApplicationComponents
def __init__(self, master=None):
print("\napp开始初始化...")
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
# 导入变量类型
self.props: ApplicationProps = dict()
# 声明(StringVar这种需要声明,普通的变量不需要)
self.props['state'] = StringVar()
self.props['curpos'] = StringVar()
self.props['xpos'] = StringVar()
self.props['ypos'] = StringVar()
self.props['spacer'] = StringVar()
# 变量初始化
self.props['auto_click_on'] = False
self.props['state'].set("自动点击:关闭")
self.props['curpos'].set("0,0")
self.props['xpos'].set("1840")
self.props['ypos'].set("1061")
self.props['spacer'].set("1.0")
self.createWideget()
# 自定义变量(内部使用)
self.appalive = True
print("app初始化完成!")
def destroy(self):
print("app关闭")
self.appalive = False
time.sleep(0.1)
print("good bye!")
# 创建+摆放空间
def createWideget(self):
# 创建控件
img = Image.open(curpath+"back.jpg")
img = img.resize((150, 150))
photo = ImageTk.PhotoImage(img)
# 简介信息
imglabel = Label(self, image=photo)
imglabel.image = photo
imglabel.grid(row=1, column=2, rowspan=3, columnspan=3, padx=30)
Label(self, text="作者:ke").grid(row=4, column=2, padx=60)
Label(self, text="q:905142251").grid(row=5, column=2, padx=30)
label_pos = Label(self, text="当前坐标:", name="label_pos")
mouse_pos = Label(self, textvariable=self.props['curpos']) # bg="#ffffff"
label_state = Label(self, textvariable=self.props['state'])
label_xpos = Label(self, text="x轴设置")
xpos_entry = Entry(self, width=10, textvariable=self.props['xpos'])
label_yset = Label(self, text="y轴设置")
ypos_entry = Entry(self, width=10, textvariable=self.props['ypos'])
label_spacer = Label(self, text="点击间隔(秒)")
time_entry = Entry(self, width=10, textvariable=self.props['spacer'])
Label(self, text="热键").grid(row=5, column=0, sticky="w")
Label(self, text="自动点击开关:v").grid(row=6, column=0, sticky="w")
Label(self, text="更新点击位置:g").grid(row=7, column=0, sticky="w")
# 摆放控件
label_pos.grid(row=0, column=0, sticky="w", pady=30)
mouse_pos.grid(row=0, column=1, sticky="w", pady=10)
label_state.grid(row=1, column=0, sticky="w", pady=10)
label_xpos.grid(row=2, column=0, sticky="w", pady=10)
xpos_entry.grid(row=2, column=1, sticky="w", pady=10)
label_yset.grid(row=3, column=0, sticky="w", pady=10)
ypos_entry.grid(row=3, column=1, sticky="w", pady=10)
label_spacer.grid(row=4, column=0, sticky="w", pady=10)
time_entry.grid(row=4, column=1, sticky="w", pady=10)
def getpath():
if hasattr(sys, '_MEIPASS'):
print("在打包环境中运行")
path = os.path.join(sys._MEIPASS, '')
else:
print("在开发环境中运行")
path = os.path.join(os.path.dirname(__file__), '')
return path
def task_click(app):
time.sleep(0.5)
while app.appalive:
app.props["curpos"].set(str(pyautogui.position().x) + "," + str(pyautogui.position().y))
if app.props["auto_click_on"]:
try:
# 1.获得对话框坐标
x = int(app.props["xpos"].get())
y = int(app.props["ypos"].get())
# 2.鼠标移动到对话框
pyautogui.moveTo(x, y)
# 3.鼠标点击
print("click")
pyautogui.leftClick()
time.sleep(float(app.props["spacer"].get()))
except ValueError:
pass
time.sleep(0.1)
def on_press(key):
if key == keyboard.KeyCode(char='v'):
Mainapp.props["auto_click_on"] = not Mainapp.props["auto_click_on"]
print("auto_click =", Mainapp.props["auto_click_on"])
if Mainapp.props["auto_click_on"]:
sound1.play()
Mainapp.props["state"].set("状态:启动")
else:
sound2.play()
Mainapp.props["state"].set("状态:关闭")
if key == keyboard.KeyCode(char='g'):
sound3.play()
Mainapp.props["xpos"].set(str(pyautogui.position().x))
Mainapp.props["ypos"].set(str(pyautogui.position().y))
if __name__ == '__main__':
# 获取工作路径
curpath = getpath()
print("资源路径:" + curpath)
# 开启GUI
root = Tk()
root.iconbitmap(curpath+"icon.ico")
root.title("自动点击")
root.geometry("500x400")
Mainapp = Application(root)
Mainapp.grid(row=0, column=0, sticky=tk.NSEW, padx=50)
# 导入资源
pygame.mixer.init()
sound1_file = curpath + "successful.mp3"
sound2_file = curpath + "cancel.wav"
sound3_file = curpath + "hit.mp3"
sound1 = pygame.mixer.Sound(sound1_file)
sound2 = pygame.mixer.Sound(sound2_file)
sound3 = pygame.mixer.Sound(sound3_file)
# debug
# app.props["auto_click_on"] = True
# print(app.props["auto_click_on"])
# 开启自动点击线程v
listener = keyboard.Listener(on_press=on_press)
listener.start()
p1 = threading.Thread(target=task_click, args=(Mainapp,))
p1.start()
root.mainloop()
功能演示
程序框架
Main:程序执行入口
Application:UI的主对象类,其包含很多子类和控件
Task_click:一个定时触发的线程
Listener:键盘和鼠标的监听器
tkinter控件定义和布局
tkinter官方文档:https://docs.python.org/3/library/tkinter.html
tkinter的三大布局方式
- pack布局:pack布局是一种按照顺序的布局方式,默认是自上而下居中布局(不同行),也可以改为左右布局(同行)。
- grid布局:grid布局,按照表格的行列进行布局,把元素放在单元格里,所以这种布局方式很灵活,也比较容易排列整齐。
- place布局:place布局既可以绝对布局也可以相对布局,灵活性非常高,可以直接指定元素的x、y的坐标(原点为窗口或父容器左上角),注意,如果元素位置有重叠,则后布局的元素会盖住前布局的元素。
详情参考文献[3]
鼠标操作
# 获取鼠标位置
pyautogui.position() # 进一步可以获取.x或者.y
# 设置鼠标位置
pyautogui.moveTo(x, y)
键盘监听函数
def on_press(key):
if key == keyboard.KeyCode(char='v'):
# 执行当键盘按下v时的代码
线程
# 定义线程,执行task_click()函数,传入参数Mainapp
p1 = threading.Thread(target=task_click, args=(Mainapp,))
# 线程开启
p1.start()
线程里一般用While循环,但别写成死循环,不然当你主窗口关了线程也仍然会运行
使用PyCharm打包
在pycharm下工具栏点击文件 -设置,然后进入Python 解释器,点击+号
搜索pyinstaller安装即可
另一种指令安装的办法:
pip intsall pyinstaller
不过这个方法会出一些问题。
如果你是用的PyCharm图形化界面,建议使用PyCharm内置的打包方法。
获取当前工作路径
def getpath():
if hasattr(sys, '_MEIPASS'):
print("在打包环境中运行")
path = os.path.join(sys._MEIPASS, '')
else:
print("在开发环境中运行")
path = os.path.join(os.path.dirname(__file__), '')
return path
打包命令(以我的为例)
pyinstaller -w -F -i icon.ico --add-data "icon.ico;." --add-data "successful.mp3;." --add-data "cancel.wav;." --add-data "hit.mp3;." --add-data "back.jpg;." --onefile main.py
拆解分析
pyinstaller
-w 让运行时不出现cmd弹窗
-F 忘了
-i icon.ico 添加icon.ico图标,必须是.ico格式
--add-data "icon.ico;." 额外的数据1
--add-data "successful.mp3;."
--add-data "cancel.wav;."
--add-data "hit.mp3;."
--add-data "back.jpg;."
--onefile 打包成一个exe文件
main.py 主文件
这个icon指的是外部的图标,内部窗口的图标要在程序内部设置
参考文章:
1.[**PC原神自动过对话python]** 栗二犬 - 哔哩哔哩
2.使用pycharm进行Python项目打包 如何用pycharm打包文件 mob6454cc68daf3 - 51cto博客
3.【Python】tkinter的简单使用(Tk对象、三大布局、变量、事件) 冰冷的希望 - CSDN
4.如何使用Python实现多任务的多线程处理? PHPz - php中文网
5.Pyinstaller打包,彻底解决图片、数据路径问题 朝阳区靓仔_James - CSDN
6.Python Tkinter Grid布局管理器详解(转载网络) 芝麻豆 - 知乎 7.Python Gui之tkinter 渗透者:' - CSDN
Comments