以一个模拟场景为例:用户想做一个“南方雨季足球杯”,先在一个工具里生成了8支球队和完整赛程,赛程说明里特意写着“赛事在梅雨季举行,约一半比赛会在雨中进行”;然后在另一个工具里生成了比赛场地,得到一块漂亮的草皮球场;最后再把这些素材导入游戏编辑器。试玩第一场雨战时,画面里在下雨,但球的滚动速度、球员的急停距离和晴天一模一样,草皮上甚至连积水反光都没有。天气写在了赛事里,却没有传到场地里,更没有传到物理规则里。
这就是分开生成最常见的问题:每个模块单独看都完成了任务,合在一起却互相不认识。乐鱼体育把地图生成、赛事生成和游戏生成放进同一个AI系统,出发点就是这个。
一场比赛要回答的四个问题
把体育游戏拆开看,至少有四个问题需要回答:
- 在哪里比赛:场地尺寸、地形、路线、障碍、天气承载能力,这是地图生成的工作。
- 谁和谁比赛:球队、球员、强弱差异、对阵关系和赛程,这是赛事与球队生成的工作。
- 怎么比赛:上场人数、计分方式、时长、胜负判定、犯规,这是规则生成的工作。
- 如何组合起来:输入控制、电脑对手、计分板、比赛循环,把前三者变成一个能开局、能进行、能结束的游戏,这是游戏生成的工作。
前三个问题的答案彼此引用。场地大小决定了多少人上场才不拥挤,上场人数决定了球队名单怎么排,天气决定了场地表面和物理参数,赛程场次又受规则里的单场时长影响。任何一个答案单独生成,都要去“猜”其他答案,而猜错的概率很高。
分开生成时,断裂具体出现在哪里
下面这张表列出几个在模拟测试里很容易复现的断裂,它们都不是某个模块“做错了”,而是模块之间没有共享信息:
| 上游设定 | 下游没跟上 | 玩家实际看到的问题 |
|---|---|---|
| 赛事:半数比赛为雨天 | 地图没有排水与抓地设置 | 雨天和晴天手感完全一样,天气只剩画面效果 |
| 规则:3v3半场 | 球队生成按惯例排了12人首发 | 场上挤满球员,或多出来的9人站在场边不动 |
| 赛事:安排了晚场比赛 | 场馆没有灯光配置 | 夜场画面一片漆黑,或者干脆按白天渲染 |
| 地图:赛道宽度只容纳4辆车并排 | 赛事设置20人同时发车 | 发车区严重堵塞,第一个弯道就大面积碰撞 |
| 规则:单场40分钟 | 赛程按一天8场安排在同一块场地 | 日程排不下,或比赛时间互相重叠 |
这些问题有一个共同点:只要有一个地方记住“这是3v3”“这是雨天”“赛道只有4车宽”,下游模块读一下就能避免。分开生成的工具不是没能力处理,而是根本拿不到这些信息。
一条链:Map → Team → Rule → Event → Game
在同一个系统里,这些模块围绕同一份“比赛描述”工作。它记录运动类型、场地参数、天气、上场人数、计分方式、队伍数量、赛制等核心字段,每个模块既读它,也往里写入自己的产物。顺序大致是:
- Map:先确定场地。3v3半场篮球只需要一个篮筐;雨季足球场要写入排水等级和湿地抓地系数;山地自行车赛道要写入宽度、坡度和最窄处能并排几辆车。
- Team:读取场地和上场人数,再生成球队。3v3就生成3名首发加1到2名替补,而不是照搬11人足球或5人篮球的名单结构。
- Rule:读取场地和球队,确定计分、时长、犯规和胜负判定。雨天场地的抓地系数会直接进入规则层的移动与控球参数。
- Event:读取规则里的单场时长和队伍数量,推算赛程。8队单败淘汰需要4场、2场、1场共7场;如果单场40分钟且只有一块场地,系统会算出每天最多能排几场,而不是随手塞进8场。
- Game:把以上内容装配成可玩的版本,加入操作输入、电脑对手、计分板和比赛循环,并做一轮自动试玩检查。
这个顺序并不是说用户必须从地图开始填。用户可以一句话描述整场赛事,也可以先挑球队再选场地,系统会把输入归位到链条中对应的环节,缺什么就用默认值补上,并把默认值显示出来。具体的一句话生成过程,可以参考一句话体育游戏生成流程。
改一处,其余部分知道要跟着动
一体化带来的另一个好处是修改时的连锁提示。假设用户把篮球赛从3v3改成5v5,系统能沿着链条列出受影响的内容:
- 地图:半场可能不够,需要改为全场,或保留半场并提示“5v5在半场会非常拥挤”。
- 球队:每队首发从3人变为5人,替补结构需要重排,已经写好的球员简介要补充。
- 规则:进攻计时、得分方式、犯规次数的默认值都需要换一套。
- 赛事:单场时长变长,原本一天能排的场次可能排不下,需要重新计算日程。
系统不会在用户不知情的情况下把这些全部改掉,而是列出影响范围,让用户选择“全部按5v5默认值更新”或“逐项确认”。每一次这样的调整都会保存为新版本,改坏了可以回到上一个版本。在各个模块彼此独立的工具里,这种提示几乎无法实现,因为没有任何一个模块知道其他模块用了“3v3”这个前提。
最后一环:拼起来之后要真的跑一遍
共享同一份比赛描述,能拦住大部分“字段对不上”的问题,但还有一类问题只有在游戏真正运行时才会暴露。比如雨天的抓地系数已经正确传到了规则层,可是在这个系数下,电脑球员在禁区里急停时总是滑出底线,导致雨战里几乎每一次进攻都以出界结束;又比如赛车赛道宽度和发车人数匹配了,但赛事设定的湿滑路面和发车后第一个下坡弯道的入弯速度组合在一起,大多数电脑车手根本转不过去。
所以在Game这一环,系统会让电脑控制的角色按默认设置完整地跑几局:从开球或发车开始,到比赛正常结束为止,记录有没有卡死、有没有永远达不到的胜利条件、有没有某个位置反复发生碰撞。检查结果同样写回那份比赛描述,并标注是哪一环的参数引起的,用户看到的提示会是“雨天场地下电脑球员出界次数偏高,建议把抓地系数从0.55调到0.65”,而不是一句笼统的“生成失败”。这里的系数是示意值,实际数值取决于具体的物理设置。
这一步之所以必须放在同一个系统里,是因为只有同一个系统才知道一个问题应该回到哪个模块去修。独立工具最多能告诉用户“游戏跑起来有问题”,却说不清问题来自地图、规则还是赛程。
放在一个系统里,不等于把模块捆死
把模块整合在一起,常被担心会变成“只能一次生成全部,改不了局部”。乐鱼体育的做法是共享数据、分开编辑:用户仍然可以进入AI体育地图生成单独调整一段赛道的坡度,或者在赛事页面单独改某支球队的风格,只是这些修改会写回同一份比赛描述,其他模块随后能读到。
用户也可以锁定某个模块。比如一位用户已经花了很多时间调好了一张高尔夫球场,想试几种不同的比赛形式,就可以锁定地图,只让赛事和规则重新生成。系统在生成新规则时如果发现和锁定的地图冲突,比如想加一项“最远开球挑战”,但锁定的地图9个洞全是Par 3短洞,没有足够长的Fairway,就会提示而不是去改动锁定内容。
这种设计也有明确的边界。一体化只能保证系统里记录过的参数保持一致,没有被建模的东西它无法检查,比如用户在球队简介里手写了一句“这支队从不在雨天输球”,系统就不会把它当成一条规则。这类文字描述和机器能理解的参数之间的差距,需要通过界面清楚区分“这是说明文字”和“这是会生效的规则”。
回到那场雨季足球杯。在同一个系统里,赛事写下“雨天”的那一刻,场地的排水等级和抓地系数就会被标记为需要设置,规则层的控球参数会读取这个系数,试玩时雨战的手感自然就和晴天不同。用户不需要知道这中间经过了几个模块,他只会觉得:下雨的比赛,确实像是在雨里踢的。