脚本运行实况
这是多开后台脚本钓鱼的视频
三国杀多开钓鱼脚本钓鱼实况
前台钓鱼脚步开发思路
模拟人钓鱼的思路,人钓鱼,需要人去实时的看张力以及爆发进度。张力低,快速点击钓鱼按钮,爆发进度到了100%,同时张力不低的情况就可以进行爆发。为此脚本也是如此,基本框架就是两个线程,一个线程作为监控线程,使用opencv去获取张力与进度,一个线程进行操作按钮。
本人非科班出现,代码会比较难看,讲究一个能用就行。
第一个实现的代码就是如何捕获模拟器的窗口,这里使用pyautogui或者dxcam即可
窗口捕获代码
class ScreenCapturer:
def __init__(self, region: tuple = None, target_fps: int = 60):
"""
参数:
- region: (left, top, width, height) 截图区域
- target_fps: 目标采集帧率
"""
self.camera = dxcam.create(output_idx=0, output_color="BGR")
self.region = region
self.target_fps = target_fps
self._setup_capture()
# self.fps=HighPerfFPS()
def _setup_capture(self):
"""配置采集参数"""
if self.region:
left, top, width, height = self.region
self.camera.start(region=(left, top, left + width, top + height),
target_fps=self.target_fps)
else:
self.camera.start(target_fps=self.target_fps)
def get_frame(self) -> np.ndarray:
"""获取最新帧(非阻塞模式)"""
frame = self.camera.get_latest_frame()
if frame is None:
raise RuntimeError("无法获取屏幕帧")
return frame
def stop(self):
"""停止采集"""
self.camera.stop()
由于dxcam需要窗口的位置信息,所有其实第一步需要开发获取模拟器窗口位置信息的脚本。由于WSA可用直接调整窗口大小,模拟器得自己来。为了适配WSA以及模拟器的不同情况,通过标题判断是否是WSA,如果是就使用代码调整(内部有一部分无用代码),窗口定位由SimpleWindowLocator实现,GameOperator继承该类同时添加了点击与拖动功能。
class SimpleWindowLocator:
def __init__(self, title_keywords):
self.keywords = [title_keywords] if isinstance(title_keywords, str) else title_keywords
self.dpi_scale = self._get_dpi_scale()
self.valid_windows = []
def _get_dpi_scale(self):
"""通过Tkinter获取DPI缩放比例"""
root = tk.Tk()
root.withdraw() # 隐藏临时窗口
dpi = root.winfo_fpixels('1i')
root.destroy()
return dpi / 96.0
def _is_target_window(self, window):
"""使用模糊匹配验证窗口"""
try:
title = window.title.lower()
# 组合匹配逻辑:至少包含一个关键词且不包含排除词
return (any(re.search(kw.lower(), title) for kw in self.keywords)
and "error" not in title) # 示例排除词
except:
return False
def get_window_rect(self):
all_windows = gw.getAllWindows()
self.valid_windows.clear()
for w in all_windows:
try:
# 验证窗口有效性并检查可见性
if (self._is_target_window(w)
and w.visible
and not w.isMinimized): # 排除最小化窗口
self.valid_windows.append(w)
except Exception as e:
print(f"窗口 {w.title} 检查失败: {str(e)}")
continue
if not self.valid_windows:
raise WindowNotFound(f"未找到含 {self.keywords} 的可见窗口")
target= self.valid_windows[0]
if len(self.valid_windows)>1:
raise ConfigFail(f'存在多个匹配窗口,不要将脚本放到名为(MUMU/雷电)的文件夹\n请修改脚本存放文件夹名称,当前钓鱼窗口名字匹配了{len(self.valid_windows)}个窗口\n请从下列匹配列表填写完整模拟器名称\n[{" | ".join([i.title for i in self.valid_windows])}]')
# DPI转换补偿计算
def scale(value):
return int(value * self.dpi_scale + 0.5) # 四舍五入
return (
scale(target.left),
scale(target.top),
scale(target.width),
scale(target.height)
)
class GameOperator(SimpleWindowLocator):
def __init__(self, keywords,settings):
super().__init__(keywords)
self.controller = TouchController(drag_duration=settings['delay'])
self.bias_y=settings['title_height']
self.window_rect = self.get_window_rect()
self.gc=GameCoordinate(self.window_rect,self.bias_y)
print('当前比率',((self.window_rect[3]-self.bias_y)/self.window_rect[2]),'推荐比率16:9(0.5625)')
if settings['window_title']== '三国杀':
if 0.562<((self.window_rect[3] - 30) / self.window_rect[2])<0.563:
print('窗口大小合适')
else:
width=800
height=450+self.bias_y
print(f'重新设置模拟器窗口 宽度{width},高度{height}')
self.valid_windows[0].resizeTo(width, height)
self.window_rect = self.get_window_rect()
print(f'{self.window_rect}')
self.gc = GameCoordinate(self.window_rect,self.bias_y)
resize_btn=(self.gc.cal_pose(1,1)[0]-50,self.gc.cal_pose(1,1)[1]-50)
self.controller.click(resize_btn,0.5)
time.sleep(2)
messagebox.showinfo("提示","重设游戏窗口比例16:9")
self.kbs={'up':settings['up'],'down':settings['down'],'left':settings['left'],'right':settings['right'],
'feng':settings['wind'],'huo':settings['fire'],'lei':settings['thunder'],'dian':settings['electricity']}
def lambda_click(self,x,y):
self.controller.click(self.gc.cal_pose(x,y),0.1)
def click_relative(self, rel_x, rel_y,delay=0.1):
"""点击相对窗口的位置(0~1范围)"""
self.controller.click(self.gc.cal_pose(rel_x,rel_y),delay)
def mouse_click(self,rel_x,rel_y):
self.controller.mouse_click(self.gc.cal_pose(rel_x,rel_y))
def drag_relative(self, rel_x,rel_y,dir,dis=100,duration=None):
"""窗口内相对坐标拖拽"""
self.controller.drag(self.gc.cal_pose(rel_x,rel_y),dir,dis,duration)
def kill(self,res):
for action in res:
self.lambda_click(*self.kbs[action])
这是控制类TouchController主要封装了Pyautogui的功能,这里给的坐标都是屏幕坐标,为了后续在不同大小屏幕进行匹配,在GameOperator的xy,是rel_x,rel_y,即比率坐标,rel_x=0.5也就是模拟器窗口x方向的中间,全局坐标还需加上模拟器窗口左上角的坐标,需要一个类(GameCoordinate)去转换相对到全局
class Direction(Enum):
"""简化后的方向枚举"""
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
class TouchController:
def __init__(self,
default_offset=1,
drag_duration=(0.5, 1.0),
click_delay=(0.1, 0.2)):
self.default_offset = default_offset
self.drag_duration = drag_duration
self.click_delay = click_delay
# 初始化安全参数
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.001
def _get_real_pos(self, pos):
"""添加随机偏移的实际操作位置"""
return (
pos[0] + random.randint(-self.default_offset, self.default_offset),
pos[1] + random.randint(-self.default_offset, self.default_offset)
)
def drag(self, start_pos, direction, distance,drag_duration=None):
if not isinstance(direction, Direction):
raise ValueError("必须使用Direction枚举指定方向")
# 计算带随机角度的方向向量
base_x, base_y = direction.value
angle = math.radians(random.uniform(-3, 3)) # 小范围随机角度
dx = distance * (base_x * math.cos(angle) - base_y * math.sin(angle))
dy = distance * (base_x * math.sin(angle) + base_y * math.cos(angle))
# 拟真参数计算
if drag_duration:
duration = random.uniform(*drag_duration)
else:
duration = random.uniform(*self.drag_duration)
start_pos = self._get_real_pos(start_pos)
try:
# 带加速度曲线的拖拽
pyautogui.moveTo(start_pos, duration=0.0)
pyautogui.dragRel(
dx, dy,
duration=duration,
tween=pyautogui.easeInOutQuad,
button='left'
)
return (round(dx), round(dy))
except pyautogui.FailSafeException:
self._handle_failsafe()
return None
def click(self, target_pos,delay):
pyautogui.click(*target_pos,duration=delay)
return None
def mouse_click(self,target_pos):
actual_pos = self._get_real_pos(target_pos)
pyautogui.mouseDown(*actual_pos)
time.sleep(random.uniform(*self.click_delay))
pyautogui.mouseUp(*actual_pos)
def _random_approach(self, target_pos):
"""随机路径接近目标位置"""
waypoints = [
(target_pos + random.randint(-20, 20),
target_pos + random.randint(-20, 20))
for _ in range(random.randint(1, 3))
]
for pos in waypoints:
pyautogui.moveTo(
pos,
duration=random.uniform(0.05, 0.2),
tween=pyautogui.easeOutQuad
)
def _handle_failsafe(self):
"""安全机制触发处理"""
pyautogui.alert("操作已中断!")
pyautogui.moveTo(50, 50)
class GameCoordinate:
def __init__(self,window_rect,bias_y):
self.bias_y=bias_y
self.window_rect=(window_rect[0],window_rect[1],window_rect[2],window_rect[3]-self.bias_y)
def cal_pose(self,rel_x,rel_y):
abs_x = self.window_rect[0] + self.window_rect[2] * rel_x
abs_y = self.window_rect[1] + self.window_rect[3] * rel_y+self.bias_y
return round(abs_x), round(abs_y)
def cal_ref_pose(self,rel_x,rel_y):
abs_x = self.window_rect[2] * rel_x
abs_y = self.window_rect[3] * rel_y+self.bias_y
return round(abs_x), round(abs_y)
如何让我们的张力保持在合适的值下面,使用经典的pid控制器即可,从监控线程获取张力,计算误差给出点击延迟,误差大,点击频率快。
class PIDClickController:
def __init__(self, target_process=0.8,
pid_params=(0.5, 0.1, 0.05),
delay_range=(0.1, 2.0)):
# PID参数
self.Kp, self.Ki, self.Kd = pid_params
self.target = target_process
self.delay_min, self.delay_max = delay_range
# PID状态变量
self.last_error = 0
self.integral = 0
self.last_time = time.perf_counter()
self.last_process = 0
# 低通滤波器参数(用于微分项)
self.alpha = 0.3 # 滤波系数
# 安全参数
self.stability_threshold = 0.02 # 稳定判定阈值
self.stable_counter = 0
def update(self, current_process):
# 计算时间差
now = time.perf_counter()
dt = now - self.last_time
dt=max(dt,1e-9)
self.last_time = now
# 计算误差
error = self.target - current_process
# 积分项(带抗饱和)
self.integral += error * dt
self.integral = np.clip(self.integral, -2.0, 2.0)
# 微分项(带低通滤波)
derivative = (error - self.last_error) / dt
filtered_derivative = self.alpha * derivative + (1 - self.alpha) * self.last_error
# 计算PID输出
output = (self.Kp * error +
self.Ki * self.integral +
self.Kd * filtered_derivative)
# 保存误差状态
self.last_error = error
# 转换输出为延迟时间(反向关系)
base_delay = np.clip(1/output, 0.001, 0.5)
# 动态调整范围
# adjusted_delay = np.clip(base_delay, self.delay_min, self.delay_max)
# 稳定性检测
if abs(error) < self.stability_threshold:
self.stable_counter += 1
if self.stable_counter > 5:
# 进入稳定状态后降低积分累积
self.integral *= 0.9
else:
self.stable_counter = 0
return base_delay
最重要的监控线程,起着我们的眼睛作用,获取钓鱼界面的所有信息,是否在钓鱼,张力值,爆发进度,是否进入斩杀,斩杀需要按下那些按钮
def get_path(file):
bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
path = str(os.path.join(bundle_dir, file))
return path
def cv_imread(file_path):
#解决中文路径
cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), cv2.IMREAD_COLOR)
return cv_img
def phash(image, hash_size=32, dct_size=8):
# 转换为灰度图并调整尺寸
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray, (hash_size, hash_size), interpolation=cv2.INTER_LINEAR)
# 2. 转换为浮点并计算DCT
dct_input = resized.astype(np.float32) / 255.0
dct_result = cv2.dct(dct_input)
# 3. 保留低频区域(左上角dct_size x dct_size)
low_freq = dct_result[:dct_size, :dct_size]
# 4. 计算哈希值(排除直流分量)
median = np.median(low_freq[1:, 1:])
hash_binary = (low_freq > median).flatten().astype(int).tolist()
return hash_binary
def similar(hash1, hash2):
if len(hash1) != len(hash2):
raise ValueError("哈希值长度不一致")
distance = sum(b1 != b2 for b1, b2 in zip(hash1, hash2))
similarity = 1 - distance / len(hash1)
return distance, similarity
class MatchBTN:
def __init__(self, region,settings):
self.up = cv2.cvtColor(cv_imread(get_path('u.png')), cv2.COLOR_BGR2GRAY)
self.down = cv2.cvtColor(cv_imread(get_path('d.png')), cv2.COLOR_BGR2GRAY)
self.left = cv2.cvtColor(cv_imread(get_path('l.png')), cv2.COLOR_BGR2GRAY)
self.right = cv2.cvtColor(cv_imread(get_path('r.png')), cv2.COLOR_BGR2GRAY)
self.feng = cv2.cvtColor(cv_imread(get_path('feng.png')), cv2.COLOR_BGR2GRAY)
self.huo = cv2.cvtColor(cv_imread(get_path('huo.png')), cv2.COLOR_BGR2GRAY)
self.lei = cv2.cvtColor(cv_imread(get_path('lei.png')), cv2.COLOR_BGR2GRAY)
self.dian = cv2.cvtColor(cv_imread(get_path('dian.png')), cv2.COLOR_BGR2GRAY)
self.btn_dict = {'up': self.up, 'down': self.down, 'left': self.left, 'right': self.right,
'feng': self.feng, 'huo': self.huo, 'lei': self.lei, 'dian': self.dian}
self.conf = settings['detect_conf']
self.kbtn_size = (round(region[2] / 20), round(region[2] / 20))
# print('斩杀按钮大小',self.kbtn_size)
self.resize(self.kbtn_size)
def crop(self,image, center, width, height):
x, y = center
half_w = int(width // 2)
half_h = int(height // 2)
x1 = max(0, int(x) - half_w)
y1 = max(0, int(y) - half_h)
x2 = min(image.shape[1], x + half_w)
y2 = min(image.shape[0], y + half_h)
cropped = image[y1:y2, x1:x2]
return cropped
def match(self, image,region):
# image=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
keys=[]
#删除了匹配代码
return keys
def resize(self, size):
for key, img in self.btn_dict.items():
self.btn_dict[key] = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR)
class GameInfo(threading.Thread):
def __init__(self, region,settings):
threading.Thread.__init__(self)
self.process = 0.0
self.cy_process = 0.0
self.conf = 0.85
self.kill_conf=0.65
self.bias_y = settings['title_height']
self.is_baigan = False
if settings['window_title']== '三国杀':
self.cap_region = (region[0] + 7, region[1], region[2] - 14, region[3] - 7)
else:
self.cap_region = region
self.gc = GameCoordinate(region, self.bias_y)
self.region = region
self.fish_size = (round(region[2] / 20), round(region[2] / 20))
self.kill_size = (round(region[2] / 20), round(region[2] / 20))
self.baigan_size = (round(region[2] / 10), round((region[3] - 30) / 15))
self.block_size = round(0.04944 * self.region[2])
self.kill_match = MatchBTN(region,settings)
self.capture = ScreenCapturer(self.cap_region, target_fps=settings['cap_fps'])
self.PID_Click = PIDClickController(settings['progress_tracking'], (settings['kp'], settings['ki'], settings['kd']), (0.001, 0.1))
self.click_delay = 0
self._running = True
self.daemon = True
self._start_fish = False
self.finish = cv2.resize(cv_imread(get_path('finish.png')), self.fish_size,
interpolation=cv2.INTER_LINEAR)
self.baigan = cv2.resize(cv2.cvtColor(cv_imread(get_path('baigan.png')), cv2.COLOR_BGR2GRAY), self.baigan_size,
interpolation=cv2.INTER_LINEAR)
self.up = cv2.resize(cv_imread(get_path('u.png')), self.kill_size, interpolation=cv2.INTER_LINEAR)
self.bg_bright = np.mean(
cv2.cvtColor(cv2.resize(cv_imread(get_path('baigan.png')), self.baigan_size, interpolation=cv2.INTER_LINEAR),
cv2.COLOR_BGR2HSV)[:, :, 2])
self._start_kill = False
self._kill_res = []
self.bagan_check = True
self.baigan_cood = ()
self.lock = threading.Lock()
self.sets=settings
def run(self):
count=0
while True:
if self._running:
count+=1
image = self.capture.get_frame()
self.process = self.get_process(image)
self.click_delay = self.PID_Click.update(self.process)
with self.lock:
self._start_kill = self.detect_kill(image)
with self.lock:
self._start_fish = self.detect_finish(image)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
if self.bagan_check:
res = self.check_exist(gray_image, self.baigan, 0.8)
if res[0]:
self.bagan_check = False
self.baigan_cood = (int(res[1]), int(res[2])), (
int(res[1] + self.baigan_size[0]), int(res[2] + self.baigan_size[1]))
print('摆杆点', self.baigan_cood)
x=round(int(res[1] + self.baigan_size[0]//2)/self.region[2],4)
y=round(int(res[2] + self.baigan_size[1]//2)/self.region[3],4)
self.sets['baigan_p']=(x,y)
self.cy_process = self.detect_ciyu(image)
# print(f'钓鱼进度{self.process},爆发进度:{self.cy_process}')
if not self.bagan_check and count%5==0 :
self.is_baigan = self.detect_baigan(image)
self._start_fish = self._start_fish or self.is_baigan
if self.start_kill:
with self.lock:
self._kill_res = self.match_kill(gray_image)
if count>60:
count=0
if self.sets['debug']:
cv2.rectangle(image,self.gc.cal_ref_pose(*self.sets['process_tl']),self.gc.cal_ref_pose(*self.sets['process_br']),(0,0,255))
cv2.rectangle(image,self.gc.cal_ref_pose(*self.sets['cy_tl']),self.gc.cal_ref_pose(*self.sets['cy_br']),(0,0,255))
cv2.putText(image,f'{round(self.process,2)}',self.gc.cal_ref_pose(*self.sets['process_tl']),cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255))
cv2.putText(image,f'{round(self.cy_process,2)}',self.gc.cal_ref_pose(*self.sets['cy_tl']),cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255))
cv2.imshow('debug',image)
cv2.waitKey(1)
else:
pass
@property
def kill_res(self):
with self.lock:
return self._kill_res
@property
def start_fish(self):
with self.lock:
return self._start_fish
@property
def start_kill(self):
with self.lock:
return self._start_kill
def check_exist(self, image, ck_img, t):
ret = cv2.matchTemplate(image, ck_img, cv2.TM_CCOEFF_NORMED)
res = np.where(ret > t)
if len(res[0]) > 0:
return True, res[1][0], res[0][0]
else:
return False, 0, 0
def find_region(self, image):
pass
def get_process(self, image):
tl = self.gc.cal_ref_pose(*self.sets['process_tl'])
br = self.gc.cal_ref_pose(*self.sets['process_br'])
process_image = image[tl[1]:br[1], tl[0]:br[0]]
gray_image = cv2.cvtColor(process_image, cv2.COLOR_BGR2GRAY)
_, binary_img = cv2.threshold(gray_image, 160, 255, cv2.THRESH_BINARY)
h, w = binary_img.shape
process = binary_img[round(h / 2), 0:w]
process_value=sum(p > 0 for p in process) / process.size
return process_value
def detect_baigan(self, image):
tl = self.baigan_cood[0]
br = self.baigan_cood[1]
process_image = image[tl[1]:br[1], tl[0]:br[0]]
gary_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
res = self.check_exist(gary_image, self.baigan, 0.8)
if res[0]:
hsv_image = cv2.cvtColor(process_image, cv2.COLOR_BGR2HSV)
avg_bright = np.mean(hsv_image[:, :, 2])
if abs(self.bg_bright - avg_bright) < 10:
return True
else:
return False
else:
return False
def detect_ciyu(self, image):
tl = self.gc.cal_ref_pose(*self.sets['cy_tl'])
br = self.gc.cal_ref_pose(*self.sets['cy_br'])
process_image = deepcopy(image[tl[1]:br[1], tl[0]:br[0]])
gray_image = cv2.cvtColor(process_image, cv2.COLOR_BGR2GRAY)
_, binary_img = cv2.threshold(gray_image, 160, 255, cv2.THRESH_BINARY)
h, w = binary_img.shape
process = binary_img[round(h / 2), 0:w]
process_value = sum(p > 0 for p in process) / process.size
return process_value
def detect_finish(self, image):
# image=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
return self.check_exist(image, self.finish, 0.75)[0]
def detect_kill(self, image):
res = self.check_exist(image, self.up, 0.75)
return res[0]
def match_kill(self, image):
import time
tl = self.gc.cal_ref_pose(*self.sets['kill_tl'])
br = self.gc.cal_ref_pose(*self.sets['kill_br'])
process_image = deepcopy(image[tl[1]:br[1], tl[0]:br[0]])
#这里是按钮匹配代码
return results
def stop_cap(self):
self._running = False
def start_cap(self):
self._running = True
def down_conf(self):
self.kill_conf = self.kill_match.conf
if self.kill_conf >= 0.5:
self.kill_match.conf = self.kill_conf - 0.02
else:
self.kill_match.conf = 0.8
操作线程放在主线程运行,操作线程需要获取相关的配置参数,如张力框的区域,爆发条的区域,每个按钮的位置。
def delayMsecond(t):
start, end = 0, 0
start = time.time_ns() # 精确至ns级别
while end - start < t * 1000000:
end = time.time_ns()
def main(settings):
try:
# 初始化定位器
ss=get_system_scale()
settings['scale']=ss
if ss!=1.0:
raise ConfigFail('系统缩放请设置为100%')
qm = EscMonitor(settings['esc_ms'])
qm.start()
locator = GameOperator([settings['window_title']],settings)
# 获取窗口位置信息
left, top, width, height = locator.get_window_rect()
print(locator.get_window_rect())
screen_rect = get_real_resolution()
if left+width>screen_rect[0] or top+height>screen_rect[1]:
raise ConfigFail('模拟器窗口请勿贴边')
standard_height=round(width*0.5625)
if not (standard_height-5<height - settings['title_height']<standard_height+5):
raise ConfigFail(f'游戏窗口配置出错,先设置模拟器800x450,\n当前模拟器窗口:X={left} Y={top} 宽度={width} 高度={height}\n(尝试)请调整"标题栏高度"让屏幕比率接近0.5625\n当前比率:{(height - settings["title_height"])/width},"标题栏高度"参数尝试设置{round(height-width*0.5625)}附近')
game_info = GameInfo((left, top, width, height),settings)
game_info.start()
switch_lure=time.perf_counter()
while True:
delayMsecond(500)
if not game_info.start_fish:#释放鱼饵与刺鱼
locator.mouse_click(*settings['fish'])
delayMsecond(2000)
locator.drag_relative(*settings['fish'], Direction.UP, 100)
if time.perf_counter() - switch_lure > 6:
print('切换鱼饵')
locator.mouse_click(*settings['switch'])
switch_lure = time.perf_counter()
continue
else:
delayMsecond(settings['delay_ms'])
locator.click_relative(*settings['fish'])
delayMsecond(800)
if game_info.start_fish:#钓鱼函数
bg_time=time.perf_counter()#摆杆计时器
while True:
delayMsecond(max(1.5, round(game_info.click_delay * 1000)))
locator.click_relative(*settings['click'], 0.001)
bp = game_info.cy_process
if game_info.is_baigan and time.perf_counter()-bg_time>1:
game_info.is_baigan = False
bg_time=time.perf_counter()
locator.drag_relative(*settings['baigan_p'], Direction.LEFT)
locator.drag_relative(*settings['baigan_p'], Direction.RIGHT)
if bp >= 0.99:
locator.drag_relative(*settings['click'], Direction.UP)
delayMsecond(1000)
if not game_info.start_fish:
delayMsecond(800)
if game_info.start_fish:
continue
else:
delayMsecond(1000)
print(f'斩杀与结束判断?{"斩杀" if game_info.start_kill else "结束"}')
kill = False
if game_info.start_kill:
kill = True
print('进入斩杀')
while game_info.start_kill:
res = game_info.kill_res
game_info.stop_cap()
if res:
locator.kill(res)
delayMsecond(2000)
game_info.start_cap()
delayMsecond(100)
if game_info.start_kill:
pass
# game_info.down_conf()
else:
break
# game_info.down_conf()
else:
game_info.start_cap()
print('结束斩杀')
delayMsecond(1500)
if game_info.start_fish:
print('斩杀失败')
else:
if not kill:
print('完成一条')
switch_lure = time.perf_counter()
break
delayMsecond(settings['fish_wait'])
except WindowNotFound as e:
messagebox.showerror("错误", str(e))
except ConfigFail as e:
messagebox.showerror("参数错误",str(e))
if __name__ == "__main__":
multiprocessing.freeze_support()
root = tk.Tk()
app = FishingSettingsWindow(root)
root.mainloop()
main(app.settings)
上面main函数需要一个参数settings,这个我是从图形化界面设定并传递的,将相关参数打包成dict类型传给main去构造相关对象。
为了能随时退出,再写了一个类开了一个线程去监控esc
class EscMonitor:
def __init__(self, detection_interval=0.05):
"""
参数:
- detection_interval: 检测间隔(秒),默认0.01秒(10ms)
"""
self.detection_interval = detection_interval
self._exit_flag = False
self._thread = None
# 根据操作系统选择检测方式
self._check = self._windows_check
def _windows_check(self):
"""Windows系统专用检测方法"""
import ctypes
VK_ESCAPE = 0x1B
return ctypes.windll.user32.GetAsyncKeyState(VK_ESCAPE) & 0x8000 != 0
def _monitor(self):
"""后台监控线程"""
while not self._exit_flag:
if self._check():
print('用户退出')
os._exit(1)
time.sleep(self.detection_interval)
def start(self):
"""启动监控"""
if not self._thread or not self._thread.is_alive():
self._exit_flag = False
self._thread = threading.Thread(target=self._monitor, daemon=True)
self._thread.start()
def stop(self):
"""停止监控"""
self._exit_flag = True
if self._thread and self._thread.is_alive():
self._thread.join(timeout=1)