假设创作者输入这样一句话:“做一个足球小游戏,规则尽量接近正式比赛,对手是电脑。”如果只看演示效果,AI几秒钟就能交出一张绿茵场俯视图、二十二个穿着两种颜色球衣的小人、一个写着“0:0”的比分牌。截图发出去很像一个游戏。但只要按下“开始”,问题就来了:球被踢出底线之后该发球门球还是角球?前锋站在最后一名后卫身后接球算不算越位?比分牌什么时候加一?九十秒倒计时结束后游戏停在哪一帧?

这些问题没有一个能从图片里得到答案。所以讨论“AI能不能生成体育游戏”,首先要把两件事分开:生成素材,和生成游戏。

Prompt:一句话先要被翻译成一份结构化需求

生成流程的第一步不是画图,而是把自然语言转成一份后续每一层都能读取的结构化需求。上面那句话里,“足球”确定了运动类型,“接近正式比赛”意味着需要越位、界外球、角球、球门球等规则,“对手是电脑”确定了单人模式和AI对手的存在。没说的部分——几人制、比赛时长、难度——按默认值补齐并显示出来:以设计参考为例,可以是11人制、上下半场各3分钟、中等难度。

这份结构化需求是后面所有层的“合同”。之后任何一层如果发现自己需要的信息合同里没有,要么回到这一层补,要么显式地使用默认值,而不能自己悄悄编一个。

Generation:十层生成,每层都在引用上一层

从结构化需求到可玩原型,可以拆成下面这条链:Prompt → Sport → Rules → Map → Players → AI Opponent → Scoring → UI → Game Loop → Playable Prototype。它看起来像流水线,但更准确的理解是一张依赖图:后面的层大量引用前面层的定义,前面层一旦含糊,后面层就只能猜。

主要输出 依赖 常见断裂
Sport 运动类型、人数、时长、基本玩法 结构化需求 把“足球”和“五人制”混用,人数与场地规模不匹配
Rules 进球、越位、界外、犯规、换边、胜负判定 Sport 引用了地图中不存在的区域
Map 场地尺寸、边线、中线、禁区、球门、碰撞体 Sport、Rules缺中线或禁区的可检测区域定义
Players 球员属性、位置分工、动作集 Sport、Map 速度参数与场地尺寸不匹配,跑一次全场要半分钟
AI Opponent 决策逻辑、状态机、难度参数Rules、Map、Players没有状态切换,丢球后站桩
Scoring 计分事件、比分存储 Rules、Map 进球判定与规则定义的“整球越过门线”不一致
UI比分牌、计时器、提示 Scoring、Rules显示的比分与规则层记录不同步
Game Loop开球、比赛进行、暂停、重开、终局 以上全部没有终止条件或平局出口

Rules 与 Map:越位规则需要一条“能被计算”的中线

越位是最能说明层间依赖的例子。规则层写下“进攻球员在对方半场、比倒数第二名防守球员更靠近对方底线时接球即越位”,这条规则至少引用了三个空间概念:半场归属、底线位置、每个球员在进攻方向上的坐标。如果地图层只生成了一张画着白色中线的草皮贴图,却没有在逻辑上定义“中线的位置和哪一侧属于哪支球队”,规则层就无法计算越位——要么永远不吹,要么在本方半场也吹。

所以地图在这里不是美术资产,而是规则的坐标系。以设计参考为例,一块105米×68米的场地,除了渲染用的贴图,还需要一份逻辑描述:中线在x=52.5,禁区是距底线16.5米、宽40.3米的矩形,球门宽7.32米。规则层引用的是这份描述,而不是贴图上的像素。这也是AI体育地图生成必须“先解决可玩性再谈视觉”的原因。

Players 与 AI Opponent:没有状态机,电脑就会站桩

最常见的“能跑但不能玩”现象,是电脑球员只会一件事:朝球跑。进攻时十个人一起追球,防守时十个人一起追球,一旦球被玩家带走,所有电脑球员挤成一团;更糟的情况是,只有持球逻辑、没有无球逻辑,电脑方一丢球,所有人原地不动。

避免这一点,AI对手至少需要一个以球权为核心的状态机:本方控球时进入进攻状态,前锋前插、边路拉开、后卫压上;对方控球时进入防守状态,最近的一到两人上抢,其余人回收保持阵型;球处于无人控制时进入争抢状态;死球时进入站位状态,按规则站到界外球或角球的合理位置。每个状态下,不同位置的球员还有不同的目标点。这一层依赖地图(知道哪里是本方半场)、依赖规则(知道死球后谁发球)、依赖球员分工(知道谁是后卫),任何一个依赖缺失,状态机都搭不起来。

Scoring 与 UI:两个地方记分,就会出现两个比分

另一种不太显眼但很伤体验的断裂:计分逻辑在规则层,比分显示在UI层,两者各自维护一个数字。规则层判定了进球,UI层却因为监听的是“球碰到球网”事件,漏掉了一个贴着门柱滚进去的球;或者进球后被判越位取消,规则层把比分减了回去,UI层没有收到撤销事件。结果是屏幕上显示2:1,比赛却按1:1进入了加时。

