Unity ML-Agents强化学习实战:训练自适应AI Boss的完整指南

📅 2026/7/12 22:17:52
Unity ML-Agents强化学习实战:训练自适应AI Boss的完整指南
1. 项目概述为什么要在Unity里训练一个会学习的Boss如果你是一个游戏开发者尤其是独立开发者或者小团队可能都面临过类似的困境设计一个有趣、有挑战性但又不会让玩家感到沮丧的Boss战需要投入大量的时间和精力去手动调整行为树、状态机或者一堆if-else逻辑。更头疼的是一旦玩家找到了固定的“套路”这个Boss就变得索然无味。传统的脚本化AI其行为边界是开发者预设的缺乏真正的“灵性”和“成长性”。这就是为什么我们要把目光投向Unity ML-Agents Toolkit。它不是一个简单的插件而是一个桥梁将Unity这个强大的实时3D创作平台与主流的机器学习框架如PyTorch连接起来。简单来说它允许你直接在Unity编辑器里通过“试错”的方式让一个虚拟角色比如我们的Boss自己学会如何战斗。这个学习过程就是强化学习。想象一下你不再需要告诉Boss“当玩家距离小于5米时有30%概率使用冲锋技能”。你只需要定义好Boss能做什么动作移动、攻击、防御、释放技能以及什么是“好”什么是“坏”比如击中玩家得正分被玩家击中得负分然后把它扔进一个模拟环境里让它和成千上万个“玩家”克隆体对战。通过数百万次的尝试Boss会自己摸索出一套最优的战斗策略这套策略可能比你手动设计的更狡猾、更难以预测甚至能发展出一些你意想不到的“骚操作”。这次我们要做的就是打造这样一个Boss。它不是一个只会按脚本走位的木桩而是一个能观察战场形势玩家位置、自身血量、技能冷却、能决策是逼近还是周旋用普攻还是蓄力大招、并能从失败中学习的对手。我会附上完整的训练代码和Unity工程关键部分让你能亲手复现并训练属于你自己的“AI Boss”。2. 核心设计拆解一个可学习的Boss需要哪些模块在动手写代码之前我们必须把整个系统拆解清楚。一个基于ML-Agents的AI智能体其核心是一个“感知-决策-执行”的循环。对于我们的Boss战场景需要精心设计每一个环节。2.1 观察空间设计Boss的“眼睛”和“感觉”观察空间是AI了解世界状态的窗口。我们不能把整个游戏世界的原始数据比如所有顶点的位置都扔给AI那会带来“维度灾难”导致学习无法进行。我们需要提取高层次、有意义的特征。对于Boss战我设计了以下几个维度的观察值相对位置与朝向Boss自身到玩家的归一化向量X, Y, Z。归一化到[-1, 1]区间避免绝对距离值过大影响训练。Boss面向方向与指向玩家方向的点积。这个值能告诉AI“我是否正面对着玩家”对于判断是否需要转身很有用。玩家相对于Boss的局部空间坐标前、右、上方向。这比世界坐标更直观。自身状态当前生命值百分比0到1。各个技能的冷却状态0表示可用1表示冷却中。可以设计多个技能如普攻、重击、冲锋、范围AOE。当前是否处于某个特殊状态如蓄力、防御的布尔值编码。环境与历史信息可选但推荐最近几次攻击是否命中的历史记录一个固定长度的队列如最近5次用0/1表示。这能帮助AI学习攻击节奏和预判。与玩家的最近距离变化率是正在接近还是远离。将这些观察值拼接成一个一维浮点数数组就构成了Boss的观察向量。设计原则是尽可能提供与决策强相关的信息且保持低维度。一开始可以简单些后续再根据AI的表现增加。注意ML-Agents支持三种观察类型Vector向量、Raycast射线感知和Camera视觉。对于Boss战Vector观察通常就够了因为它速度快且稳定。Raycast可以用来感知墙壁或障碍物如果你的战场有复杂地形可以考虑加入。2.2 动作空间设计Boss的“手脚”动作空间定义了AI能做什么。ML-Agents支持离散动作、连续动作以及两者混合。对于Boss我采用混合动作空间离散动作用于触发技能。例如分支0无技能、普攻、重击、冲锋、范围AOE。分支1开启/关闭防御状态。可以设计多个分支对应不同类型的技能组连续动作用于控制移动。例如Vector2控制平面移动前/后左/右。float控制转向速度-1到1表示向左转或向右转的强度。这样设计既能让AI灵活地走位又能精确地触发关键技能。切忌给AI太多无效或矛盾的动作选项比如同时允许向前和向后移动这会让AI困惑。2.3 奖励函数设计引导Boss学习的“胡萝卜与大棒”奖励函数是强化学习的灵魂直接决定了AI会学成什么样。我们的目标是让Boss学会“有效且有趣地战斗”而不是简单地“秒杀玩家”或“苟活”。核心正向奖励胡萝卜对玩家造成伤害每次成功击中玩家给予一个正奖励如0.3。可以根据伤害值进行缩放。成功格挡或闪避如果设计了防御机制成功防御玩家攻击时给予奖励如0.1。保持威胁距离当Boss与玩家保持在一个理想的战斗距离比如3-8米时每步给予一个微小的持续奖励如0.001。这能鼓励Boss不要离得太远或贴得太近。技能连招奖励如果AI连续使用了两个有配合效果的技能如冲锋击晕后接重击给予额外奖励。核心负向奖励大棒受到玩家伤害每次被玩家击中给予负奖励如-0.2。技能打空释放技能但未命中玩家给予小额负奖励如-0.05避免AI胡乱放技能。长时间无交互如果超过一定时间如200步既未击中玩家也未被打中给予一个较大的负奖励如-0.5鼓励积极交战。生命值过低惩罚当生命值低于一定阈值时每一步都给予小额负奖励模拟“危机感”。回合结束奖励终局奖惩击败玩家5.0 巨大成功被玩家击败-3.0 失败一个关键技巧奖励塑造。一开始训练时可以把奖励设置得“稠密”一些比如每步都有小奖励帮助AI快速建立初步概念。等AI学会基础操作后再逐渐将奖励“稀疏化”更多地依赖回合结束的胜负奖励让AI学习更长远的策略。2.4 训练环境配置创造一个高效的“训练场”单个Boss和玩家对练效率太低。我们需要利用ML-Agents的并行训练功能。场景复制在同一个Unity场景中创建多个比如20-50个完全相同的训练区域副本。每个区域都有一个Boss和一个玩家。玩家控制训练初期玩家可以由另一个简单的脚本AI控制比如随机移动和攻击。这比用真人或复杂AI高效得多。后期为了提升Boss的对抗性可以换用更高级的脚本AI甚至另一个训练过的AI作为对手。课程学习这是高级技巧。可以设计一个“课程”随着Boss表现变好逐步增加难度。例如阶段一玩家AI静止不动让Boss学习基本的移动和攻击。阶段二玩家AI会简单移动和闪避。阶段三玩家AI会使用技能和走位。阶段四提高玩家AI的属性血量、攻击力。 通过动态调整这些参数可以引导Boss平稳地学习到高难度技巧。3. 实战搭建从零构建Unity ML-Agents Boss训练工程理论说完了我们进入实战环节。请确保你已安装Unity建议2021 LTS或更新版本和Python环境用于运行训练脚本。3.1 环境准备与ML-Agents安装首先在Unity中创建一个新项目3D Core模板即可。安装ML-Agents包打开Package Manager选择“Unity Registry”搜索“ML-Agents”并安装。或者对于更前沿的特性可以从Git URL添加https://github.com/Unity-Technologies/ml-agents.git?pathcom.unity.ml-agents。安装Python依赖打开命令行创建一个虚拟环境推荐然后安装pip install mlagents这将会安装mlagents命令行工具和相关的PyTorch等依赖。3.2 Boss智能体脚本编写在Unity中创建一个C#脚本命名为BossAgent并让它继承自Agent类。这是核心。using UnityEngine; using Unity.MLAgents; using Unity.MLAgents.Actuators; using Unity.MLAgents.Sensors; public class BossAgent : Agent { // 引用 private Rigidbody rb; private PlayerController player; // 假设有一个PlayerController脚本在玩家身上 private BossCombat combat; // Boss自身的战斗逻辑脚本 // 参数 public float moveSpeed 5f; public float rotateSpeed 180f; public float idealCombatDistance 5f; public float distanceTolerance 2f; // 初始化 public override void Initialize() { rb GetComponentRigidbody(); combat GetComponentBossCombat(); // 玩家引用需要在场景初始化时赋值或通过Tag查找 player GameObject.FindGameObjectWithTag(Player).GetComponentPlayerController(); } // 收集观察值 public override void CollectObservations(VectorSensor sensor) { if (player null) return; // 1. 相对位置与朝向 Vector3 toPlayer player.transform.position - transform.position; sensor.AddObservation(toPlayer.normalized); // 3个值 (x, y, z) float dot Vector3.Dot(transform.forward, toPlayer.normalized); sensor.AddObservation(dot); // 1个值 Vector3 localPlayerPos transform.InverseTransformPoint(player.transform.position); sensor.AddObservation(localPlayerPos.normalized); // 3个值 // 2. 自身状态 sensor.AddObservation(combat.CurrentHealthPercent); // 1个值 sensor.AddObservation(combat.IsSkillOnCooldown(0)); // 普攻冷却 sensor.AddObservation(combat.IsSkillOnCooldown(1)); // 重击冷却 sensor.AddObservation(combat.IsSkillOnCooldown(2)); // 冲锋冷却 // ... 其他技能状态 // 3. 历史信息示例最近3次攻击命中 foreach (bool hit in combat.recentHitHistory) // 假设这是一个Listbool { sensor.AddObservation(hit ? 1.0f : 0.0f); } // 总观察值维度 313133 14 (示例根据实际情况增减) } // 执行动作 public override void OnActionReceived(ActionBuffers actions) { // 处理连续动作移动和转向 Vector2 moveInput new Vector2(actions.ContinuousActions[0], actions.ContinuousActions[1]); float rotateInput actions.ContinuousActions[2]; // 移动 Vector3 moveDirection new Vector3(moveInput.x, 0, moveInput.y).normalized; Vector3 worldMove transform.TransformDirection(moveDirection) * moveSpeed * Time.deltaTime; rb.MovePosition(rb.position worldMove); // 转向 float rotation rotateInput * rotateSpeed * Time.deltaTime; Quaternion deltaRotation Quaternion.Euler(0f, rotation, 0f); rb.MoveRotation(rb.rotation * deltaRotation); // 处理离散动作释放技能 int skillToUse actions.DiscreteActions[0]; bool defend actions.DiscreteActions[1] 1; combat.ExecuteSkill(skillToUse); // 这个方法会处理技能释放逻辑和冷却 combat.SetDefend(defend); // 奖励塑造保持理想距离 float distanceToPlayer Vector3.Distance(transform.position, player.transform.position); float distanceReward Mathf.Exp(-Mathf.Pow(distanceToPlayer - idealCombatDistance, 2) / (2 * distanceTolerance * distanceTolerance)); AddReward(distanceReward * 0.001f); // 非常小的每步奖励 } // 回合开始重置 public override void OnEpisodeBegin() { // 重置Boss状态 combat.ResetHealth(); combat.ResetAllCooldowns(); // 重置位置可以在一个范围内随机 transform.localPosition new Vector3(Random.Range(-5f, 5f), 0, Random.Range(-5f, 5f)); transform.localRotation Quaternion.identity; // 重置玩家如果玩家也是Agent或需要重置 if (player ! null) { player.ResetPlayer(); } } // 外部调用当Boss击中玩家时 public void OnHitPlayer(float damageDealt) { AddReward(0.3f damageDealt * 0.01f); // 基础奖励伤害比例奖励 } // 外部调用当Boss被玩家击中时 public void OnGetHit(float damageTaken) { AddReward(-0.2f - damageTaken * 0.01f); } // 外部调用当Boss击败玩家 public void OnPlayerDefeated() { AddReward(5.0f); EndEpisode(); // 结束本轮训练 } // 外部调用当Boss被击败 public void OnSelfDefeated() { AddReward(-3.0f); EndEpisode(); } }3.3 配套组件脚本你需要编写BossCombat和PlayerController或PlayerAgent脚本。这里给出BossCombat的简化框架public class BossCombat : MonoBehaviour { public float maxHealth 1000f; private float currentHealth; public Listfloat skillCooldowns; // 技能冷却时间列表 private float[] skillCooldownTimers; public Listbool recentHitHistory; // 最近命中记录 public float CurrentHealthPercent currentHealth / maxHealth; void Start() { currentHealth maxHealth; skillCooldownTimers new float[skillCooldowns.Count]; recentHitHistory new Listbool() { false, false, false }; } void Update() { // 更新技能冷却 for (int i 0; i skillCooldownTimers.Length; i) { if (skillCooldownTimers[i] 0) skillCooldownTimers[i] - Time.deltaTime; } } public bool IsSkillOnCooldown(int skillIndex) { return skillCooldownTimers[skillIndex] 0; } public void ExecuteSkill(int skillIndex) { if (skillIndex 0 || IsSkillOnCooldown(skillIndex)) return; // 0代表“无技能” // 触发技能动画、碰撞体、伤害计算等 Debug.Log($Boss 使用技能 {skillIndex}); // 假设这里通过动画事件或触发器来实际造成伤害并调用BossAgent的OnHitPlayer // 技能释放后进入冷却 skillCooldownTimers[skillIndex] skillCooldowns[skillIndex]; } public void TakeDamage(float amount) { currentHealth - amount; if (currentHealth 0) { currentHealth 0; GetComponentBossAgent()?.OnSelfDefeated(); } } public void RecordHit(bool hitSuccess) { recentHitHistory.RemoveAt(0); recentHitHistory.Add(hitSuccess); } // ... 其他重置方法 }3.4 配置训练参数文件在项目根目录创建一个config文件夹里面新建一个YAML文件例如boss_trainer_config.yaml。这是控制训练过程的大脑。behaviors: BossBehavior: # 这个名称需要和Unity中Behavior Parameters组件的Behavior Name一致 trainer_type: ppo # 使用PPO算法最常用且稳定 hyperparameters: batch_size: 1024 buffer_size: 10240 learning_rate: 3.0e-4 beta: 5.0e-3 # 熵系数鼓励探索 epsilon: 0.2 lambd: 0.95 num_epoch: 3 learning_rate_schedule: linear network_settings: normalize: true hidden_units: 128 num_layers: 2 reward_signals: extrinsic: gamma: 0.99 # 折扣因子越接近1越考虑长远回报 strength: 1.0 max_steps: 5.0e6 # 最大训练步数 time_horizon: 64 # 每次更新前收集的步数 summary_freq: 10000 # 每多少步输出一次总结日志3.5 启动训练在Unity中为Boss的GameObject添加Behavior Parameters组件设置Behavior Name为BossBehavior并根据脚本定义好Vector Observation的Space Size我们之前算的14以及Actions的Continuous和Discrete分支。添加Decision Requester组件设置Decision Period为5意思是每5个Unity帧请求一次决策平衡性能与响应速度。构建并运行场景确保多个训练区域能正常工作。在命令行中进入到项目根目录运行训练命令mlagents-learn config/boss_trainer_config.yaml --run-idboss_v1 --envbuilds/YourGameBuild --num-envs20 --force--run-id: 本次训练运行的标识符用于区分不同实验。--env: 指向你构建的可执行文件路径。如果直接在Editor中训练可以不加此参数使用--envnull。--num-envs: 并行环境数量与你Unity场景中复制的区域数对应。--force: 如果已有同名run-id的模型则覆盖。训练开始后你会看到命令行窗口输出每一步的奖励和进度。同时可以使用TensorBoard来可视化训练过程tensorboard --logdir results4. 训练调优与问题排查实录训练AI很少能一帆风顺。下面是我在多次实践中总结的常见问题与解决思路。4.1 问题奖励不增长智能体“摆烂”现象累计奖励曲线几乎是一条零附近的水平线Boss要么不动要么重复无意义的动作。排查与解决检查奖励函数这是最常见的原因。是否正负奖励抵消严重比如击中玩家得0.1但移动一步就扣-0.1。确保智能体做出正确行为时奖励净值是明显正向的。初期可以给予更“慷慨”的奖励。检查观察值观察空间是否包含了足够且正确的信息打印出几组观察值看看数值是否在合理范围内有无NaN或极大值。确保Player引用不为空。简化任务是不是一开始太难了让玩家AI先站着不动只训练Boss的移动和基础攻击。使用课程学习从简单任务开始。调整超参数增加beta熵系数来鼓励探索比如从5.0e-3调到1.0e-2。降低learning_rate有时也能让学习更稳定。检查动作执行在OnActionReceived中打印接收到的动作值看看是否在预期范围内。连续动作是否被正确应用到Rigidbody或CharacterController上4.2 问题智能体学会“作弊”或出现怪异行为现象Boss获得了高奖励但行为不符合预期。例如它可能疯狂旋转而不攻击因为旋转有微小奖励或者卡在墙角因为那里距离奖励计算有误。排查与解决审查奖励漏洞仔细检查每一步的奖励添加逻辑。是不是在Update()而不是OnActionReceived()里加了奖励是不是有 unintended 的奖励源奖励函数必须绝对精确地反映你的设计意图。增加负奖励对于不希望出现的行为明确给予惩罚。例如对无意义的持续旋转施加一个微小的负奖励。修改观察值如果Boss卡墙角可以在观察值中加入“是否碰撞到墙壁”的布尔值并对此施加负奖励。约束动作空间给连续动作如移动速度加上合理的物理限制防止出现不现实的移动。4.3 问题训练不稳定奖励曲线剧烈波动现象奖励曲线像过山车没有收敛的趋势。排查与解决减小learning_rate过高的学习率可能导致策略更新步伐太大无法稳定。尝试将其降低一个数量级如从3e-4降到1e-4。增加batch_size和buffer_size更大的批次和缓冲区能让梯度估计更稳定。检查环境随机性每次OnEpisodeBegin()重置时随机化是否过于剧烈比如Boss和玩家的出生点距离极远或极近。适当控制随机范围保证初始状态在一个合理的分布内。确保环境确定性除了你设计的随机源物理引擎、Time.deltaTime等是否可能导致不同步在训练时尽量固定随机种子。4.4 问题技能释放逻辑混乱现象Boss乱放技能或者在冷却时仍试图释放。排查与解决在动作执行端做限制就像我们代码里写的在ExecuteSkill中如果技能在冷却中或动作值是0无技能直接return。不要依赖AI自己学会“技能在冷却时不能放”这个规则。提供清晰的冷却信号观察值中的技能冷却状态必须准确。确保IsSkillOnCooldown方法返回正确的布尔值。对“空放技能”施加惩罚在奖励函数中如果AI选择了某个技能但该技能处于冷却状态可以给予一个小的负奖励加速它学习冷却机制。4.5 模型导出与应用训练完成后在results目录下会找到最好的模型文件.onnx格式。导入模型在Unity中将生成的.onnx文件拖入Project视图。切换为推理模式在Boss的Behavior Parameters组件中将Behavior Type从Default改为Inference然后在Model栏中拖入刚才导入的.onnx模型文件。移除或禁用Decision Requester推理模式下模型会自动每帧计算通常不需要Decision Requester。测试运行游戏你的Boss现在就会使用训练好的大脑进行战斗了训练一个强大的AI Boss绝非一蹴而就它需要你像打磨游戏关卡一样反复迭代观察空间、动作空间和奖励函数。每一次训练结果的反馈都是你与AI的一次对话。当你看到Boss从最初的跌跌撞撞到后来能娴熟地走位、连招、甚至诱敌深入时那种成就感是无可比拟的。最关键的是这个Boss的“套路”是涌现出来的而非你设定的这为你的游戏带来了真正动态和持久的挑战性。