高斯核在游戏设计中的实战应用
05/2457 浏览开发心得 疑似 AI 合成内容
一种直觉的数学化
你在写一个仓库拍卖游戏的战利品系统。
最朴素的想法是:从物品池里随机抽,直到凑够总价值。
跑起来一看——运气好时全是高价大件,运气差时一堆零碎小物,两次游戏体验天差地别。
你想要的是"大概在范围内,但还有惊喜"这种感觉。
怎么把这种模糊直觉变成代码?
你需要的工具叫高斯核:weight = exp( -k · distance² )
它描述的是一件最朴素的事——离中心越远,这件事发生的概率越低,但不是零。
weight
1.0 ┤ █ ← k = 3.0(严格集中)
│ ███
│ █████
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒ ← k = 1.0(中等)
0.5 ┤ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ ░░░░░░░░░░░░░░░░░░░░ ← k = 0.3(宽松分散)
│░░░░░░░░░░░░░░░░░░░░░░░
0.0 ┼────────────┬─────→ distance
0
三个参数直觉:
- k 越大,钟形越窄,结果越集中在中心
- k 越小,钟形越宽,结果更分散、更有惊喜
- 中心本身权重永远是 1,永远是最优先的候选
一、战利品生成:围绕目标价值选物品困境
仓库总价 1000 万,要放 15 件物品,平均每件约 67 万。系统每次选物品时,应该给"接近 67 万"的物品更高权重,给"只值 500 块"或"值 800 万"的物品更低权重——但不能完全排除。解法:把"当前目标均价"当中心,把价格偏离程度当距离:weight(item) = exp( -k · ln(item.value / targetPerPick)² )
这里用的是对数比值而不是线性差,因为价格的感知是乘法性质的——10 万偏到 5 万,和 100 万偏到 50 万,感知上"一样偏"。每次从剩余物品中按权重随机选一件,再把它的价格从剩余预算里扣掉,targetPerPick 随之更新。总价值被软性约束在目标附近,但不是死板的精确控制。
二、对称 vs 非对称:一维的各向异性困境
仓库总是出一堆小红,大红几乎没见过。
去看代码,发现用的是标准对称高斯核。
对称核意味着"比目标贵 10 倍"和"比目标便宜 10 倍"惩罚相同。
但在游戏体验上,这两种偏差截然不同:
- 出现一件太便宜的物品:玩家略感失望,无伤大雅
- 出现一件超级昂贵的物品:玩家惊喜,愿意截图分享
以 k=1.5 为例,价值是目标 10 倍的物品,相对权重只有 1.4%——大红不是统计事故,是系统主动压制的结果。
解法:高于目标价的方向用更小的 k(衰减更慢),低于目标价保持正常衰减:
当 value > target: weight = exp( -(k/2) · ln(ratio)² )
当 value ≤ target: weight = exp( -k · ln(ratio)² )
weight
1.0 ┤ *
│ * * * ← 非对称核
│ * * *
0.5 ┤ * * * *
│ * * * ← 对称核 * *
│ * *
0.0 ┼────────────→ ln(value/target)
偏 -2 -1 0 1 2 偏
便 贵
宜 左侧对称 右侧衰减更缓
同样是 10 倍偏离,非对称核下权重提升到约 12%。
把设计意图直接编码进衰减函数的形状,这是高斯核最迷人的地方。
三、各向同性 vs 各向异性:方向决定一切困境
做一个潜行游戏的敌人感知系统。第一版用圆形感知范围:以敌人为圆心,5 米内就被发现。测试下来感觉不对——从背后悄悄靠近和从正面硬闯一样容易被发现,完全不符合常识。
问题在于用了各向同性核:衰减在所有方向上相同,形状是圆。weight = exp( -k · (dx² + dy²) )
Y
↑
░░░░░░░░░
░░░▒▒▒▒▒░░░
░░▒▒▒███▒▒▒░░
░░▒▒▒███▒▒▒░░
░░░▒▒▒▒▒░░░
░░░░░░░░░
└──────→ X
各方向衰减相同(圆形等值线)
解法:换成各向异性核,不同方向用不同 k:weight = exp( -( kx · dx² + ky · dy² ) )
Y
↑
░░░░░
░░░▒▒▒▒▒▒▒░░░
░░▒▒███████▒▒░░
░░░▒▒▒▒▒▒▒░░░
░░░░░
└──────────────→ X
kx < ky:X 方向衰减慢,椭圆横向拉伸
(正前方感知远,侧面和背后感知近)
更进一步,椭圆可以旋转——跟随敌人的朝向动态更新。这样视野锥就自然地成为了敌人身体的一部分。各向异性核的经典应用场景:
- 敌人视野锥:正前方感知远(kx 小),侧后方急剧衰减(ky 大)
- 风向下的气味传播:顺风方向扩散慢,逆风方向扩散快
- 运动模糊:沿速度方向拉伸,垂直方向极窄
- 地形坡向扩散:水流、泥石流沿坡度方向传播
四、双边滤波:懂得在哪里停手的高斯困境
用柏林噪声生成了一张地形图,噪声太杂想做高斯模糊。
效果出来了,但海岸线和悬崖的陡峭边缘也跟着糊掉了——平原和山地的边界变成了一片漫坡,地图失去了地理感。
普通高斯模糊不区分"要不要模糊"——它对所有相邻像素一视同仁,悬崖两侧的格子会互相平滑。
解法:双边滤波(Bilateral Filter),在空间距离之外加入值域差异这第二个维度:weight = exp( -k_spatial · dist² ) · exp( -k_range · Δvalue² )
两个高斯核同时工作:靠近的格子权重高(空间高斯),但值差异大的格子权重被另一个高斯压低。
结果是:相似区域之间自由平滑,跨越陡峭边界的格子互不影响。游戏中的应用:
- 地形生成:平原内部平滑,山地与平原的陡坡保持锐利
- 光照烘焙:光影交界处保持清晰,阴影区内部柔和
- NPC 情绪扩散:同类情绪在友军间传播,跨越阵营边界的传播被抑制
五、可分离高斯:从 2D 拆成两次 1D困境
想给 1080p 全屏加一个 Bloom 辉光,高斯核半径 15 像素。每个像素要做 15×15 = 225 次乘加,两百万像素,60fps,计算量爆炸。
各向同性高斯核有一个救命性质:可以分解为两次独立的 1D 高斯卷积——先横向扫一遍,再纵向扫一遍,结果与一次性做 2D 完全相同。
225 次乘加 → 2×15 = 30 次。GPU 上跑大范围 Bloom 因此变得可行。
注意边界:各向异性核如果椭圆旋转了任意角度,就不可分离,必须完整计算 2D 卷积,或用近似方法。
只有椭圆长短轴与坐标轴对齐时,才能拆成两个不同 k 的 1D 卷积。
六、时间域高斯:把"距离"换成"时间差"困境
赛车游戏的方向盘,直接读取陀螺仪数据——一抖一抖的,玩起来像在冰上漂移。
你想要的是"跟手但不颤抖"的手感,过度平滑又会让操控感觉迟钝。
高斯核的"距离"可以换成任何连续量,包括时间。
把历史输入按时间距离加权平均,越近的帧权重越高——这正是时间域高斯卷积。
k 越大平滑越少(响应灵敏),k 越小平滑越多(手感稳定但黏)。
时间域高斯的其他游戏场景:时间抗锯齿(TAA):当前帧与历史帧加权混合,历史帧权重随帧龄高斯衰减。
越老的帧权重越低,画面发生突变时历史帧的影响自动减小,在抗锯齿和避免鬼影之间取得平衡。
事件余震衰减:BOSS 死亡或队友阵亡后,NPC 的反应强度不应立刻归零,而应随时间自然消退:influence(t) = exp( -k · (t - t_event)² )
适用于冷却期内的 NPC 台词触发概率、debuff 强度衰减、音乐情绪权重的平滑过渡。
七、混合高斯:叠加多个核,制造多峰分布困境
做 RPG 的掉落系统,总感觉少了什么。
物品质量中规中矩,没有那种"废品一堆,偶尔中大奖"的紧张感。
单个高斯核永远只有一个峰——它让结果集中在某个目标附近,天然抹平了两极分化。
要制造"要么很差,要么很好,中间反而少"的体验,需要叠加两个核:weight = w₁ · Gauss(center₁, k₁) + w₂ · Gauss(center₂, k₂)
weight
│ * *
│ * * * *
│ * * * *
│ * * * *
│ * * * *
│ * * * *
│* * * *
│ * *
│ ******
0.0 ┼──────────────────────────────────→ value
↑ ↑
中心1(低价日用) 中心2(高价稀有)
中间价格出现频率极低
典型游戏应用:双峰战利品:一个核集中在低价(日常消耗品),另一个集中在高价(稀有件)。
中间价格反而极少出现——"没什么用 / 超值"的二元体验,比线性分布更有戏剧张力。
敌人行为节奏:攻击间隔有两个峰——“疯狂连击期"和"蓄力等待期”。
用混合高斯采样间隔时间,在不写硬状态机的情况下产生节奏感。
程序生成资源矿脉:贫矿和富矿各有一个核,很少出现"中等矿"。
玩家要么值得停留,要么完全不值——探索的决策更加干脆。
八、截断高斯:有边界的连续分布困境
做 AOE 技能,范围内均匀伤害。
测试下来,玩家在技能圈里根本不走位——站在圈边上和站在圆心受到的伤害完全一样,走位有什么意义?
解法:范围内改用高斯分布伤害,范围外截断为零:
damage = MAX_DMG · exp( -k · dist² ) if dist < radius
= 0 otherwise
damage
MAX ┤ ████
│ ████████
│ ██████████
│ ████████████████
│ ████████████████████
0 ┼──┤ ├──→ distance
↑ ←── radius ──→ ↑
边界外=0 边界外=0
● 核心区:高伤,须规避
◎ 中间圈:可承受,决策空间
○ 边界外:安全
三层策略空间自然形成。走位拉扯、卡距离、近战取舍,都有了数学支撑。
变种:
- BUFF 光环:站在施法者身边收益最大,边缘收益渐弱,超出范围归零
- 仇恨分配:最近目标仇恨最高,但仇恨在一定范围内按高斯分布,而非只锁定最近一个
九、粒子系统与程序动画
困境 A:手枪准星圆,奔跑时大、蹲伏时小。每颗子弹的飞行方向怎么决定,才能让散射既符合直觉又可调节?
粒子初速度方向偏离主轴的角度,几乎普遍使用高斯分布:小角度偏离概率高(形成密集主束),大角度偏离概率低(形成稀疏外沿)。
k 值直接控制"准直性":激光束用大 k,火焰用小 k,手枪的 k 值根据玩家状态动态变化。
困境 B:角色走上台阶时,脚的 IK(逆向运动学)修正量每帧根据地形重算,结果脚像在弹跳。
脚踩地形的目标高度不能直接用,需要在时间轴上做高斯平滑——让 IK 修正"追上"真实地面而不是跳变。
本质上是对目标位置序列做时间域高斯卷积。
十、玩家匹配:技能的高斯不确定性困境
多人对战需要匹配系统。
最朴素的做法是 ELO 分——赢了加分输了减分。
问题是,新玩家只打了 5 场,他的分数到底可信吗?
一个 1500 分的老玩家和一个 1500 分的新玩家,应该被同等对待吗?
微软 TrueSkill 系统的做法:把每个玩家的技能建模为高斯分布:skill ~ N(μ, σ²)
μ 是估计的技能均值,σ 是不确定性。
新玩家 σ 很大(不确定),随着对局积累 σ 收窄(越来越确定)。
匹配算法寻找 μ 相近且 σ 都较小的玩家组队——两个人的技能都已经比较确定时,这场匹配才是公平的。
这里高斯的意义不在于衰减,而在于把一个确定值扩展成一个分布来建模不确定性。
同样的思路可以用在游戏 AI 上:与其给 NPC 一个固定的射击准确率,不如给它一个高斯分布的命中偏差——高技能 NPC 的 σ 小(命中稳定),低技能 NPC 的 σ 大(命中随机)。
十一、自适应高斯:k 值本身是变量困境
AI 敌人平时巡逻时不那么敏感,但一旦听到枪声就应该进入高度警觉状态,感知范围大幅扩大。
用硬状态切换来实现,感觉很生硬——"正常/警觉"之间像拨开关。
让 k 随状态连续变化:警觉值从 0 升到 1 的过程中,k 从 2.0 线性降到 0.5,感知椭圆从小圆变成朝声源方向拉伸的大椭圆。
AI 的行为变化就不是状态机跳转,而是感知能力的平滑过渡。其他应用:
- 动态景深:玩家拉近相机查看细节时,近处虚化的 sigma 减小,推远相机时增大
- 技能疲劳效应:同一 AOE 连续释放,k 值逐渐增大(范围收缩),表达"过热"而不需要单独写冷却系统
- RTS 的损伤状态:单位受损时感知 k 增大,表现出战术收缩而不需要显式编写撤退逻辑
十二、高斯核的边界——什么时候不该用它
认清局限,才能在对的地方用对的工具。
需要长尾分布时困境:想加一个极端事件——超级史诗掉落,概率极低但不能为零,一旦出现玩家会震惊。
高斯核衰减极快,极端值出现概率趋近于零。
这类场景需要幂律分布(Power Law):weight ∝ value^(-α)
幂律的尾部"厚"得多,可以自然产生极端值。
游戏里的彩票掉落、经济系统的财富集中、Pareto 法则(20% 的物品带来 80% 的价值感),背后都是幂律而非高斯。
需要平顶均匀区域时困境:技能范围内所有位置应该同等重要,但又不想要硬边界。
用平顶高斯(Super-Gaussian):weight = exp( -k · dist^(2n) ) n > 1
weight
1.0 ┤ ████████ ← n=3(平顶高斯)
│ ██ ██
│██ ██
0.5 ┤ · · · · · ← n=1(普通高斯)
│ · ·
│ · ·
0.0 ┼─────────────────────→ distance
←── 平顶区 ───→
n 越大,核越平顶越接近 Box 核,但保留平滑边缘过渡。
需要保证总量守恒时困境:RTS 影响力地图每帧更新,单位的威胁值向周围格子扩散,但几轮之后总威胁量越来越大。
高斯核用于权重采样时不需要归一化,但用于扩散时,未归一化的核会导致总量持续增加。
热扩散、传染病扩散、影响力传播,都需要先把核所有权重归一化到 1——扩散前后总量守恒。
小结:四个问题定一个核
每次用高斯核之前,问自己四个问题:
1. 容忍多少偏差? → k 的大小
2. 哪个方向的偏差更可接受? → 对称还是非对称,或各向异性的轴向
3. 边界需要保留吗? → 要不要加值域维度(双边滤波)
4. 状态会变化吗? → k 是常数还是随系统状态更新的变量四个问题回答完,一个高斯核的设计基本就定了。
它不是复杂的数学,而是把"越偏离越不好,但不是完全不可能"这种设计直觉,翻译成机器可以执行的概率权重。



