优化以下代码“import cv2
import numpy as np
import pyautogui
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import time
import os
from PIL import Image, ImageTk
import json
import random
import gc
import psutil
import win32gui
import win32con
import win32api
import subprocess
import platform
import math
class OptimizedAutoClicker:
def __init__(self, root):
self.root = root
self.root.title("星辰奇缘小助手")
self.root.geometry("850x650") # 缩小窗口尺寸
# 变量初始化
self.templates = []
self.is_running = False
self.click_history = []
self.storage_location = os.getcwd()
self.config_file = "auto_clicker_config.json"
self.selected_window = None
self.window_rect = None
self.cache_clean_interval = 100 # 增加缓存清理间隔
self.loop_count = 0
self.run_in_background = False
self.process = psutil.Process()
self.anti_detection_enabled = False
self.human_behavior_enabled = False
self.silent_mode_enabled = False
# 性能优化变量
self.template_cache = {} # 缓存模板图像
self.last_screenshot = None # 缓存上一次截图
self.screenshot_count = 0
# 加载配置
self.load_config()
# 创建界面
self.create_widgets()
# 初始化窗口列表
self.update_window_list()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="8")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建左右分栏
left_frame = ttk.Frame(main_frame)
left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
right_frame = ttk.Frame(main_frame)
right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
# 存储位置区域 - 放在左侧顶部
storage_frame = ttk.LabelFrame(left_frame, text="存储设置", padding="5")
storage_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
self.storage_location_label = ttk.Label(storage_frame, text=self.storage_location,
wraplength=300, font=("Arial", 8))
self.storage_location_label.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=5)
storage_buttons = ttk.Frame(storage_frame)
storage_buttons.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=3)
ttk.Button(storage_buttons, text="更改位置", command=self.change_storage_location,
width=10).grid(row=0, column=0, padx=2)
ttk.Button(storage_buttons, text="打开位置", command=self.open_storage_location,
width=10).grid(row=0, column=1, padx=2)
# 窗口选择区域 - 放在左侧
window_frame = ttk.LabelFrame(left_frame, text="窗口选择", padding="5")
window_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
self.window_var = tk.StringVar()
self.window_combo = ttk.Combobox(window_frame, textvariable=self.window_var, width=30)
self.window_combo.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), padx=5, pady=2)
window_buttons = ttk.Frame(window_frame)
window_buttons.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=2)
ttk.Button(window_buttons, text="刷新窗口", command=self.update_window_list,
width=10).grid(row=0, column=0, padx=2)
ttk.Button(window_buttons, text="当前窗口", command=self.select_current_window,
width=10).grid(row=0, column=1, padx=2)
self.window_info_label = ttk.Label(window_frame, text="未选择窗口", font=("Arial", 8))
self.window_info_label.grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5)
# 模板管理区域 - 放在左侧
template_frame = ttk.LabelFrame(left_frame, text="模板管理", padding="5")
template_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
# 模板列表
self.template_listbox = tk.Listbox(template_frame, height=6, selectmode=tk.SINGLE,
font=("Arial", 9))
self.template_listbox.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=2)
self.template_listbox.bind('<<ListboxSelect>>', self.on_template_select)
# 模板操作按钮
template_buttons = ttk.Frame(template_frame)
template_buttons.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=2)
ttk.Button(template_buttons, text="添加", command=self.add_template,
width=6).grid(row=0, column=0, padx=1)
ttk.Button(template_buttons, text="删除", command=self.remove_template,
width=6).grid(row=0, column=1, padx=1)
ttk.Button(template_buttons, text="上移", command=self.move_template_up,
width=6).grid(row=0, column=2, padx=1)
ttk.Button(template_buttons, text="下移", command=self.move_template_down,
width=6).grid(row=0, column=3, padx=1)
# 模板预览 - 使用Frame包装Label以控制大小
preview_frame = ttk.Frame(template_frame)
preview_frame.grid(row=2, column=0, columnspan=4, pady=2)
self.template_preview_label = ttk.Label(preview_frame, text="模板预览")
self.template_preview_label.grid(row=0, column=0)
# 设置预览框架的最小尺寸
preview_frame.config(width=120, height=120)
preview_frame.pack_propagate(False) # 防止子组件改变框架大小
# 参数设置区域 - 放在右侧顶部
param_frame = ttk.LabelFrame(right_frame, text="参数设置", padding="5")
param_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
# 第一行参数
param_row1 = ttk.Frame(param_frame)
param_row1.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=2)
ttk.Label(param_row1, text="置信度阈值:").grid(row=0, column=0, sticky=tk.W)
self.confidence_var = tk.DoubleVar(value=0.7)
confidence_scale = ttk.Scale(param_row1, from_=0.5, to=1.0, variable=self.confidence_var,
orient=tk.HORIZONTAL, length=120)
confidence_scale.grid(row=0, column=1, sticky=tk.W, padx=5)
self.confidence_label = ttk.Label(param_row1, text="0.7", width=4)
self.confidence_label.grid(row=0, column=2, sticky=tk.W)
ttk.Label(param_row1, text="点击间隔:").grid(row=0, column=3, sticky=tk.W, padx=(10, 0))
self.interval_var = tk.DoubleVar(value=1.0)
interval_spin = ttk.Spinbox(param_row1, from_=0.1, to=60.0, increment=0.1,
textvariable=self.interval_var, width=6)
interval_spin.grid(row=0, column=4, sticky=tk.W, padx=5)
# 第二行参数
param_row2 = ttk.Frame(param_frame)
param_row2.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=2)
ttk.Label(param_row2, text="点击次数:").grid(row=0, column=0, sticky=tk.W)
self.click_count_var = tk.IntVar(value=0)
count_spin = ttk.Spinbox(param_row2, from_=0, to=1000, textvariable=self.click_count_var, width=6)
count_spin.grid(row=0, column=1, sticky=tk.W, padx=5)
ttk.Label(param_row2, text="匹配模式:").grid(row=0, column=2, sticky=tk.W, padx=(10, 0))
self.match_mode_var = tk.StringVar(value="顺序匹配")
match_mode_combo = ttk.Combobox(param_row2, textvariable=self.match_mode_var,
values=["顺序匹配", "随机匹配", "最高置信度"], width=10)
match_mode_combo.grid(row=0, column=3, sticky=tk.W, padx=5)
# 第三行参数
param_row3 = ttk.Frame(param_frame)
param_row3.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=2)
# 后台运行复选框
self.background_var = tk.BooleanVar(value=False)
ttk.Checkbutton(param_row3, text="后台运行", variable=self.background_var).grid(row=0, column=0, sticky=tk.W)
# 缓存清理间隔
ttk.Label(param_row3, text="缓存间隔:").grid(row=0, column=1, sticky=tk.W, padx=(10, 0))
self.cache_interval_var = tk.IntVar(value=100)
cache_spin = ttk.Spinbox(param_row3, from_=50, to=500, textvariable=self.cache_interval_var, width=5)
cache_spin.grid(row=0, column=2, sticky=tk.W, padx=5)
# 内存使用显示
self.memory_label = ttk.Label(param_row3, text="内存: 0 MB", font=("Arial", 8))
self.memory_label.grid(row=0, column=3, sticky=tk.W, padx=(10, 0))
# 防检测设置区域 - 放在右侧
anti_detect_frame = ttk.LabelFrame(right_frame, text="防检测设置", padding="5")
anti_detect_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
# 防检测选项
self.anti_detect_var = tk.BooleanVar(value=False)
ttk.Checkbutton(anti_detect_frame, text="启用防检测", variable=self.anti_detect_var,
command=self.toggle_anti_detection).grid(row=0, column=0, sticky=tk.W)
self.human_behavior_var = tk.BooleanVar(value=False)
ttk.Checkbutton(anti_detect_frame, text="模拟人类", variable=self.human_behavior_var).grid(row=0, column=1,
sticky=tk.W)
self.silent_mode_var = tk.BooleanVar(value=False)
ttk.Checkbutton(anti_detect_frame, text="静默模式", variable=self.silent_mode_var).grid(row=0, column=2,
sticky=tk.W)
# 随机延迟
ttk.Label(anti_detect_frame, text="随机延迟:").grid(row=1, column=0, sticky=tk.W, pady=3)
self.random_delay_min_var = tk.DoubleVar(value=0.1)
self.random_delay_max_var = tk.DoubleVar(value=0.5)
ttk.Entry(anti_detect_frame, textvariable=self.random_delay_min_var, width=4).grid(row=1, column=1, sticky=tk.W)
ttk.Label(anti_detect_frame, text="-").grid(row=1, column=2, sticky=tk.W)
ttk.Entry(anti_detect_frame, textvariable=self.random_delay_max_var, width=4).grid(row=1, column=3, sticky=tk.W)
# 点击位置偏移
ttk.Label(anti_detect_frame, text="位置偏移:").grid(row=2, column=0, sticky=tk.W)
self.click_offset_var = tk.IntVar(value=5)
offset_scale = ttk.Scale(anti_detect_frame, from_=0, to=20, variable=self.click_offset_var,
orient=tk.HORIZONTAL, length=120)
offset_scale.grid(row=2, column=1, columnspan=3, sticky=tk.W, padx=5)
self.offset_label = ttk.Label(anti_detect_frame, text="5", width=2)
self.offset_label.grid(row=2, column=4, sticky=tk.W)
# 操作按钮区域 - 放在右侧
button_frame = ttk.LabelFrame(right_frame, text="操作控制", padding="5")
button_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
button_row1 = ttk.Frame(button_frame)
button_row1.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=2)
self.start_button = ttk.Button(button_row1, text="开始", command=self.start_clicking, width=10)
self.start_button.grid(row=0, column=0, padx=2)
self.stop_button = ttk.Button(button_row1, text="停止", command=self.stop_clicking,
state=tk.DISABLED, width=10)
self.stop_button.grid(row=0, column=1, padx=2)
button_row2 = ttk.Frame(button_frame)
button_row2.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=2)
ttk.Button(button_row2, text="测试模板", command=self.test_all_templates,
width=10).grid(row=0, column=0, padx=2)
ttk.Button(button_row2, text="截图", command=self.capture_screen,
width=10).grid(row=0, column=1, padx=2)
ttk.Button(button_row2, text="清理缓存", command=self.force_clean_cache,
width=10).grid(row=0, column=2, padx=2)
# 日志区域 - 放在右侧底部
log_frame = ttk.LabelFrame(right_frame, text="操作日志", padding="5")
log_frame.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 添加滚动文本框
self.log_text = tk.Text(log_frame, height=10, width=50, font=("Arial", 9))
scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=scrollbar.set)
self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 清空日志按钮
ttk.Button(log_frame, text="清空日志", command=self.clear_log,
width=10).grid(row=1, column=0, sticky=tk.W, pady=3)
# 配置网格权重
main_frame.columnconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(0, weight=1)
left_frame.columnconfigure(0, weight=1)
left_frame.rowconfigure(2, weight=1) # 模板区域可扩展
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(3, weight=1) # 日志区域可扩展
storage_frame.columnconfigure(0, weight=1)
window_frame.columnconfigure(0, weight=1)
template_frame.columnconfigure(0, weight=1)
template_frame.rowconfigure(0, weight=1) # 模板列表可扩展
param_frame.columnconfigure(0, weight=1)
anti_detect_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(0, weight=1)
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
# 绑定事件
confidence_scale.configure(command=self.update_confidence_label)
offset_scale.configure(command=self.update_offset_label)
self.window_combo.bind('<<ComboboxSelected>>', self.on_window_select)
# 更新模板列表显示
self.update_template_list()
# 启动内存监控
self.monitor_memory()
def update_confidence_label(self, value):
self.confidence_label.config(text=f"{float(value):.2f}")
def update_offset_label(self, value):
self.offset_label.config(text=f"{int(float(value))}")
def toggle_anti_detection(self):
if self.anti_detect_var.get():
self.log("防检测功能已启用")
else:
self.log("防检测功能已禁用")
def change_storage_location(self):
"""更改存储位置"""
new_location = filedialog.askdirectory(title="选择存储位置")
if new_location:
self.storage_location = new_location
self.storage_location_label.config(text=self.storage_location)
self.log(f"存储位置已更改为: {self.storage_location}")
# 如果配置文件存在,移动到新位置
old_config_path = os.path.join(os.getcwd(), self.config_file)
new_config_path = os.path.join(self.storage_location, self.config_file)
if os.path.exists(old_config_path) and not os.path.exists(new_config_path):
try:
import shutil
shutil.move(old_config_path, new_config_path)
self.log("配置文件已移动到新位置")
except Exception as e:
self.log(f"移动配置文件时出错: {str(e)}")
def update_window_list(self):
"""更新窗口列表"""
windows = []
def enum_windows_proc(hwnd, windows):
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd):
windows.append((hwnd, win32gui.GetWindowText(hwnd)))
win32gui.EnumWindows(enum_windows_proc, windows)
# 更新下拉框
self.window_combo['values'] = [f"{title} (0x{hwnd:X})" for hwnd, title in windows]
self.log("窗口列表已更新")
def on_window_select(self, event):
"""当选择窗口时"""
selection = self.window_combo.get()
if selection:
# 提取窗口句柄
try:
hwnd_str = selection.split("(0x")[1].rstrip(")")
hwnd = int(hwnd_str, 16)
self.selected_window = hwnd
# 获取窗口位置和大小
rect = win32gui.GetWindowRect(hwnd)
self.window_rect = rect
# 更新窗口信息显示
self.window_info_label.config(
text=f"位置: ({rect[0]}, {rect[1]}) 大小: {rect[2] - rect[0]}x{rect[3] - rect[1]}"
)
self.log(f"已选择窗口: {selection}")
except Exception as e:
messagebox.showerror("错误", f"选择窗口时出错: {str(e)}")
def select_current_window(self):
"""选择当前活动窗口"""
hwnd = win32gui.GetForegroundWindow()
if hwnd:
title = win32gui.GetWindowText(hwnd)
if title:
self.window_combo.set(f"{title} (0x{hwnd:X})")
self.on_window_select(None)
else:
messagebox.showwarning("警告", "当前窗口没有标题")
else:
messagebox.showwarning("警告", "无法获取当前窗口")
def add_template(self):
file_paths = filedialog.askopenfilenames(
title="选择模板图像",
filetypes=[("图像文件", "*.png;*.jpg;*.jpeg;*.bmp"), ("所有文件", "*.*")]
)
for file_path in file_paths:
template_name = os.path.splitext(os.path.basename(file_path))[0]
self.templates.append({
"name": template_name,
"path": file_path,
"enabled": True
})
self.update_template_list()
self.log(f"添加了 {len(file_paths)} 个模板")
def remove_template(self):
selection = self.template_listbox.curselection()
if selection:
index = selection[0]
template_name = self.templates[index]["name"]
# 从缓存中删除模板
if template_name in self.template_cache:
del self.template_cache[template_name]
del self.templates[index]
self.update_template_list()
self.template_preview_label.config(image='', text="模板预览")
self.log(f"删除了模板: {template_name}")
def move_template_up(self):
selection = self.template_listbox.curselection()
if selection and selection[0] > 0:
index = selection[0]
self.templates[index], self.templates[index - 1] = self.templates[index - 1], self.templates[index]
self.update_template_list()
self.template_listbox.select_set(index - 1)
def move_template_down(self):
selection = self.template_listbox.curselection()
if selection and selection[0] < len(self.templates) - 1:
index = selection[0]
self.templates[index], self.templates[index + 1] = self.templates[index + 1], self.templates[index]
self.update_template_list()
self.template_listbox.select_set(index + 1)
def update_template_list(self):
self.template_listbox.delete(0, tk.END)
for template in self.templates:
status = "✓" if template["enabled"] else "✗"
self.template_listbox.insert(tk.END, f"{status} {template['name']}")
def on_template_select(self, event):
selection = self.template_listbox.curselection()
if selection:
index = selection[0]
template = self.templates[index]
# 显示模板预览
try:
image = Image.open(template["path"])
image.thumbnail((120, 120)) # 缩小缩略图大小
photo = ImageTk.PhotoImage(image)
self.template_preview_label.config(image=photo, text="")
self.template_preview_label.image = photo # 保持引用
except Exception as e:
self.template_preview_label.config(image='', text="无法加载预览")
def get_screen_region(self):
# 如果选择了特定窗口,使用窗口区域
if self.selected_window and self.window_rect:
return self.window_rect
# 否则使用全屏
return None
def capture_screen(self):
region = self.get_screen_region()
if region:
screenshot = pyautogui.screenshot(region=region)
else:
screenshot = pyautogui.screenshot()
# 保存截图到指定存储位置
filename = f"screenshot_{time.strftime('%Y%m%d_%H%M%S')}.png"
file_path = os.path.join(self.storage_location, filename)
screenshot.save(file_path)
self.log(f"截图已保存: {file_path}")
def test_all_templates(self):
if not self.templates:
messagebox.showerror("错误", "请先添加模板图像")
return
region = self.get_screen_region()
if region:
screenshot = pyautogui.screenshot(region=region)
else:
screenshot = pyautogui.screenshot()
screenshot_cv = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
results = []
for template in self.templates:
if not template["enabled"]:
continue
try:
# 使用缓存的模板图像
template_img = self.get_cached_template(template["path"])
if template_img is None:
self.log(f"错误: 无法读取模板 {template['name']}")
continue
# 执行模板匹配
result = cv2.matchTemplate(screenshot_cv, template_img, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if max_val >= self.confidence_var.get():
h, w = template_img.shape[:2]
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 在截图上绘制矩形
cv2.rectangle(screenshot_cv, top_left, bottom_right, (0, 0, 255), 2)
cv2.putText(screenshot_cv, template['name'],
(top_left[0], top_left[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
results.append({
"name": template['name'],
"confidence": max_val,
"position": (top_left[0] + w // 2, top_left[1] + h // 2)
})
except Exception as e:
self.log(f"测试模板 {template['name']} 时出错: {str(e)}")
# 显示结果图像
if results:
self.show_result_image(screenshot_cv, f"找到 {len(results)} 个匹配项")
for result in results:
self.log(f"找到模板 '{result['name']}': 位置 {result['position']}, 置信度 {result['confidence']:.4f}")
else:
self.log("测试完成: 未找到任何匹配项")
messagebox.showinfo("测试结果", "未找到任何匹配项")
def show_result_image(self, image, title):
# 创建新窗口显示结果
result_window = tk.Toplevel(self.root)
result_window.title(title)
result_window.geometry("600x500") # 限制结果窗口大小
# 调整图像大小以适应窗口
h, w = image.shape[:2]
scale = min(500 / w, 400 / h) # 缩小显示尺寸
new_w, new_h = int(w * scale), int(h * scale)
image_resized = cv2.resize(image, (new_w, new_h))
image_rgb = cv2.cvtColor(image_resized, cv2.COLOR_BGR2RGB)
img = Image.fromarray(image_rgb)
imgtk = ImageTk.PhotoImage(image=img)
label = ttk.Label(result_window, image=imgtk)
label.image = imgtk
label.pack(padx=10, pady=10)
ttk.Button(result_window, text="关闭", command=result_window.destroy).pack(pady=5)
def start_clicking(self):
if not any(template["enabled"] for template in self.templates):
messagebox.showerror("错误", "请先添加并启用至少一个模板")
return
self.is_running = True
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.run_in_background = self.background_var.get()
self.anti_detection_enabled = self.anti_detect_var.get()
self.human_behavior_enabled = self.human_behavior_var.get()
self.silent_mode_enabled = self.silent_mode_var.get()
# 清除截图缓存
self.last_screenshot = None
self.screenshot_count = 0
# 在新线程中运行点击循环
self.click_thread = threading.Thread(target=self.click_loop)
self.click_thread.daemon = True
self.click_thread.start()
self.log("开始自动点击")
# 如果选择后台运行,最小化窗口
if self.run_in_background:
self.root.iconify()
self.log("程序已最小化到后台运行")
def stop_clicking(self):
self.is_running = False
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.log("停止自动点击")
def get_cached_template(self, template_path):
"""获取缓存的模板图像,减少磁盘IO"""
template_name = os.path.basename(template_path)
if template_name not in self.template_cache:
template_img = cv2.imread(template_path)
if template_img is not None:
self.template_cache[template_name] = template_img
return template_img
return self.template_cache[template_name]
def human_like_mouse_move(self, start_x, start_y, end_x, end_y):
"""模拟人类鼠标移动轨迹"""
# 使用贝塞尔曲线模拟自然移动
steps = random.randint(10, 20)
control_x = random.randint(min(start_x, end_x), max(start_x, end_x))
control_y = random.randint(min(start_y, end_y), max(start_y, end_y))
for i in range(steps):
if not self.is_running:
return
t = i / steps
# 二次贝塞尔曲线
x = (1 - t) ** 2 * start_x + 2 * (1 - t) * t * control_x + t ** 2 * end_x
y = (1 - t) ** 2 * start_y + 2 * (1 - t) * t * control_y + t ** 2 * end_y
pyautogui.moveTo(int(x), int(y))
time.sleep(random.uniform(0.01, 0.05))
def silent_mouse_move(self, x, y):
"""静默移动鼠标(无动画效果)"""
# 使用win32api直接设置鼠标位置,不显示移动动画
try:
win32api.SetCursorPos((x, y))
except:
# 如果win32api失败,使用pyautogui的瞬时移动
pyautogui.moveTo(x, y, duration=0)
def random_delay(self):
"""随机延迟"""
if self.anti_detection_enabled:
delay = random.uniform(self.random_delay_min_var.get(), self.random_delay_max_var.get())
time.sleep(delay)
def add_click_offset(self, x, y):
"""添加点击位置偏移"""
if self.anti_detection_enabled and self.click_offset_var.get() > 0:
offset = self.click_offset_var.get()
x_offset = random.randint(-offset, offset)
y_offset = random.randint(-offset, offset)
return x + x_offset, y + y_offset
return x, y
def click_loop(self):
click_count = 0
max_clicks = self.click_count_var.get()
match_mode = self.match_mode_var.get()
region = self.get_screen_region()
self.loop_count = 0
# 获取当前鼠标位置
current_x, current_y = pyautogui.position()
while self.is_running and (max_clicks == 0 or click_count < max_clicks):
try:
# 检查是否需要清理缓存
self.loop_count += 1
if self.loop_count % self.cache_interval_var.get() == 0:
self.clean_cache()
# 截取屏幕 - 使用缓存优化
self.screenshot_count += 1
if self.last_screenshot is None or self.screenshot_count % 5 == 0:
# 每5次循环重新截图一次,减少截图频率
if region:
screenshot = pyautogui.screenshot(region=region)
else:
screenshot = pyautogui.screenshot()
self.last_screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
del screenshot # 及时释放内存
screenshot_cv = self.last_screenshot
# 根据匹配模式查找模板
matched_template = None
matched_position = None
matched_confidence = 0
enabled_templates = [t for t in self.templates if t["enabled"]]
if match_mode == "顺序匹配":
# 按顺序查找第一个匹配的模板
for template in enabled_templates:
result = self.match_template(screenshot_cv, template)
if result:
matched_template = template
matched_position = result["position"]
matched_confidence = result["confidence"]
break
elif match_mode == "随机匹配":
# 随机查找一个匹配的模板
random.shuffle(enabled_templates)
for template in enabled_templates:
result = self.match_template(screenshot_cv, template)
if result:
matched_template = template
matched_position = result["position"]
matched_confidence = result["confidence"]
break
elif match_mode == "最高置信度":
# 查找置信度最高的模板
for template in enabled_templates:
result = self.match_template(screenshot_cv, template)
if result and result["confidence"] > matched_confidence:
matched_template = template
matched_position = result["position"]
matched_confidence = result["confidence"]
# 如果找到匹配的模板,执行点击
if matched_template and matched_position:
# 调整点击位置(如果设置了区域限制)
if region:
click_x = matched_position[0] + region[0]
click_y = matched_position[1] + region[1]
else:
click_x, click_y = matched_position
# 应用点击位置偏移
click_x, click_y = self.add_click_offset(click_x, click_y)
# 根据设置移动鼠标
if self.silent_mode_enabled:
# 静默模式:直接设置鼠标位置,不显示移动
self.silent_mouse_move(click_x, click_y)
elif self.human_behavior_enabled:
# 模拟人类行为:使用曲线移动鼠标
self.human_like_mouse_move(current_x, current_y, click_x, click_y)
else:
# 普通模式:直接移动到目标位置
pyautogui.moveTo(click_x, click_y)
# 点击目标
pyautogui.click(click_x, click_y)
# 更新当前鼠标位置
current_x, current_y = click_x, click_y
# 记录点击
click_count += 1
self.click_history.append({
'time': time.strftime("%Y-%m-%d %H:%M:%S"),
'template': matched_template['name'],
'position': (click_x, click_y),
'confidence': matched_confidence
})
self.log(
f"点击 '{matched_template['name']}': ({click_x}, {click_y}), 置信度: {matched_confidence:.4f}, 总点击: {click_count}")
else:
if self.loop_count % 10 == 0: # 减少日志输出频率
self.log("未找到任何匹配的模板")
except Exception as e:
self.log(f"错误: {str(e)}")
# 等待指定间隔 + 随机延迟
base_interval = self.interval_var.get()
if self.anti_detection_enabled:
base_interval += random.uniform(self.random_delay_min_var.get(), self.random_delay_max_var.get())
time.sleep(base_interval)
if max_clicks > 0 and click_count >= max_clicks:
self.log(f"已完成指定点击次数: {max_clicks}")
# 停止运行
self.is_running = False
self.root.after(0, self.stop_clicking)
def match_template(self, screenshot, template):
try:
# 使用缓存的模板图像
template_img = self.get_cached_template(template["path"])
if template_img is None:
return None
# 执行模板匹配
result = cv2.matchTemplate(screenshot, template_img, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
# 检查是否找到目标
if max_val >= self.confidence_var.get():
h, w = template_img.shape[:2]
center_x = max_loc[0] + w // 2
center_y = max_loc[1] + h // 2
return {
"position": (center_x, center_y),
"confidence": max_val
}
except Exception as e:
self.log(f"匹配模板 '{template['name']}' 时出错: {str(e)}")
return None
def clean_cache(self):
"""清理内存缓存"""
try:
# 强制垃圾回收
gc.collect()
# 记录清理前的内存使用
memory_before = self.process.memory_info().rss / 1024 / 1024
# 清理OpenCV内部缓存
cv2.destroyAllWindows()
# 清理模板缓存(保留最近使用的)
if len(self.template_cache) > 10:
# 只保留前10个模板
keys_to_remove = list(self.template_cache.keys())[10:]
for key in keys_to_remove:
del self.template_cache[key]
# 记录清理后的内存使用
memory_after = self.process.memory_info().rss / 1024 / 1024
memory_freed = memory_before - memory_after
if memory_freed > 0:
self.log(f"缓存清理完成,释放了 {memory_freed:.2f} MB 内存")
except Exception as e:
self.log(f"清理缓存时出错: {str(e)}")
def force_clean_cache(self):
"""强制清理缓存"""
self.clean_cache()
messagebox.showinfo("缓存清理", "缓存清理完成")
def open_storage_location(self):
"""打开存储位置"""
try:
# 根据操作系统打开文件管理器
if platform.system() == "Windows":
os.startfile(self.storage_location)
elif platform.system() == "Darwin": # macOS
subprocess.Popen(["open", self.storage_location])
else: # Linux
subprocess.Popen(["xdg-open", self.storage_location])
self.log(f"已打开存储位置: {self.storage_location}")
except Exception as e:
messagebox.showerror("错误", f"打开存储位置时出错: {str(e)}")
def monitor_memory(self):
"""监控内存使用情况"""
try:
memory_usage = self.process.memory_info().rss / 1024 / 1024
self.memory_label.config(text=f"内存: {memory_usage:.1f} MB")
except:
pass
# 每隔10秒更新一次内存显示(减少频率)
self.root.after(10000, self.monitor_memory)
def log(self, message):
timestamp = time.strftime("%H:%M:%S")
log_message = f"[{timestamp}] {message}\n"
# 在主线程中更新UI
def update_log():
self.log_text.insert(tk.END, log_message)
self.log_text.see(tk.END)
# 限制日志长度,防止内存占用过大
if int(self.log_text.index('end-1c').split('.')[0]) > 500:
self.log_text.delete(1.0, 100.0)
self.root.after(0, update_log)
def clear_log(self):
self.log_text.delete(1.0, tk.END)
def load_config(self):
try:
# 首先尝试从存储位置加载配置
config_path = os.path.join(self.storage_location, self.config_file)
if not os.path.exists(config_path):
# 如果存储位置没有配置,尝试从当前目录加载
config_path = os.path.join(os.getcwd(), self.config_file)
if os.path.exists(config_path):
with open(config_path, 'r') as f:
config = json.load(f)
# 应用配置
if 'templates' in config:
self.templates = config['templates']
if 'storage_location' in config:
self.storage_location = config['storage_location']
self.storage_location_label.config(text=self.storage_location)
except Exception as e:
print(f"加载配置失败: {str(e)}")
def save_config(self):
try:
config = {
'templates': self.templates,
'storage_location': self.storage_location
}
# 保存配置到存储位置
config_path = os.path.join(self.storage_location, self.config_file)
with open(config_path, 'w') as f:
json.dump(config, f)
except Exception as e:
print(f"保存配置失败: {str(e)}")
def on_closing(self):
self.save_config()
self.is_running = False
self.root.destroy()
def main():
root = tk.Tk()
app = OptimizedAutoClicker(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
if __name__ == "__main__":
main()”
最新发布