解决办法是让比分只有一个来源:规则层是唯一的记录者,UI层只负责读取和显示,不做任何自己的判定。这听起来是工程常识,但在自动生成的代码里,如果没有明确约束,模型很容易在两个地方各写一份计分逻辑。

Game Loop:比赛必须能结束

最后一层把前面所有部分串起来:开球、比赛进行、死球处理、半场换边、终局判定、重新开始。这里最常见的漏洞是终止条件不完整——定义了“时间到比分高者胜”,却没定义平局。如果创作者要求“必须分出胜负”,游戏循环就要包含加时或点球大战的分支,否则会在平局时卡在一个既不结束也不继续的状态。

把这十层放在一起看,就能理解为什么说生成素材不等于生成游戏:一个真正的体育游戏至少要有Map、Player、Rules、Input、AI、Score、Win Condition、Game Loop八个部分,而且它们不是并列摆放的资源,而是互相引用的逻辑。少任何一个,或者任意两个之间引用断裂,得到的就只是一张会动的图。

Simulation:让游戏先在没有人看的时候跑起来

十层生成完成后,还不能直接交给创作者。更稳妥的做法是先在后台把游戏跑起来。在乐鱼体育的设计里,这一步是交付前的固定环节:两支AI球队互相对抗,不渲染画面,以加速模式运行。以一个模拟场景为例,同一个版本会在交付前连续模拟数百局,每一局记录完整的事件日志——开球、传球、射门、进球、越位、界外、换边、终局。

模拟的意义在于把“看起来能玩”变成“统计上能玩”。一局比赛能结束不代表每一局都能结束;十局比分正常不代表两百局里没有一次比分错乱。只有跑足够多的局数,那些低概率但致命的问题才会暴露出来。

Testing:用指标定位出错的层

模拟跑完之后,测试阶段要回答的不是“好不好玩”这种主观问题,而是一组可以自动判定的问题:

  • 比赛能否结束:所有模拟局是否都到达了终局状态,是否有局卡在平局或死循环里。卡住了,通常指向Game Loop层。
  • 比分是否一致:事件日志里的进球数、规则层记录的比分、UI层显示的比分三者是否相同。不一致,指向Scoring或UI层。
  • 球员是否长时间静止:比赛进行中,某名球员速度为零持续超过若干秒(例如8秒)且球处于活球状态。频繁出现,指向AI Opponent层的状态机。
  • 规则是否被触发:“接近正式比赛”却在两百局里一次越位都没吹,可能是规则层引用的中线不存在;每局吹二十次,可能是半场归属弄反了。
  • 球是否卡死:球在角落或球门后方的碰撞体里停留过久,指向Map层的碰撞设计。
  • 结果分布是否合理:中等难度下,AI对AI应接近五五开;如果一方胜率远高于另一方,说明两边的生成参数不对称。

更进一步,还可以让试玩Agent模仿玩家行为:随机输入的Agent用来发现崩溃和卡死,按脚本行动的Agent用来验证特定规则(比如故意站到越位位置接球),用强化学习训练的Agent则可以发现“一直在同一个位置射门就能得分”这类设计漏洞。关于这类让AI自己玩一遍再检查的方法,可以参考AI自己试玩检查游戏问题这篇文章。

Revision:只重做出错的那一层

测试定位到问题之后,修改的原则是:只重新生成出错的那一层,以及依赖它的下游层,而不是整个推倒重来。越位从来不被吹,定位到地图缺少中线定义,那就补全地图的逻辑描述,然后重新验证规则层和AI层(因为AI的站位也引用了半场归属),计分和UI层不用动。

每一次修改都应该产生一个新版本。V1通过测试交给创作者,创作者要求“加一个点球大战”生成V2,V2测试发现点球环节的计时器没有停,修复后成为V3;如果创作者试玩后觉得点球大战拖慢节奏,可以直接回到V1。版本记录不仅方便回退,也让测试结果可以对比:V3的平均比赛时长比V1多了多少,AI胜率有没有变化,一目了然。

这里还有一个容易忽略的边界:自动测试能发现“坏掉的”问题,但发现不了“无聊的”问题。一个比赛能结束、比分一致、没人站桩的足球游戏,仍然可能因为节奏太慢或AI太呆板而不好玩。所以测试通过后,最终判断仍然要回到创作者手里——而系统要做的,是保证交到创作者手里的至少是一个完整、可运行、不会中途卡死的版本。

所以,AI能不能一直生成到可以玩

答案是可以,但前提是把“生成”理解为生成一整套互相引用的逻辑,而不是生成一组好看的资源。从一句话到可玩原型,中间有十层依赖,每一层都可能因为上一层的含糊而断裂;真正让这条链可靠的,不是某一层生成得多漂亮,而是层与层之间的引用是否明确,以及交付前是否经过了足够多局的模拟和测试。乐鱼体育在体育游戏生成大模型上的工作,很大一部分就花在这些不显眼的层间约束和自动验证上。如果想从“每一层产物和检查点”的角度看同一件事,从文字提示到可玩体育小游戏需要多少层生成给出了更清单化的版本。