强化学习实战-用强化学习打跑酷游戏 GreatWallRun 第三节 强化学习层构建 临时笔记
强化学习层技术笔记 — GreatWallRun基于 Stable Baselines3 PPO 的手游自动化 RL 训练系统核心代码ppo_acting.py/game_env.py/train_ppo.py一、整体架构三层分离 多线程通信1.1 设计哲学系统严格遵循“感知层只感知决策层只决策”的职责分离原则。三层之间通过共享内存变量和同步原语通信不允许跨层直接调用。┌─────────────────────────────────────────────────────────────────┐ │ train_ppo.py │ │ (训练编排层) │ │ PPO(MlpPolicy, env, tensorboard_log...) │ │ model.learn(total_timesteps100000) │ │ 断点续训 / 最优模型保存 / CtrlC 中断保护 │ └──────────────────────────┬──────────────────────────────────────┘ │ env.step(action) │ env.reset() ▼ ┌─────────────────────────────────────────────────────────────────┐ │ game_env.py │ │ (环境决策层 — Master) │ │ │ │ gymnasium.Env 接口: │ │ • observation_space: Box(207,) — 200 terrain 7 state │ │ • action_space: Discrete(3) — 0:待机 1:跳 2:射 │ │ │ │ 内部逻辑: │ │ • _calculate_reward(action) — 8 项奖励/惩罚 │ │ • _get_obs() — 构建 207 维观测向量 │ │ • _update_terrain() — 200 维地形采样 │ │ • _update_run_stats(action) — 当局统计 │ │ │ │ 与 Worker 通信: │ │ • queue.put(action) → 发送动作指令 │ │ • action_done.wait() ← 等待执行完成 │ │ • 读取共享状态变量 ← 感知数据 │ │ • death_triggered / env_restart_order ⇄ 死亡重开握手 │ └──────────────────────────┬──────────────────────────────────────┘ │ queue.Queue(maxsize1) │ threading.Event (action_done) │ 共享变量 (共享内存) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ ppo_acting.py │ │ (感知执行层 — Worker) │ │ │ │ Thread 1 — 帧抓取线程 (全速 30 FPS): │ │ cap.read() → _latest_frame (共享缓冲Lock 保护) │ │ │ │ Thread 2 — 主循环 (YOLO 渲染 动作执行): │ │ _latest_frame → 悬崖检测 → YOLO → RuleEngine → 更新共享状态 │ │ → 处理动作队列 → 渲染 3 窗口 → cv2.waitKey(1) │ │ │ │ Thread 3 — OCR 线程 (每 0.3s): │ │ _latest_frame → Tesseract → shared_lives/arrows/coins/distance │ └─────────────────────────────────────────────────────────────────┘1.2 为什么不用 OpenAI Gym 标准的单进程模式标准 Gym 中env.step()内部直接执行动作并返回下一状态。但在本项目中动作延迟不可忽略— ADB 点击到游戏画面变化有 300-500ms 延迟必须等待感知必须持续运行— YOLO 推理不能因为step()没被调用就停止否则画面滞后多消费者— OCR、YOLO、game_env 三者都需要最新的游戏画面因此采用了独立 daemon 线程 队列同步的方案ppo_acting的感知循环永远运行不依赖step()调用game_env.step()只是一个下单 → 等结果的同步点这种模式类似于异步环境或client-server RL architecture1.3 同步原语设计原语类型方向语义env_action_queuequeue.Queue(maxsize1)Master → Worker动作指令容量为 1 强制同步action_donethreading.EventWorker → Master动作已执行 冷却完成 感知已更新perception_readythreading.EventWorker → Master首帧感知完成环境可以开始death_triggeredboolWorker → Master检测到死亡lives0 或距离停滞env_restart_orderboolMaster → Worker确认死亡执行重开点击流程_stats_lockthreading.Lock双向保护训练统计 dict 的读写_frame_lockthreading.Lock单向保护_latest_frame的并发访问二、观测空间设计2.1 总览observation_space Box(0, 1, shape(207,), dtypefloat32) ┌─────────────────────┬──────────┬──────────────────────────┐ │ 部分 │ 维度 │ 来源 │ ├─────────────────────┼──────────┼──────────────────────────┤ │ 地形向量 │ 200 │ 像素级地面颜色扫描 │ │ 最近障碍物距离 │ 1 │ RuleEngine.danger_list │ │ 最近敌人距离 │ 1 │ RuleEngine.enemy_list │ │ 最近 Soul 距离 │ 1 │ RuleEngine.soul_list │ │ 最近金币距离 │ 1 │ RuleEngine.coin_list │ │ 箭矢数 │ 1 │ OCR shared_arrows │ │ 金币数 (累计) │ 1 │ OCR shared_coins │ │ 生命值 │ 1 │ OCR shared_lives │ │ 总计 │ 207 │ │ └─────────────────────┴──────────┴──────────────────────────┘2.2 地形向量 (200 维)采样方法从主角马匹的右边界horse_pos[2]开始向右每隔 3 像素采样一次固定 200 个点。每个采样点读取画面底部往上 15 像素处的颜色与GROUND_COLORS对比判定是否为地面。forxinrange(start_x,min(start_x200*3,w),3):pixelframe[h-15,x]is_ground(pixel ≈ GROUND_COLORS[0]orpixel ≈ GROUND_COLORS[1])terrain_slice.append(1.0ifis_groundelse0.0)归一化直接为[0, 1]值无需额外缩放。为什么采样 200 个点1920×1080 分辨率下 200×3 600px 足够覆盖马匹前方到屏幕右边缘的全部地面200 维足够让 MLP 策略网络感知到前方空洞 悬崖的 pattern3px 间隔在精度和向量长度间折中2.3 状态向量 (7 维)距离归一化danger_dist/500# 超过 500px 视为无穷远enemy_dist/500soul_dist/500coin_dist/500选择 500 作为分母是因为游戏画面中超过 500px 的物体已接近屏幕边缘Agent 不需要区分 500px 和 800px —— 都很远。HUD 归一化arrows/100# 箭矢上限约 99coins/500# 金币可能累积数百lives/10# 生命值通常 1-5生命值不做上限截断——如果lives 10也能正确归一化到 1.0策略网络会从数值本身学到生命很多。三、动作空间设计3.1 动作定义Action名称ADB 操作游戏效果0待机无角色自然奔跑1跳跃adb_tap(300, 300)跳过障碍/悬崖/拾取头顶金币2射击adb_swipe(horse → enemy, offset300)消灭前方敌人3.2 为什么只有 3 个动作大招加速未加入— 需要消耗金币决策复杂度过高先不纳入 RL没有方向控制— 游戏是自动奔跑的卷轴跑酷角色自动前进没有向下滑— 游戏不提供下蹲/滑铲操作射击方向自动瞄准— swipe 的终点根据shoot_target最近敌人自动计算3.3 射击坐标计算# 从马的位置向敌人方向延伸 SHOOT_OFFSET300pxhx,hyhorse 中心坐标 ex,ey最近敌人的中心坐标 dx,dyex-hx,ey-hy ratioSHOOT_OFFSET/dist end_xhxdx*ratio end_yhydy*ratio# 远距离射击时加 Y 轴补偿箭矢抛物线ifdistDISTANCE_SWITCH(240px):end_ySHOOT_Y_OFFSET(-50px)# 向上抬模拟抛物线3.4 动作冷却不同动作的执行时间不同冷却时间也不同Action冷却原因0 待机0.0s无物理操作1 跳跃0.5stap 瞬间完成但需要等角色起跳→落地2 射击0.6sswipe 100ms 箭矢飞行 游戏动画冷却以非阻塞方式实现执行动作后记录cooldown_until now interval下一帧信号检查时若冷却已过 新帧感知完成 → 才action_done.set()。四、奖励函数设计4.1 设计原则密集信号优先— 距离增量每步都给Agent 每步都有反馈关键事件高权重— 悬崖跨越 20、死亡 -50明确强化/抑制防止 OCR 噪声— 所有 OCR 来源的 delta 都做上限裁剪引导合理行为— 战斗惩罚引导 Agent 不浪费箭矢、不放任敌人4.2 完整奖励项详见REWARD.md此处只列核心逻辑def_calculate_reward(action):reward0.0# P1 生存税 — 打破全正奖励reward-0.005# R1 距离增量 — 核心正向驱动delta_distcurrent_dist-prev_distif|delta_dist|50:# OCR 防抖rewarddelta_dist*0.1# R2 金币增量delta_coinscurrent_coins-prev_coinsif0delta_coins10:# OCR 防抖rewarddelta_coins*1.0# R3 障碍越过 — 跟踪 nearest danger 的 x 坐标变化ifnearest_danger_x 换了:reward2.0# R4 悬崖跨越 — True → Falseifcliff 解除:reward20.0# P2 无目标射击 — action2 但无敌或 300pxifaction2andnotenemy_close:reward-0.3# P3 有敌不射 — 敌人 300px 但 action≠2ifaction!2andenemy_close:reward-0.8# P4 死亡ifdone:reward-50.04.3 典型一局预算跑 2000m, 捡 20 币, 过 15 障碍, 跨 2 悬崖, 射 10 次 生存税: -0.005 × 400步 -2.0 距离: 0.1 × 2000 200.0 金币: 1.0 × 20 20.0 障碍: 2.0 × 15 30.0 悬崖: 20.0 × 2 40.0 射击奖惩: ≈ 0.0 ──────────────────────────────── 合计: ~288.0 死亡: -50.0 (17%)死亡惩罚约占一局总正奖励的 17%既能起到威慑作用又不会让 Agent 过度保守不敢冒险跳跃。4.4 奖励 shaping 的反面为什么不加按键惩罚有些 RL 项目对每次行动action≠0施加小惩罚防止 Agent 疯狂按键。本项目不需要冷却机制已经限制频率— Agent 每秒最多 2 次动作无效射击已有专门惩罚— 比通用按键惩罚更有针对性生存税已经打破全正奖励— 无需额外 per-action 惩罚五、死亡检测与重开闭环5.1 死亡判定双重机制主判定OCR 读到 lives0 → 立即标记 death_triggeredTrue └─ 优点零延迟不会有死后还在选动作的问题 兜底判定距离停滞 2s → 标记 death_triggeredTrue └─ 用途OCR 偶尔漏读 lives0 的情况5.2 端到端时序时间线: T0.0s lives0 → death_triggeredTrue → step() 返回 done T0.0s PPO 调用 reset() T1.0s 等待 OCR 稳定0.3s×3 周期 T1.0s 采样 dist_before T3.0s 采样 dist_after T3.0s dist_before dist_after → 确认死亡 T3.0s env_restart_order True → Worker 收到指令 T3.0s _death_check_enabled False ← 锁住死亡检测 T3.0s tap(300, 300) — 进入死亡页面 T6.0s tap(960, 1000) — 点击重开 T8.0s 等待游戏加载 T8.0s _death_check_enabled True ← 恢复死亡检测 T8.0s reset() 返回初始 obs — 新局开始5.3 关键保护机制保护机制防止的问题OCR 滞后误判等 1s 再采样确保 OCR 稳定lives 归零但距离读数还在更新 → 误判为还活着2s 距离验证两次采样对比OCR 短暂异常读数 → 误判死亡_death_check_enabled锁重开期间完全关闭死亡检测死画面上 lives0 持续触发 → 无限循环重启六、多线程交互时序6.1 正常 Step 的完整交互train_ppo game_env ppo_acting 主循环 OCR 线程 │ │ │ │ │ env.step(1) │ │ │ │ ──────────────► │ │ │ │ │ queue.put(1) │ │ │ │ ────────────────────► │ │ │ │ │ │ │ │ action_done.wait() │ │ │ │ (阻塞, 最多 2s) │ │ │ │ │ │ │ │ ┌─────┴─────┐ │ │ │ │ pop 动作 1 │ │ │ │ │ adb_tap │ │ │ │ │ 设 cooldown│ │ │ │ │ pending │ │ │ │ │ True │ │ │ │ └─────┬─────┘ │ │ │ │ │ │ │ ┌──────────┴──────────┐ │ │ │ │ 继续跑感知循环 │ │ │ │ │ 读帧 → 悬崖 → YOLO │ │ │ │ │ → 更新共享状态 │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ │ │ ┌─────┴─────┐ │ │ │ │ cooldown │ │ │ │ │ 已过 │ │ │ │ │ YES → │ │ │ │ │ action_ │ │ │ │ │ done.set()│ │ │ │ └─────┬─────┘ │ │ │ │ │ │ │ ◄─ action_done 收到 ──┘ │ │ │ │ │ │ │ 读共享状态: │ │ │ │ horse_pos, │ │ │ │ danger_list, │ │ │ │ shared_distance ... │ │ │ │ │ │ │ │ _calculate_reward() │ │ │ │ _get_obs() │ │ │ │ │ │ │ ◄─ (obs, r, done)│ │ │ │ │ │ │6.2 关键时序保证为什么action_done一定在感知更新后主循环的执行顺序保证了while True: 1. 读新帧 ← 上轮动作的效果已经体现在画面中 2. 感知 (YOLO rule_engine) ← 更新所有共享状态 3. 如果 pending_signal cooldown_done: action_done.set() ← game_env 醒来读到的一定是动作后的状态 4. 如果 queue 非空: 执行动作 ← 标记 pending_signalTrue 5. 渲染当 game_env 被action_done.set()唤醒时第 2 步已经完成共享状态是最新的。为什么选择maxsize1的 Queuemaxsize1意味着 game_env 的queue.put()会阻塞直到 Worker 消费了上一个动作这自然形成了背压机制Agent 不可能领先 Worker 超过 1 个动作比无限 queue 手动同步更简洁健壮七、PPO 训练配置7.1 超参数与设计意图参数值设计意图policyMlpPolicy207 维观测 → 3 维动作MLP 足够。不需要 CNNn_steps256每 256 步做一次策略更新。约等于 2-3 局游戏batch_size64256 步分 4 个 batch 更新平衡训练速度和稳定性n_epochs5每轮数据重放 5 次。RL 环境数据不能过拟合learning_rate3e-4PPO 标准值太大了策略震荡太小了收敛慢gamma0.99重视长期回报。游戏可以跑很久远期的生存也很重要gae_lambda0.95标准值平衡优势估计的偏差和方差clip_range0.2PPO 核心限制每次更新的幅度防止策略崩溃ent_coef0.05略高鼓励探索。游戏有随机性敌人/障碍随机出现vf_coef0.5标准值价值函数损失权重7.2 模型保存策略类型触发用途ppo_greatwall_N_steps.zip每 10000 步定期 checkpoint断点续训ppo_greatwall_best.zipepisode reward 创新高训练中最佳模型ppo_greatwall_final.zip训练完成最终模型ppo_greatwall_interrupt.zipCtrlC中断保护BestModelCallback是自定义的——监听Monitor包装器在每个 episode 结束时写入infos的episode字段比较ep_reward。八、设计决策 FAQQ: 为什么 RuleEngine 仍然在 ppo_acting 中运行RL 不是不应该用规则吗RuleEngine 的输出danger_list,enemy_list等是作为观测的一部分输入给 RL 的不是用来做决策的。它本质上是一个特征提取器——把 YOLO 的边界框列表转化为最近的障碍物距离 230px这种结构化信息。PPO 策略自己决定跳不跳。Q: 为什么 terrain 向量用像素采样而不是 YOLO 检测悬崖是地面缺失不是可检测的物体。YOLO 不知道地面长什么样。像素扫描是唯一可靠的悬崖检测方式。Q: 为什么 OCR 读 HUD 而不是让 YOLO 检测数字OCR 识别任意数字比训练 YOLO 检测每个数字0-9更灵活通用。Tesseract 专门优化了数字识别。Q: reset() 中的 2s 等待会不会浪费时间只在死亡时发生。一局游戏可能几百上千步一次 3 秒的 reset 开销可忽略。更重要的是它确保了重开可靠性。占位图​​我们挑几张验证效果看看​

