教程
雷电模拟器脚本循环、停止条件与定时运行
让 LDPlayer 或 BlueStacks 上的宏带停止条件循环,在运行之间保存进度,用状态码退出,并在 MAS 定时任务中每天运行。
- Windows
- Mac
- 模拟器
- Studio
- Python SDK
本页目录
一个无人值守运行的宏需要三样东西:知道何时停止的循环、记住进行到哪里的方法,以及不用你动手就启动它的定时。这篇指南为 Macro Automation Studio(MAS)中的雷电模拟器脚本循环(LDPlayer 或 BlueStacks 上都一样)讲清这三样。你会得到循环模式、停止条件、运行之间的存储、Webhook 可以报告的退出码,以及定时任务。适合把宏从”我点运行”变成”它每天早上自己跑”的人。
开始之前
- 已安装并登录 MAS。试用期内可免费下载。
- 一个代码型项目能从 Code Editor 在模拟器上运行。参见快速开始。
- 模拟器已添加为设备,端口可以在它的卡片上找到。参见设备。
- 你的模板和区域已经截取。参见图像识别指南。
LDPlayer 和 BlueStacks 的脚本循环模式
三种形状覆盖几乎所有的宏。
带停止条件的 while。 循环一直跑,直到屏幕上或计数器里的某样东西说停。
import mas
images = mas.images({"collect": 401, "done": 402})
collected = 0
while collected < 50:
if mas.find_object(images.done):
break
button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
if button is None:
continue
mas.click(button.x, button.y, delay_ms=1000)
collected += 1固定次数的 for。 你知道这件事要做多少次。
for round_no in range(1, 11):
mas.log(f"Round {round_no} of 10")
button = mas.find_object_retry(images.collect)
if button is None:
mas.log("Nothing to collect, stopping early", level="warning")
break
mas.click(button.x, button.y)直到模板出现。 一个带截止时间的等待,用于加载页面和长计时器。
import time
def wait_for(image, timeout_s=120, every_s=3.0):
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
match = mas.find_object(image)
if match:
return match
time.sleep(every_s)
return None上面每个循环都有出口。find_object 找不到时返回 None,从不抛异常,所以模板缺失本身不能结束一个 while True。给每个循环一个边界。
脚本循环的停止条件
至少组合其中两个。一个防游戏,另一个防你自己的 bug。
- 计数器。
while collected < 50。便宜且可预测。 - 时间预算。
time.monotonic()按秒计数,时钟变化时也不会跳变。把它用在外层循环,这样定时运行总会在下一次开始前结束。 - 屏幕状态。 一个模板,比如”体力不足”、“背包已满”或每日上限提示。像其他模板一样在 Asset Lab 中截取,在每一轮开头检查。
- 连续失败次数。 数一数连续多少轮什么都没找到,五次之后放弃。一个你没有计划到的屏幕看起来正是这样。
started = time.monotonic()
misses = 0
while time.monotonic() - started < 20 * 60:
if mas.find_object(images.out_of_energy):
break
button = mas.find_object_retry(images.collect)
if button is None:
misses += 1
if misses >= 5:
break
continue
misses = 0
mas.click(button.x, button.y)停顿与拟人化节奏
click 在点击后已经等待 delay_ms=1000,swipe 默认持续 duration_ms=1000。在轮次之间,加入一个有变化的停顿。
import random
def pause(low=0.8, high=2.5):
time.sleep(random.uniform(low, high))固定间隔看起来很机械,还会和游戏抢时间。点击后 900 毫秒才出现的弹窗,在慢的日子里会从固定的 1000 毫秒延迟中溜过去。随机停顿两者都能缓和。click 上的 delay_ms 留给应用自己的反应时间,pause() 用于人的节奏。
用存储保存进度
中途停止的定时运行,下一次应该从上次的位置继续。存储函数为每个任务保存一个小的 JSON 文档,键由这台电脑、设备的端口和你选的任务名组成。
TASK = "daily_collect"
state = mas.retrieve(TASK) # {} on the first run
collected = state.get("collected", 0)
last_day = state.get("day")
mas.save(TASK, {"collected": collected, "day": today}) # after each pass of the loop
if collected >= 50:
mas.clear(TASK) # start fresh next timesave(task_name, data) 写入或替换条目;retrieve(task_name) 返回字典或空字典;clear(task_name) 删除它。data 必须可以 JSON 序列化。因为端口是键的一部分,运行同一个宏的两个模拟器实例永远不会共享计数器。想要总数时,retrieve_all(task_name) 返回每个实例的条目。参见存储。
用退出码退出,让 Webhook 报告状态
脚本结束时 MAS 读取进程的退出码。零表示运行完成;其他任何值都标记为失败。Webhook 订阅者相应地收到 macro.completed 或 macro.failed,载荷里带 exit_code。
import sys
if energy is None:
mas.log("Could not read energy", level="error")
sys.exit(2) # macro.failed, exit_code 2
mas.log("All done")
sys.exit(0) # macro.completed未处理的异常也会以非零退出,所以崩溃无需额外代码就会被报告为失败。对于只有你的脚本知道的事件,mas.webhook("level.reached", {"level": 40}) 会向订阅了自定义事件的端点发送一个 custom.level.reached 事件。在 Webhook 页设置端点;参见 Webhook。
一个完整的示例
这个宏每天最多领取五十个奖励,记住自己的计数,在”体力不足”或二十分钟后停止,并通过退出码报告。把 ID 换成你的。
import datetime
import random
import sys
import time
import mas
images = mas.images({
"collect": 401,
"close_popup": 402,
"out_of_energy": 403,
})
TASK = "daily_collect"
DAILY_LIMIT = 50
TIME_BUDGET_S = 20 * 60
def pause(low=0.8, high=2.5):
time.sleep(random.uniform(low, high))
def main():
today = datetime.date.today().isoformat()
state = mas.retrieve(TASK)
collected = state.get("collected", 0) if state.get("day") == today else 0
mas.log(f"{today}: starting at {collected} of {DAILY_LIMIT}")
started = time.monotonic()
misses = 0
while collected < DAILY_LIMIT:
if time.monotonic() - started > TIME_BUDGET_S:
mas.log("Time budget reached", level="warning")
break
popup = mas.find_object(images.close_popup)
if popup:
mas.click(popup.x, popup.y, delay_ms=800)
continue
if mas.find_object(images.out_of_energy):
mas.log("Out of energy")
break
button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
if button is None:
misses += 1
if misses >= 5:
mas.log("Five misses in a row, giving up", level="error")
mas.save(TASK, {"collected": collected, "day": today})
sys.exit(2)
continue
misses = 0
mas.click(button.x, button.y, delay_ms=1000)
collected += 1
mas.save(TASK, {"collected": collected, "day": today})
pause()
mas.log(f"Finished at {collected} of {DAILY_LIMIT}")
sys.exit(0)
if __name__ == "__main__":
main()先在 Code Editor 中用运行(F5)跑一次,确认它能自己结束,再设置定时。
在 BlueStacks 或 LDPlayer 上用 MAS 定时任务运行宏
定时任务位于桌面应用内部,在你设定的时间在模拟器上启动宏。
- 打开定时任务,点击新建计划。
- 输入名称(3 到 100 个字符),选择宏。
- 在模拟器端口下点击扫描并选择实例的端口,或者直接输入。
- 设置日期和时间。时间是你电脑的本地时间。
- 设置重复:无表示只运行一次,每天,每周并勾选星期几,或者每月。
- 重复次数保持为 1,除非你想让宏在每次触发时连续运行多次。
- 保持启用打开,点击创建计划。
依赖它之前要知道的事:
- 应用必须保持打开并登录。定时任务不是系统服务,也没有 cron 语法。
- 任务每 30 秒检查一次,所以运行可能比设定时间晚最多半分钟开始。
- 一个计划针对一个端口。到时间时端口正被另一次运行占用,定时任务会跳过这一轮,下一轮再试。
- 两个计划不能在同一个端口共用一个时间段;应用会拒绝并提示 “Time slot is occupied on this port”。
- 最多 20 个定时任务可以同时运行。
- 重复次数大于 1 时,一次失败的运行(非零退出)会停止剩余的重复,并把任务标记为失败。
- 定时运行使用设备卡片上保存的参数,或设备跟随的设置方案。参见设置方案。
- 任务上的查看历史显示每次运行及其状态和日志。
定时任务页介绍编辑、跳过和取消任务。
在组上运行
要手动在多个实例上运行同一个宏,把它们放进一个设备组,给每台设备分配宏,点击全部启动。设备依次启动,间隔半秒,每台都有自己的运行、日志和退出码。要为组设置定时,为每个端口创建一个宏和时间相同的定时任务。多开指南介绍按设备的参数、代理和存储。
可能出什么问题
循环永远不结束
一个没有边界的 while True,或者一个依赖永远不会出现的模板的停止条件。加上用 time.monotonic() 的时间预算和连续失败计数器;这两个能结束任何循环。
定时没有触发
到时间时应用已关闭或处于休眠、端口被占用,或者任务没有启用。在查看历史中查找跳过或失败的条目,并在定时窗口内让电脑保持唤醒。
Time slot is occupied on this port
同一个端口上的另一个任务已经占用了那个时间。把一个任务挪开几分钟,或者用重复次数把第二个宏放进第一个里。
运行显示失败,但宏完成了
脚本在最后一个动作之后以非零退出码或未处理的异常结束。阅读日志的最后几行;清理路径里的 sys.exit(1),或存储状态上的 KeyError,是常见原因。
没有任何自动化工具是 100% 无风险的,请负责任地使用自动化,并自行斟酌决定。
下一步
相关页面
谢谢。如果有哪里不对,请在 Discord 告诉我们。
有疑问? 在 Discord 提问