ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Canvas 跑酷游戏:障碍间距定多少才不劝退,12 组参数实测

Canvas 跑酷游戏:障碍间距定多少才不劝退,12 组参数实测

默认参数下的跳跃我量了 12 组。重力 2400、起跳初速 900 时,最高点实测 161.3 px,理论值 168.8 px,差 4.40%。滞空实测 0.733 秒,理论 0.750 秒,差 2.22%。

同一个脚本玩家跑 25 局,障碍间距保持默认的 280~520 px,中位只有 848 米。把间距改成 700~1200 px,中位直接摸到 3000 米的测试上限,25 局里 21 局一次没死。

劝退还是耐玩,不由障碍数量决定,由时间窗决定。间距写死在像素上,速度却随米数一直涨,两个障碍之间的可用时间一直在缩短。

在线案例与源码:https://atomgit.com/deli007/demo_park/tree/main/codearts-endless-runner-game
页面是一个 3.7 万字符的单文件,参数面板、物理解算、自检全在里面跑,不发任何网络请求。

一、为什么值得搞清楚:间距是像素,难度是时间

障碍间距在页面里是一个区间,默认 280 到 520 px,每次随机取。

速度是另一条式子:速度 = 起步速度 + 每米增速 × 米数。默认 350 起步,每米加 0.35。

两条式子放在一起,就得到一件反直觉的事:间距不动,时间窗一直在缩。

我把三档间距在不同速度下的时间窗算了一遍,单位毫秒。

当前速度对应米数(默认增速)280 px 间距400 px 间距520 px 间距
350 px/s0 米800 ms1143 ms1486 ms
500 px/s429 米560 ms800 ms1040 ms
700 px/s1000 米400 ms571 ms743 ms
1000 px/s1857 米280 ms400 ms520 ms
1350 px/s2857 米207 ms296 ms385 ms
1600 px/s3571 米175 ms250 ms325 ms

再放一把尺子:默认参数下起跳一次,人要离开地面 0.733 秒。

两个数一摆就清楚了。1000 米处的 280 px 间距只剩 400 毫秒,这 400 毫秒里还夹着一整次跳跃。

二、准备环境:在码道 Web 里把要求写死

浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwsf&sourcead=dmzntgwwbwz,登录后就能在对话窗口输入需求,不需要装软件。

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

需求我写得比较死,四块内容直接钉进去。

  • 参数面板:重力、跳跃初速、基础速度、每米增速、障碍最小间距、障碍最大间距,六个输入框都能现场改
  • 物理面板:最大跳跃高度与滞空时间,实测值和理论值并排,附相对误差
  • 自检面板:至少 20 条断言,把结果逐条渲染到页面上
  • 三种障碍:绿色矮箱直接跳过,蓝色中箱要算起跳时机,红白高梁必须滑铲钻过去

最值钱的是第三条。自检和游戏共用同一套公式,页面上量出来的数字才算数。

三、12 组跳跃实测:离散积分比理论低 2% 到 8%

我把重力和初速拆成 12 组,每组都走游戏自己的起跳与逐帧积分,再和解析公式对一遍。

重力初速最高点实测理论 v²/2g误差滞空实测理论 2v/g误差
2400900161.3 px168.75 px4.40%0.7333 s0.7500 s2.22%
240070096.3 px102.08 px5.63%0.5667 s0.5833 s2.86%
24001200290.0 px300.00 px3.33%0.9833 s1.0000 s1.67%
240050048.0 px52.08 px7.84%0.4000 s0.4167 s4.00%
24001600520.0 px533.33 px2.50%1.3167 s1.3333 s1.25%
1600900245.7 px253.13 px2.95%1.1167 s1.1250 s0.74%
3200900119.1 px126.56 px5.89%0.5500 s0.5625 s2.22%
1200900330.0 px337.50 px2.22%1.4833 s1.5000 s1.11%
3600900105.0 px112.50 px6.67%0.4833 s0.5000 s3.33%
1000600175.0 px180.00 px2.78%1.1833 s1.2000 s1.39%
50001400184.4 px196.00 px5.90%0.5500 s0.5600 s1.79%
40001000116.7 px125.00 px6.67%0.4833 s0.5000 s3.33%

12 组的实测高度全部低于理论值,滞空也一样。差值来自积分方式:每帧先加速度、再走位移,属于半隐式欧拉,永远摸不到解析上界。

初速越大、重力越小,误差越小,反方向就放大。初速 1600 那一组误差只有 2.50%,重力 4000 那一组是 6.67%。