相关新闻

TMS320F28335 XINTF与ADC时序配置实战:从手册参数到稳定系统

TMS320F28335 XINTF与ADC时序配置实战:从手册参数到稳定系统

1. 项目概述:从芯片手册到稳定系统的桥梁如果你正在使用TI的TMS320F28335这颗经典的浮点DSP进行开发,尤其是在做电机控制、数字电源或者需要高速数据采集的项目,那么有两个模块的时序问题你绝对绕不开:外部接口(XINTF&…

2026/7/27 8:00:44 阅读更多 →
AI动态调整销售目标的技术实现与实战经验

AI动态调整销售目标的技术实现与实战经验

1. 为什么销售目标需要动态调整?在传统销售管理中,目标设定往往采用"去年业绩固定增长率"的简单模式。我在某快消品企业任职时就深有体会:年初制定的季度目标,到第三个月市场突然出现新竞争者,原有目标立刻变…

2026/7/27 7:59:43 阅读更多 →
TMS320C3x DSP软件UART实现:中断驱动全双工串口通信详解

TMS320C3x DSP软件UART实现:中断驱动全双工串口通信详解

1. 项目概述与核心价值在嵌入式开发领域,串行通信是连接设备与外部世界的“血管”。通用异步收发传输器(UART)作为最经典、最普遍的串行通信接口,其重要性不言而喻。然而,并非所有微控制器或数字信号处理器&#xff08…

2026/7/27 7:59:43 阅读更多 →

最新新闻

TVA数字小脑:具身智能的物理交互革命(14)

TVA数字小脑:具身智能的物理交互革命(14)

前沿技术探索:AI智能体视觉(TVA,Transformer-based Vision Agent)是依托Transformer架构与“因式智能体”理论所构建的颠覆性工业视觉技术,是集深度强化学习(DRL)、卷积神经网络(CNN…

2026/7/27 8:09:48 阅读更多 →
AI技术在英语培训中的革命性应用与实战解析

AI技术在英语培训中的革命性应用与实战解析

1. AI技术在英语培训中的革命性应用 作为一名在英语教育行业深耕十年的技术顾问,我亲眼见证了AI如何从简单的单词查询工具进化为全方位的语言学习伙伴。如今的AI英语培训系统已经具备了情感识别、行业知识图谱构建和实时互动等能力,彻底改变了传统教学模…

2026/7/27 8:09:48 阅读更多 →
告别图层重建:Ai2Psd让你的AI设计无缝迁移到Photoshop [特殊字符]

告别图层重建:Ai2Psd让你的AI设计无缝迁移到Photoshop [特殊字符]

告别图层重建:Ai2Psd让你的AI设计无缝迁移到Photoshop 🚀 【免费下载链接】ai-to-psd A script for prepare export of vector objects from Adobe Illustrator to Photoshop 项目地址: https://gitcode.com/gh_mirrors/ai/ai-to-psd 你是否曾因I…

2026/7/27 8:09:48 阅读更多 →
sqlmap高级参数实战:从基础注入到高效渗透测试技巧

sqlmap高级参数实战:从基础注入到高效渗透测试技巧

1. 项目概述:从“能用”到“精通”的必经之路 如果你玩过sqli-labs靶场,或者做过一些基础的Web渗透测试,那么你对 sqlmap 这个工具一定不陌生。新手阶段,我们最熟悉的可能就是 -u 指定目标,然后 --dbs 爆数据库名…

2026/7/27 8:09:48 阅读更多 →
TPS26750A USB PD控制器固件更新与4CC任务系统实战解析

TPS26750A USB PD控制器固件更新与4CC任务系统实战解析

1. 项目概述与核心价值在嵌入式电源管理领域,尤其是USB PD(电力传输)控制器这类芯片的开发与维护中,固件更新能力是衡量产品生命力和可靠性的关键指标。想象一下,你设计的一款高端笔记本或快充充电宝,上市后…

2026/7/27 8:09:48 阅读更多 →
ASP.NET Core依赖注入:服务生命周期详解与实践

ASP.NET Core依赖注入:服务生命周期详解与实践

1. 服务生命周期基础概念在ASP.NET Core的依赖注入系统中,服务生命周期是框架最核心的设计理念之一。作为开发者,我们每天打交道的就是这三种看似简单却容易混淆的生命周期模式:Transient(瞬时)、Scoped(作…

2026/7/27 8:08:48 阅读更多 →

日新闻

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:54 阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