即时战略游戏AI如何预测与调节难度
《蘑菇战争2》开发者介绍基于权重的Utility AI,以及为何决策需要预测单位抵达时的局面。文章还讨论手工设计开局与调节难度,使机器人保持挑战性和合理表现。
AI 深度解读
让 RTS 机器人变强,和让玩家愿意继续跟它打,是两件事。Mushroom Wars 2 的做法是:用未来状态预测支撑决策,用人工开局补足短板,再通过反应速度和感知限制调节难度。
这篇文章值得读的地方,是作者把这些选择落到了具体计算上:援军什么时候赶到、技能是否值得交、三路部队怎样同时抵达。它也坦白记录了神经网络尝试的失败。不过,文中的不少建议仍是开发经验,不能直接当作经过对照验证的结论。
为什么兵力更多,也可能不该进攻
作者团队采用接近效用 AI(Utility AI)的权重方案:给候选行动打分,再决定做什么。但如果评分只看当前状态,权重调得再细,也容易做错决定。
原文的例子是:己方有 100 个单位,敌方三座建筑分别有 50、10、70 个单位,距离分别为 30、60、80。暂时假设攻占时双方兵力按一比一交换,最近的目标似乎很好打。
问题是,部队在路上时,建筑仍在产兵。走完 30 米,目标守军可能已经从 50 涨到 60。附近两座建筑还能增援,把赶得上的援军算进去,防守兵力可能达到 110。与此同时,大约过去 10 秒,己方建筑又生产了 20 个单位。
所以,真正要比较的是抵达时的双方力量,以及后续支援能否接上。距离更远、附近没有敌方支援点的建筑,反而可能更值得进攻。
预测函数决定评分依据是否靠谱,权重决定机器人如何取舍。
下面这段示意代码,把建筑生产和范围内能够及时抵达的援军合并计算:
public struct Building {
public float2 pos;
public float units;
public float productionPerSec;
public float capacity; // limit after which production stops
public int owner;
}
public static class Forecast {
public static float UnitsAt(in Building b, float t)
=> math.min(b.units + b.productionPerSec * t, math.max(b.units, b.capacity));
// How many defenders the target will have when the attack arrives.
public static float DefendersOnArrival(in NativeArray<Building> all, int target,
float arrivalTime, float unitSpeed,
float supportRadius, float supportShare) {
var tgt = all[target];
float total = UnitsAt(tgt, arrivalTime);
for (int i = 0; i < all.Length; ++i) {
if (i == target || all[i].owner != tgt.owner) continue;
float dist = math.distance(all[i].pos, tgt.pos);
if (dist > supportRadius) continue;
// The enemy only reacts once it sees our units leave,
// and only helps if it arrives before we do.
float travel = dist / unitSpeed;
if (travel > arrivalTime) continue;
total += UnitsAt(all[i], arrivalTime - travel) * supportShare;
}
return total;
}
}
要留意 arrivalTime - travel:援军必须预留赶路时间,不能把它在进攻抵达前生产的全部单位都算作现场守军。UnitsAt 里的 math.max(b.units, b.capacity) 则让已经超过容量的驻军保持原数,不会被预测函数强行截回容量。
supportShare 表示对手愿意拿出多少比例的驻军支援。作者认为,对人类玩家设为 0.5 到 0.7 比直接设为 1 更合理;高难度下还可以从回放统计实际支援习惯。这仍是模型假设,并非敌人必然采取的行动。
预测结果归一化后,还要结合建筑价值、被反击的风险等因素评分。游戏里成功进攻带来的士气加成,也会影响后续攻防。作者强调,一次决策可能调用预测函数数百次,因此它需要保持纯函数形式,计算成本也要低。
开局靠脚本,技能也不必直接交给神经网络
这套逻辑能攻能守,却没有自然长出高手的开局能力。
开局周围都是不会主动防守的中立建筑,机器人能占下来,但扩张速度明显落后于熟悉地图的顶尖玩家。团队的解决办法很直接:人工记录多个开局变体,每局随机选一个,控制半张地图后再切回常规决策。
这不是模型自己学会的策略,而是开发者明确写进去的知识。随机选择多个变体,则是为了避免玩家总能猜中下一步。
技能使用更难。加速、范围燃烧、建筑间快速转移,都可以写出基础触发规则。例如,某个半径内敌军超过阈值就放燃烧。但「能够使用」和「此刻值得使用」之间,还有很大距离。
作者尝试用回放训练神经网络,结果不满意,最终放弃。他推测训练和参数调节不足,但没有给出样本规模、模型结构、指标或对照结果,因此无法判断失败究竟发生在哪个环节。
文中另一个案例来自作者认识的开发者:对方用一个据其回忆约有三层的感知机,输入数据库中的几十盘棋局,输出棋盘格子的热力图,再把系数加入搜索评估。浅层搜索下,机器人开始改变策略;方案最终没有上线,作者转述的原因是搜索计算成本,而非网络本身。
这两个故事之外,文章给出两条折中路线:
- 用预测增强效用评分,比较技能未来能摧毁或保住多少价值,再考虑冷却成本。
- 对确定性规则做短时模拟,分别计算释放与不释放技能的结果;先用简单规则筛选,只模拟最好的 2 或 3 个候选。
后者需要能可靠复现规则的模拟器,也需要额外算力。它是可选方案,原文没有证明团队已经完整落地。
难度应该改变反应条件,还是直接让它选错
作者描述的开发顺序是,先把机器人做强,强到玩起来不舒服,再逐步削弱。
最容易调的旋钮是思考延迟。支援晚一点,重新判断局势慢一点,原先算得成立的进攻就可能失败。但固定延迟容易让行为机械,文章建议从接近人类反应时间的分布中抽样,例如简单难度采用均值为 300 到 600 ms 的对数正态分布,同时影响反应和重新决策的频率。
文章更倾向于降低感知能力,让机器人晚一点发现威胁。作者认为,如果它看见了正确答案,却故意选差的行动,玩家更容易觉得自己被让着赢。
资源加成则有明确使用边界。生产速度提高 10%、单位移动更快、初始兵力更多,这些方法只用于单人战役。多人模式前期负责教学的机器人,则是一对一公平对局,不拿额外加成。
最高难度靠协调能力体现实力。三座建筑进攻同一目标时,最远的一路先出发,其余两路按距离依次出兵,让所有部队同时抵达。
计算方式是倒推时间:每组行军时间为 t_i = dist_i / speed,取最大值 T,第 i 组延迟 T - t_i 出发。规则不复杂,却能产生玩家也愿意模仿的操作。
团队还让机器人评估对局是否值得继续,考虑双方领土等条件,并具备建塔后投降等结束比赛的行为。不过,具体判断阈值没有展开。
我的判断
我认同这篇文章把「预测」放在决策基础层。无论上层用 FSM、Behaviour Tree、Utility AI 还是 GOAP,只看眼前状态都会漏掉赶路、生产和支援带来的变化。把这些变化收进独立函数,也更容易检查某次决策为何出错。
但预测精度有边界。支援半径、supportShare、对手何时发现进攻,都包含人为假设。上面的代码是机制草图,不能据此认定它已经处理了全部战场变化。
人工开局也有适用条件:地图和开局路径相对稳定,维护成本才容易控制。相比继续调通用评分,它能直接补齐已知短板;地图或平衡规则变化后,脚本也需要重新检查。
对神经网络和难度调节,我会保留更多问号。训练失败缺少实验细节,无法推出规则普遍优于网络;感知退化比故意选差更自然,也没有玩家测试数据支持。文章提供了可尝试的设计方向,还没有给出普遍有效的证据。
能拿来做什么
- 做前端策略小游戏时,可以先实现一个独立的未来状态预测函数,用它同时支撑目标选择和防守判断,并检查容量、抵达时间与支援距离的边界。
- 给已有机器人补开局能力时,可以先记录多个可靠变体,随机选择,并明确切回通用逻辑的条件,观察收益是否足以覆盖脚本维护成本。
- 实现范围技能时,可以先比较预测收益与冷却成本;如果已有确定性模拟器,再对筛选后的 2 或 3 个候选做短时模拟。
- 调节难度时,可以分别调整发现威胁的时间、反应延迟和重新决策频率,再通过实际试玩验证体验。需要公平对局时,让强度来自协调操作也有明确的实现路径。