四、快速下落:把 0.733 秒的滞空压到 0.2 秒

空中按 ↓,下落速度会被直接顶到 1400 px/s。

我在起跳后的第 N 帧按下 ↓,试了 7 组,记录这一跳的最高点和总滞空。

按 ↓ 的时机最高点总滞空落地用了几帧
不按(基准)161.3 px0.7333 s44
第 0 帧0.0 px0.0167 s1
第 2 帧28.0 px0.0667 s4
第 4 帧53.3 px0.1167 s7
第 8 帧96.0 px0.2000 s12
第 14 帧140.0 px0.3333 s20
第 20 帧160.0 px0.4500 s27
第 30 帧161.3 px0.6000 s36

这张表解释了「跳完接着钻高梁」为什么成立:跳一个矮障碍只要 0.05 秒左右的滞空,落地后还剩大半秒可以滑铲。

第 14 帧那行是分水岭。等到从最高点开始下落再按 ↓,滞空只压到 0.333 秒,省不了多少。想省时间就得早按,代价是高度,矮障碍够用,中箱可能就撞上去。

五、跑满 1000 米要多久:模拟与解析差 0.2% 以内

我把 12 组速度参数各跑一遍:逐帧累加算模拟耗时,公式算解析耗时。

起步速度每米增速1000 米处速度模拟耗时解析耗时误差
3500350 px/s142.867 s142.857 s0.0067%
3500.35700 px/s99.033 s99.021 s0.0124%
35011350 px/s67.517 s67.496 s0.0301%
35022350 px/s47.633 s47.606 s0.0575%
2000.35550 px/s144.533 s144.514 s0.0131%
5000.35850 px/s75.817 s75.804 s0.0167%
7000.351050 px/s57.933 s57.924 s0.0168%
10000.351350 px/s42.883 s42.872 s0.0262%
15000.351850 px/s29.967 s29.960 s0.0220%
35055350 px/s27.300 s27.269 s0.1130%
3501010350 px/s16.967 s16.934 s0.1927%
6001.52100 px/s41.783 s41.759 s0.0588%

12 组误差全部小于 0.2%,并且随增速单调变大。增速越大,最后一帧的余量占比越高。

默认参数跑满 1000 米要 99 秒。想压成一局 30 秒的短跑,把每米增速调到 5 就行。

六、障碍间距:三档区间各生成 2000 次

间距区间实测最小实测最大均值越界次数去重后的取值
280 ~ 520 px280.127 px519.967 px398.46 px01994
400 ~ 800 px400.138 px799.663 px596.87 px01997
700 ~ 1200 px700.196 px1199.869 px947.62 px01995

每档 2000 次采样,越界 0 次,去重后仍有近 2000 个不同取值,说明随机是均匀铺开的,不是少数几个档位。

生成函数的写法很好懂:间距 = 最小间距 + 随机数 × 区间宽度。真正需要防的是另一头。

最小间距填得比最大间距大时,面板会拒绝这次输入,并保留上一个有效值。

两个提示的原文是「minGap(1200) 不能大于当前 maxGap(520),已保留上一有效值 280」和「abc 不是合法数字,已保留上一有效值 2400」。

七、脚本玩家 25 局:默认参数中位 848 米

我写了一个脚本玩家,规则只有三条:遇到矮箱和中箱先算弧线,算得出就在撞上之前起跳;遇到高梁滑铲;空中发现落地来不及就按快速下落。

四档增速配三档间距,每组各跑 25 局。

每米增速间距 280~520间距 400~800间距 700~1200
03000 米(25/25 触顶)3000 米(25/25 触顶)3000 米(25/25 触顶)
0.35848 米(661~1012)1667 米(1392~2030)3000 米(21/25 触顶,最低 2759)
1346 米(233~475)671 米(485~873)1262 米(1044~1441)
2205 米(159~322)374 米(285~528)669 米(562~775)

括号里是中位之外的最小值与最大值。

增速为 0 的三组全部跑满测试上限,一局没死。默认增速下间距放宽一档,中位从 848 米涨到 1667 米,再放宽一档就触顶了。

把每米增速从 0.35 提到 2,其他参数不动,中位从 848 米掉到 205 米。

死因统计更直白:默认那组 25 局里有 21 局,撞击发生在人还在空中的瞬间,也就是「还没落地,后一个障碍已经追上来」。

八、两障碍简化扫描:最小可过间距随速度涨

我把场景压到极简:只有两个障碍,中间固定一个间距,速度恒定。

