在实际游戏开发或AI研究项目中我们常常需要让智能体学会应对复杂、动态的环境例如在“僵尸生存”这类游戏场景中智能体需要学会移动、攻击、躲避以生存下来。传统的脚本式AI在面对多变情况时往往显得僵硬而强化学习提供了一种让智能体通过与环境交互、从试错中学习最优策略的框架。本文将围绕“AI角斗士学习对抗僵尸”这一主题深入探讨如何使用PPO近端策略优化算法结合神经网络在C环境中构建一个能够自主学习的智能体。我们将从零开始涵盖从理论理解、环境搭建、模型实现到训练调试的全过程目标是产出一个可运行、可复现的学习案例。本文适合有一定C和机器学习基础的开发者特别是对游戏AI、强化学习落地实践感兴趣的读者。通过本文你将理解PPO算法的核心机制掌握将强化学习理论转化为C代码的工程实践并能够构建一个在简单仿真环境中对抗“僵尸”的智能体模型。1. 理解强化学习与PPO算法从僵尸角斗场说起在开始写代码之前必须厘清几个核心概念。强化学习问题通常被建模为马尔可夫决策过程MDP智能体Agent通过执行动作Action与环境Environment交互环境返回新的状态State和奖励Reward。智能体的目标是学习一个策略Policy使得长期累积奖励最大化。在我们的“角斗士 vs 僵尸”场景中状态State可以包括角斗士的坐标、血量、弹药量以及周围僵尸的位置、数量、移动方向等。状态是智能体感知世界的输入。动作Action角斗士在每个时刻可以做出的决策例如“向前移动”、“向左转”、“攻击”、“装弹”等离散动作或者是移动速度、转向角度等连续值。奖励Reward环境根据智能体的行为给出的即时反馈。例如击中一个僵尸获得10分被僵尸攻击损失-5点生命值对应-5奖励生存每多一秒获得0.1分。奖励函数的设计是强化学习成功的关键它如同“指挥棒”引导智能体学习我们期望的行为。智能体Agent即我们正在训练的角斗士AI它内部包含一个策略网络负责根据当前状态输出动作。PPOProximal Policy Optimization算法是OpenAI在2017年提出的一种策略梯度算法因其在效果、实现复杂度和样本效率之间的良好平衡而广受欢迎。它核心解决了传统策略梯度算法中步长难以确定、训练不稳定容易“崩掉”的问题。PPO的核心思想是在更新策略时限制新旧策略之间的差异不能太大确保每次更新都是“小幅、稳健”的优化。它主要通过两种技术实现Clipped Surrogate Objective裁剪替代目标函数通过一个裁剪系数ε如0.2限制策略更新的幅度防止单次更新步子迈得太大导致性能急剧下降。用价值函数估计优势函数优势函数A(s, a)衡量在状态s下采取动作a比平均情况好多少。PPO使用一个独立的价值网络Critic来估计状态价值并用于计算优势函数从而减少策略梯度估计的方差。简单来说PPO让我们的角斗士AI在学习时既积极尝试新动作以获得更高奖励又不会因为某次“冒险”的失败而彻底忘记之前学到的所有经验学习过程更加平滑稳定。2. 环境准备与项目结构搭建在动手实现算法之前我们需要一个能让智能体交互的“角斗场”环境并搭建好项目的基本骨架。由于我们聚焦于算法本身环境可以是一个高度简化的2D网格世界模拟。2.1 开发环境与工具链我们选择C作为实现语言因其在性能敏感的游戏AI和仿真中广泛应用。你需要准备以下环境编译器支持C17标准的编译器如GCC (7.3)、Clang (6.0) 或 MSVC (Visual Studio 2019)。本文示例将在Linux/macOS的GCC环境下演示。构建工具推荐使用CMake3.10来管理项目构建它能很好地处理依赖和跨平台编译。第三方库Eigen3一个高性能的C模板库用于线性代数、矩阵和向量运算是神经网络实现中张量操作的基础。我们将用它来实现全连接层。可选JSON库如nlohmann/json用于方便地保存和加载训练配置超参数。集成开发环境IDEVisual Studio Code (VSCode) 配合C/C扩展是跨平台开发的优秀选择。确保VSCode配置好C/C环境包括正确的编译器路径和IntelliSense。环境检查清单终端输入g --version或clang --version确认编译器版本。终端输入cmake --version确认CMake已安装。确认Eigen3库已安装。在Ubuntu上可通过apt-get install libeigen3-dev安装。在其他系统上也可以直接下载头文件库放入项目目录。2.2 项目目录结构设计一个清晰的项目结构有助于管理代码、配置和训练产物。建议按如下方式组织ai_gladiator/ ├── CMakeLists.txt # 项目根CMake配置文件 ├── config/ │ └── ppo_config.json # PPO超参数配置文件可选 ├── include/ # 公共头文件 │ ├── environment.h # 环境接口与僵尸环境实现 │ ├── neural_net.h # 神经网络基类与层定义 │ ├── ppo_agent.h # PPO智能体核心类 │ └── replay_buffer.h # 经验回放缓冲区 ├── src/ # 源文件 │ ├── environment.cpp │ ├── neural_net.cpp │ ├── ppo_agent.cpp │ ├── replay_buffer.cpp │ └── main.cpp # 训练与测试主程序 ├── scripts/ # 辅助脚本 │ └── plot_training.py # Python脚本用于绘制训练曲线 └── build/ # 编译输出目录由CMake生成这个结构将核心模块环境、网络、智能体、缓冲区分离main.cpp作为程序的入口协调整个训练流程。2.3 基础依赖配置CMake与Eigen3根目录下的CMakeLists.txt是项目的构建蓝图。一个最小化的配置如下cmake_minimum_required(VERSION 3.10) project(AIGladiator VERSION 1.0.0 LANGUAGES CXX) # 设置C标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 添加可执行文件目标 add_executable(ai_gladiator_train src/main.cpp src/environment.cpp src/neural_net.cpp src/ppo_agent.cpp src/replay_buffer.cpp ) # 包含头文件目录 target_include_directories(ai_gladiator_train PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) # 查找并链接Eigen3。Eigen是纯头文件库只需包含路径。 find_package(Eigen3 3.3 REQUIRED NO_MODULE) target_include_directories(ai_gladiator_train PRIVATE ${EIGEN3_INCLUDE_DIRS}) # 如果使用json库同样需要find_package和target_link_libraries # find_package(nlohmann_json 3.2.0 REQUIRED) # target_link_libraries(ai_gladiator_train PRIVATE nlohmann_json::nlohmann_json)在include/neural_net.h中我们可以开始引入Eigen库并定义一些基础类型#ifndef NEURAL_NET_H #define NEURAL_NET_H #include Eigen/Dense #include vector #include memory #include functional // 使用Eigen的MatrixXd作为通用的全连接层矩阵这里我们使用float类型以节省内存和计算 using Matrix Eigen::MatrixXf; using Vector Eigen::VectorXf; // 激活函数类型定义 using ActivationFunc std::functionVector(const Vector); using ActivationGradFunc std::functionVector(const Vector); // ... 后续的神经网络层和网络类定义将放在这里 #endif // NEURAL_NET_H3. 构建核心模块环境、网络与智能体有了项目骨架我们开始实现三个最核心的模块仿真环境、神经网络和PPO智能体。3.1 实现简化僵尸环境环境是智能体的“老师”它定义了状态、动作空间和奖励规则。我们实现一个简单的网格世界环境。在include/environment.h中定义接口和实现#ifndef ENVIRONMENT_H #define ENVIRONMENT_H #include vector class Environment { public: struct State { // 简化状态角斗士位置(x,y)血量最近僵尸的相对位置(dx, dy) float gladiator_x, gladiator_y; float health; float nearest_zombie_dx, nearest_zombie_dy; // 可以将状态扁平化为一个向量便于输入神经网络 std::vectorfloat to_vector() const; }; struct Action { // 简化动作移动方向0:上1:右2:下3:左是否攻击0/1 int move_dir; // 离散值 0-3 int attack; // 0 或 1 }; virtual ~Environment() default; // 重置环境到初始状态 virtual State reset() 0; // 执行动作返回新的状态、奖励和是否结束 virtual std::tupleState, float, bool step(const Action action) 0; }; // 具体的僵尸环境实现 class ZombieGridEnvironment : public Environment { public: ZombieGridEnvironment(int grid_size 10); State reset() override; std::tupleState, float, bool step(const Action action) override; private: int grid_size_; State current_state_; // 僵尸位置列表 std::vectorstd::pairfloat, float zombies_; // 内部更新僵尸逻辑 void update_zombies(); // 计算奖励 float calculate_reward(const State old_state, const Action action, const State new_state); }; #endif // ENVIRONMENT_H在src/environment.cpp中实现具体逻辑。reset()函数随机初始化角斗士和僵尸的位置。step()函数根据动作更新角斗士位置移动或攻击然后更新僵尸例如向角斗士移动最后计算奖励和判断是否终止如角斗士血量0或存活足够长时间。奖励函数calculate_reward是环境设计的灵魂。一个简单的设计可以是攻击且命中僵尸10被僵尸攻击僵尸相邻-5每存活一步0.1角斗士血量归零-50 并终止回合这个函数需要你仔细调整以引导智能体学习到“主动攻击僵尸”和“保持距离避免受伤”的平衡策略。3.2 实现前馈神经网络PPO需要两个神经网络策略网络Actor和价值网络Critic。它们可以是结构相似的前馈神经网络。首先在include/neural_net.h中定义层和网络// ... 之前的using和定义 // 全连接层 class DenseLayer { public: DenseLayer(int input_size, int output_size, ActivationFunc activation, ActivationGradFunc activation_grad); // 前向传播 Vector forward(const Vector input); // 反向传播返回对输入的梯度 Vector backward(const Vector grad_output, float learning_rate); const Matrix get_weights() const { return weights_; } const Vector get_bias() const { return bias_; } void set_weights(const Matrix w) { weights_ w; } void set_bias(const Vector b) { bias_ b; } private: Matrix weights_; Vector bias_; ActivationFunc activate_; ActivationGradFunc activate_grad_; Vector last_input_; // 保存前向传播的输入用于反向传播 Vector last_pre_activation_; // 激活前的值 }; // 神经网络模型 class NeuralNetwork { public: NeuralNetwork() default; void add_layer(std::unique_ptrDenseLayer layer); // 前向传播 Vector predict(const Vector input); // 训练一步对于PPO我们通常使用批量数据这里简化 // 实际的PPO更新会更复杂涉及策略梯度和价值损失 void update(const std::vectorVector inputs, const std::vectorVector targets, float lr); // 保存/加载模型参数 bool save_weights(const std::string filepath); bool load_weights(const std::string filepath); private: std::vectorstd::unique_ptrDenseLayer layers_; };在src/neural_net.cpp中实现层的前向和反向传播。激活函数可以选择ReLU用于隐藏层和Tanh用于输出有界值。对于策略网络输出离散动作的概率分布最后一层通常用Softmax对于输出连续动作的参数如均值最后一层用Tanh或线性层。价值网络输出一个标量最后一层通常是线性层。3.3 实现PPO智能体这是最复杂的部分。PPO智能体需要管理策略网络和价值网络收集经验并按照PPO的算法步骤进行更新。在include/ppo_agent.h中定义#ifndef PPO_AGENT_H #define PPO_AGENT_H #include neural_net.h #include environment.h #include replay_buffer.h #include memory #include vector class PPOAgent { public: struct Config { // 网络结构 std::vectorint actor_hidden_dims{64, 64}; std::vectorint critic_hidden_dims{64, 64}; // PPO超参数 float learning_rate 3e-4f; float clip_epsilon 0.2f; float gamma 0.99f; // 折扣因子 float lambda 0.95f; // GAE系数 int batch_size 64; int epochs_per_update 10; // 每次更新迭代的轮数 float entropy_coef 0.01f; // 熵奖励系数鼓励探索 }; PPOAgent(int state_dim, int action_dim, bool discrete_action, const Config config Config()); // 根据状态选择动作带探索 Environment::Action act(const Environment::State state, bool deterministic false); // 将一条经验状态动作奖励下一状态是否终止存入缓冲区 void store_transition(const Environment::State state, const Environment::Action action, float reward, const Environment::State next_state, bool done); // 当缓冲区数据足够时执行PPO更新 void learn(int total_steps); // 保存/加载模型 void save(const std::string path); void load(const std::string path); private: // 计算广义优势估计GAE std::vectorfloat compute_advantages(const std::vectorfloat rewards, const std::vectorfloat values, const std::vectorbool dones); // 更新策略网络和价值网络 void update_networks(const std::vectorVector states_batch, const std::vectorVector actions_batch, const std::vectorfloat old_log_probs_batch, const std::vectorfloat advantages_batch, const std::vectorfloat returns_batch); Config config_; int state_dim_; int action_dim_; bool discrete_action_; std::unique_ptrNeuralNetwork actor_net_; // 策略网络 std::unique_ptrNeuralNetwork critic_net_; // 价值网络 std::unique_ptrReplayBuffer buffer_; // 随机数生成器用于动作采样 std::mt19937 gen_; }; #endif // PPO_AGENT_H在src/ppo_agent.cpp中核心是learn()和update_networks()函数。learn()函数的主要步骤是从缓冲区获取本轮收集的所有轨迹数据状态、动作、奖励、下一状态、终止标志。用当前的价值网络计算每个状态的价值估计。使用GAE公式计算每个状态-动作对的优势函数估计值A_t。计算每个状态-动作对的回报Return R_t A_t V(s_t)。将数据随机打乱分成多个小批次mini-batch。对每个小批次调用update_networks进行多次epochs_per_update更新。策略网络更新计算新旧策略的概率比ratio exp(new_log_prob - old_log_prob)。PPO的裁剪目标函数为L_clip E[min(ratio * A, clip(ratio, 1-ε, 1ε) * A)]。同时可以加上熵奖励β * entropy以鼓励探索。价值网络更新最小化价值网络预测值V_pred与目标回报returns之间的均方误差损失L_value (returns - V_pred)^2。4. 训练流程整合与结果验证将所有模块串联起来形成完整的训练循环。4.1 编写训练主程序在src/main.cpp中我们实现一个标准的强化学习训练循环#include environment.h #include ppo_agent.h #include iostream #include chrono #include fstream int main() { // 1. 创建环境 ZombieGridEnvironment env(10); // 2. 创建智能体 // 假设状态维度角斗士x,y,血量,僵尸dx,dy - 5维 // 动作维度离散移动(4) 离散攻击(2) - 共8个离散动作可合并为1个8维动作空间 // 为简化我们假设动作是离散的共8种组合。 int state_dim 5; int action_dim 8; // 8个离散动作 PPOAgent::Config config; config.learning_rate 3e-4f; config.gamma 0.99f; config.batch_size 32; PPOAgent agent(state_dim, action_dim, true, config); // true 表示离散动作 // 3. 训练参数 int total_episodes 5000; int max_steps_per_episode 200; std::vectorfloat episode_rewards; std::cout 开始训练AI角斗士... std::endl; auto start_time std::chrono::steady_clock::now(); for (int ep 0; ep total_episodes; ep) { auto state env.reset(); float episode_reward 0.0f; bool done false; int steps 0; std::vectorEnvironment::State states; std::vectorEnvironment::Action actions; std::vectorfloat rewards; std::vectorEnvironment::State next_states; std::vectorbool dones; // 收集一个episode的经验 while (!done steps max_steps_per_episode) { auto action agent.act(state, false); // 训练时使用随机探索 auto [next_state, reward, terminal] env.step(action); // 存储经验到智能体的内部缓冲区如果agent内部有buffer // 这里简化直接收集到本地列表一个episode后统一学习 states.push_back(state); actions.push_back(action); rewards.push_back(reward); next_states.push_back(next_state); dones.push_back(terminal); state next_state; episode_reward reward; done terminal; steps; } // 将一个episode的经验存入缓冲区并触发学习 // 注意PPO通常使用多个episode或固定步数的经验一起更新这里简化处理 for (size_t i 0; i states.size(); i) { // 这里需要将状态转换为Vector动作转换为int索引等再存入agent的buffer // 假设agent有一个接受原始类型的store_transition方法 agent.store_transition(states[i], actions[i], rewards[i], next_states[i], dones[i]); } // 当收集了足够多的经验例如一个episode后进行学习 agent.learn(steps); episode_rewards.push_back(episode_reward); // 定期输出训练进度 if ((ep 1) % 100 0) { auto avg_reward std::accumulate(episode_rewards.end() - 100, episode_rewards.end(), 0.0f) / 100.0f; auto elapsed std::chrono::duration_caststd::chrono::seconds(std::chrono::steady_clock::now() - start_time).count(); std::cout Episode ep1 , Avg Reward (last 100): avg_reward , Time: elapsed s std::endl; // 可以在这里保存模型 if ((ep 1) % 1000 0) { agent.save(ppo_model_ep std::to_string(ep1) .bin); } } } std::cout 训练完成 std::endl; // 保存最终模型 agent.save(ppo_model_final.bin); // 4. 测试训练好的模型 std::cout \n 开始测试 std::endl; agent.load(ppo_model_final.bin); // 重新加载模型或直接使用内存中的模型 auto test_state env.reset(); float test_reward 0.0f; bool test_done false; int test_steps 0; while (!test_done test_steps max_steps_per_episode) { // 测试时使用确定性策略取概率最大的动作 auto test_action agent.act(test_state, true); auto [next_test_state, r, d] env.step(test_action); // 可以在这里可视化或打印每一步的状态和动作 // std::cout Step test_steps : Pos( test_state.gladiator_x , ... test_state next_test_state; test_reward r; test_done d; test_steps; } std::cout 测试回合总奖励: test_reward , 生存步数: test_steps std::endl; return 0; }4.2 编译与运行在项目根目录下执行以下命令进行编译和运行# 创建并进入构建目录 mkdir build cd build # 生成构建系统例如Makefile cmake .. # 编译项目 make -j4 # 运行训练程序 ./ai_gladiator_train如果一切顺利你将在终端看到训练进度输出显示每100个回合的平均奖励。理想情况下随着训练进行平均奖励会呈现上升趋势表明智能体正在学习如何获得更高分数即更好地对抗僵尸。4.3 验证训练效果验证强化学习智能体是否学会不能只看最终奖励还需要观察其行为。你可以打印日志在测试阶段打印出每一步的状态如角斗士位置、最近僵尸位置和选择的动作。观察智能体是否表现出合理的策略例如主动接近僵尸攻击或在血量低时逃跑。绘制学习曲线在训练过程中记录每个回合的奖励然后用Python的matplotlib库绘制曲线。一个成功的学习曲线应该总体呈上升趋势并可能伴随波动探索导致。# scripts/plot_training.py 示例 import matplotlib.pyplot as plt rewards [...] # 从训练日志中读取的奖励列表 plt.plot(rewards) plt.xlabel(Episode) plt.ylabel(Total Reward) plt.title(PPO Training Curve for Zombie Gladiator) plt.show()修改环境难度增加僵尸数量或移动速度观察已训练的智能体是否仍能应对或是否需要继续训练迁移学习。5. 常见问题排查与调试技巧在实现和训练过程中你几乎一定会遇到各种问题。以下是几个典型问题及其排查路径问题现象可能原因检查与解决思路训练奖励不上升始终为负或很低1. 奖励函数设计不合理。2. 超参数如学习率设置不当。3. 网络结构太简单或太复杂。4. 探索不足智能体陷入局部最优。1.检查奖励函数确保奖励信号足够清晰。可以尝试大幅提高成功动作如击中僵尸的奖励或大幅降低惩罚。先让智能体学会做“对一件事”。2.调整超参数尝试降低学习率如从3e-4降到1e-4。增加折扣因子gamma如0.99-0.999使其更关注长期回报。调整PPO的clip_epsilon如0.1到0.3。3.简化问题先在一个极其简单的环境如只有一个静止僵尸中测试看智能体能否学会基础动作。4.增加探索提高熵奖励系数entropy_coef或在使用确定性策略测试前确保训练时有足够的随机采样。程序编译通过但运行时崩溃如段错误1. 数组/向量越界。2. 空指针访问。3. Eigen库矩阵维度不匹配。1.使用调试器用gdb运行程序gdb ./ai_gladiator_train在崩溃处查看堆栈信息bt命令。2.添加边界检查在所有从环境获取状态、网络输入输出处添加断言或日志检查向量维度是否符合预期。3.检查Eigen操作确保矩阵乘法(m*n) * (n*p)维度匹配Vector和Matrix初始化时大小正确。训练初期奖励波动极大甚至出现NaN1. 梯度爆炸。2. 计算log(0)或除以0。3. 网络输出值域不合适。1.梯度裁剪在反向传播中对梯度进行裁剪限制其最大范数。2.数值稳定性在计算对数概率时对概率值加上一个极小值如1e-8防止log(0)。检查价值网络输出是否过大。3.归一化输入/输出考虑对状态输入进行归一化如缩放到[-1,1]。对于概率输出确保Softmax计算稳定。智能体行为单一只重复一个动作1. 探索过早衰减。2. 奖励函数导致某个动作收益明显过高。3. 策略网络过早收敛到一个峰值。1.检查探索策略确保在训练时动作采样是基于网络输出的概率分布而不是直接取argmax。验证随机数生成器是否正常工作。2.修改奖励如果“不动”也能获得微小正奖励智能体可能选择不动。增加“不作为”的微小负奖励如-0.01。3.增加熵奖励提高PPO中熵奖励的系数鼓励策略保持一定的随机性。训练速度非常慢1. 环境模拟或渲染开销大。2. 神经网络前向/反向传播实现效率低。3. 使用Debug模式编译。1.性能分析使用工具如gprof找出瓶颈。简化环境模拟逻辑或减少状态维度。2.优化矩阵运算确保Eigen库使用了正确的编译优化标志如-O3-marchnative。3.使用Release编译使用CMake的Release配置编译cmake -DCMAKE_BUILD_TYPERelease ..。6. 工程优化与扩展方向当你的基础PPO智能体能在简单僵尸环境中学习后可以考虑以下优化和扩展使其更强大、更实用6.1 工程实践优化配置化管理将所有超参数学习率、网络结构、环境参数等移至外部JSON或YAML配置文件。这样无需重新编译即可进行大量实验。完整的经验回放缓冲区实现一个环形缓冲区支持随机采样并正确处理GAE和优势估计的计算。模型保存与加载实现模型的定期保存每隔N步或N回合并支持从断点继续训练。这对于长时间训练至关重要。日志与可视化使用spdlog等日志库替代std::cout输出不同级别的日志。将训练指标奖励、步数、价值损失、策略损失实时写入TensorBoard或简单的CSV文件便于监控。单元测试为环境逻辑、网络层的前向/反向传播、GAE计算等核心功能编写单元测试确保代码正确性。6.2 算法与模型扩展使用卷积神经网络CNN如果状态是图像如游戏屏幕截图可以将全连接网络替换为CNN在neural_net.h中增加卷积层和池化层的实现。处理连续动作空间当前示例是离散动作。对于连续动作如移动速度策略网络需要输出动作分布的参数如高斯分布的均值和标准差然后从中采样。集成更复杂的仿真平台将我们的PPO智能体与专业的机器人强化学习仿真平台如MuJoCo、PyBullet的C接口或自定义的3D游戏引擎连接。这需要将环境接口适配到这些平台提供的API。多智能体对抗训练多个角斗士智能体协同作战或让一个智能体对抗另一个由不同策略甚至也是学习型控制的“僵尸”智能体这属于多智能体强化学习范畴。结合模仿学习如果有一些人类专家演示数据可以先用模仿学习初始化策略网络再进行强化学习微调以加速训练。6.3 部署考量若要将训练好的AI部署到实际游戏或应用中模型轻量化训练好的网络可能较大。可以考虑模型剪枝、量化或知识蒸馏在保证性能的同时减少模型大小和推理延迟。C推理接口确保你的NeuralNetwork类有一个高效、无依赖的predict函数可以轻松集成到游戏主循环中。与游戏引擎集成将模型推理封装成DLL或静态库供Unity通过C# P/Invoke、Unreal Engine直接调用C等游戏引擎调用。状态需要从游戏引擎中提取动作需要反馈给引擎控制游戏实体。从零实现一个基于PPO的AI角斗士是一个将强化学习理论转化为具体工程实践的绝佳练习。这个过程会迫使你深入理解状态、动作、奖励、策略梯度、价值函数等概念的具体代码形态。最关键的一步往往是设计一个合理的奖励函数它如同教孩子学走路时的鼓励和纠正直接决定了智能体学习的方向。当你在终端看到平均奖励曲线开始稳步攀升时那种“智能体正在学会如何生存”的成就感正是驱动我们不断探索AI前沿的动力。下一步你可以尝试更换更复杂的环境调整网络结构甚至实现PPO的变种算法如PPO-Continuous来挑战更强大的“僵尸军团”。