恒定速度矮箱接高梁中箱接高梁
350 px/s80 px80 px
500 px/s80 px80 px
700 px/s80 px100 px
900 px/s120 px120 px
1200 px/s200 px200 px
1600 px/s240 px260 px

350 和 500 那两档,80 px 就能过。两个障碍几乎贴在一起时,脚本玩家靠「跳过第一个、立刻按快速下落」硬挤过去,第四节那张表就是它的底气。

1200 px/s 之后不行了。矮箱接高梁的最小可过间距涨到 200 px,1600 px/s 时要 240 到 260 px。

再对照第七节的默认间距下限 280 px:到 1600 px/s 附近,随机出来的最短间距已经贴住这个下限,过不过得去开始靠运气。

九、验证与踩坑

页面自检在加载时跑完,26 条全过。我把这 26 条逐条读过,也在上面补了几轮真跑。

第一处坑是我自己挖的。脚本玩家第一版把「已经越过」判断错了,只比了障碍的左边缘和玩家身体前缘,结果人还压在障碍上就触发了快速下落,一头撞在矮箱顶上。

那批失败很好认:死因全是「空中撞上矮箱」,而不是高梁。改成比对碰撞盒后缘之后,同样的参数下中位从 443 米涨到 848 米。

第二处坑在页面自检自己身上。第 1 条断言要求实测高度与理论值的相对误差小于 6%,可离散积分的误差本来就随重力和初速变化。

我把重力从 100 一路扫到 10000,每 100 一组:从 3300 开始误差就过 6%,这一条断言会自己判失败。

面板允许重力填到 10000。也就是说,读者完全可以在合法范围内调出一组参数,让页面自己判自己不合格。

第三处是个设计判断,不算缺陷。碰撞盒比看起来小一圈,站立时是 34 × 62 px,滑铲时压到 34 × 28 px,高梁下面的净空是 60 px。

宽容度给得偏大,钻梁实际上比看上去轻松。如果你想让它更难,把净空改成 40 px 就够了。

十、本地复现:两条命令加一段控制台脚本

把页面存成 index.html,起一个静态服务器就能跑。

python-m http.server 8000# 浏览器打开 http://127.0.0.1:8000/index.html

想自己复现第三节的 12 组跳跃,把下面这段粘进浏览器控制台。

functionprobe(g,v0){params.gravity=g;params.jumpVelocity=v0;resetGame();obstacles.length=0;lastRight=1e9;lastType=null;state='running';player.y=GROUND-STAND_H;player.h=STAND_H;player.vy=0;player.onGround=true;startJump(player);lett=0,maxH=0;while(!player.onGround&&t<3){step(1/60);t+=1/60;maxH=Math.max(maxH,GROUND-(player.y+player.h));}constth=theoJump();return{g,v0,maxH:+maxH.toFixed(2),th:+th.h.toFixed(2),err:+((Math.abs(maxH-th.h)/th.h)*100).toFixed(2)};}[2400,3200,4000].forEach(g=>console.log(probe(g,900)));

这些函数跑在页面脚本的顶层作用域,控制台里可以直接调,量到的就是我表格里那些数。

十一、直接下载试玩

在线案例与源码都在这里:https://atomgit.com/deli007/demo_park/tree/main/codearts-endless-runner-game

目录里有单文件 index.html 和 5 张截图。双击 index.html 就能玩,改右边那六个输入框,物理面板和游戏行为会立刻跟着变。

十二、使用码道的体会

需求里写清楚「面板 + 自检渲染到页面」,一轮对话就能拿到能自证的产物。这次我只提了一轮需求,生成的页面就带着 26 条断言和两块实测面板。

用中文写物理量比写画面有用。把重力、初速、滞空、碰撞盒这些词直接写进需求,出来的代码里就能找到对应的常量和函数。

它给的默认参数偏激进。每米 +0.35 的增速看着温和,实测到 1000 米处时间窗只剩 400 毫秒。这个结论是我自己跑出来才敢下的。

十三、小结与下一步

三句话收尾。

跳跃的实测值永远低于解析上界,2% 到 8% 的差额是积分方式的固有代价,不是写错了。

0.733 秒的滞空是这游戏难度的基准尺。任何间距和速度的组合,都要按「时间窗够不够落地」来反推。

按实测数据,默认的 280 px 最小间距在 1600 px/s 附近就贴住极限了。

下一步我会做三件事:把默认最小间距从 280 提到 400;给高梁单独拉长与上一个障碍的距离;把自检第 1 条的固定百分比阈值改成按帧长归一化。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进