返回
玩法拆解 · 代码 · Demo
物理射击解谜

8 球台球:一杆击球背后的拖拽力度、碰撞物理与回合裁决

拆解这只 8 球台球的确定性设计:拖拽距离如何折叠成 18 档力度、全停统一结算如何保证规则可信、犯规与自由球如何维持回合公平,以及 AI 误差如何制造对局张力。文末附可试玩 demo。

0

玩法拆解

规则骨架
  • 标准 8 球:16 颗球(母球 + 1-15 号),纯色 1-7 与条纹 9-15 两组,8 号球最后收
  • 开球后首颗入袋球决定双方球组分配(groupsAssigned 由 null 写入)
  • 犯规三态:母球落袋、空杆未碰球、先碰对方球 → 对手获得自由球,手动放置母球
  • 清空己方组后合法打进 8 号获胜;提前进 8 或犯规进 8 直接判负
行为循环
  • 母球附近按下 → 拖拽决定方向与力度(拖拽距离映射 0-18 档)→ 松手击球
  • 球体逐帧物理:球-球碰撞、库边反弹、入袋检测,全部停稳后统一结算
  • 结算判定犯规 / 球组分配 / 入袋归属,决定续杆或换人;AI 思考 1-1.5 秒后自动回杆
  • 任一方合法收掉 8 号 → result 弹出胜负,PLAY AGAIN 一键重开
实现要点
  • 8-ball/pool-model 单模块承载物理与规则:球态数组、碰撞/入袋检测、犯规判定、胜负与 AI 击球
  • __OV.Games.EightBallPool.Game 场景私有渲染器:PIXI 绘制球台/球/球杆,把指针事件翻译成 shot.aim/drag/release
  • 击球链路修过真实竞态:LogicGraph 异步执行,aiming 标志的重置必须让位到宏任务,否则真实拖拽永远打不出杆
  • common-ui 管暂停/帮助按钮;状态机 boot → menu → gameplay ⇄ paused → result 由蓝图声明

关键代码

拖拽力度折叠成击球速度(8-ball/pool-model)
js· 15 行
球-球碰撞:先分离重叠再加法向冲量(8-ball/pool-model)
js· 23 行
玩法行为:本文属于「物理」母型用物理规则与可预期性制造玩法;核心是可解释与可控。
查看母型详解

Demo

点击 Play 开局;在母球附近按住拖动瞄准、松手击球,拖拽越远力度越大;先清空己方球组再打进 8 号球获胜,AI 会自动应战。

8 球台球:一杆击球背后的拖拽力度、碰撞物理与回合裁决 Demo
游戏加载中…

文章

8 球台球:一杆击球背后的拖拽力度、碰撞物理与回合裁决

一句话本质

8 球台球(8-Ball Pool 重建版)是一个物理母型的完整对战样本:玩家与 AI 在标准球桌上轮流击球,每次击球被压缩成「按下瞄准 → 拖拽定力度 → 松手出杆」一个手势,之后的碰撞、入袋、犯规与胜负全部交给物理模拟和规则结算。它把台球最难做的两件事——手感与规则可信度——拆给了两个模块分别承担。

行为循环

  1. 开局:菜单点 Play,15 颗目标球三角摆位、母球置于开球线(25% 台宽)后,玩家执开球。
  2. 瞄准:在母球 4 倍球半径内按下开始瞄准,拖拽方向即击球方向,拖拽距离线性映射力度(150 逻辑像素拖满 18 档)。
  3. 击球:松手且力度大于 0.5 才出杆,初速度 = 力度 × 180,物理模拟启动。
  4. 结算:球-球碰撞、库边反弹、入袋检测逐帧进行,全部停稳后统一判定——犯规、球组分配、入袋归属、回合切换。
  5. 轮转:未分球组时首颗入袋球定组;进球合法则续杆,否则换 AI;AI 思考 1-1.5 秒后带误差回杆。
  6. 终盘:清空己方组后合法收 8 号获胜;提前进 8 或犯规进 8 判负。result 弹窗给出 🏆 / 😞,PLAY AGAIN 一键重开。

文末的 demo 可以直接试玩这个完整循环。

原语标注

  • AimStart(x, y) – 母球附近按下开始瞄准(shot.aim)
  • AimDrag(dx, dy) – 拖拽更新角度与力度(shot.drag)
  • Shoot(angle, power) – 松手出杆(shot.release → takeShot)
  • SimulatePhysics() – 逐帧碰撞 / 反弹 / 入袋,直到全停
  • SettleTurn() – 犯规判定、球组分配、回合切换(balls.stopped 触发)
  • PlaceCueBall(x, y) – 自由球状态下手动放置母球(cueball.place)
  • AiShoot() – AI 计算角度与力度自动回杆
  • JudgeWinner() – 8 号球合法性裁决(game.over)

参数速查表

| 参数 | 说明 | 默认值 | 单位 | |------|------|--------|------| | 球台尺寸 | 逻辑台面宽 × 高 | 600 × 300 | 逻辑像素 | | 球半径 | 所有球统一 | 10 | 逻辑像素 | | 袋口数 | 四角袋 + 两中袋 | 6 | 个 | | 球数 | 母球 + 1–15 号 | 16 | 颗 | | 力度范围 | power | 0–18 | 档 | | 满力拖拽距离 | maxDrag | 150 | 逻辑像素 | | 出杆阈值 | 最低有效力度 | 0.5 | 档 | | 初速度映射 | speed = power × 180 | 最高 3240 | 逻辑像素/秒 | | 瞄准触发半径 | 按下点距母球上限 | 40(4 倍球半径) | 逻辑像素 | | 开球线 | 母球初始位置 | 25% 台宽 | – | | AI 思考时间 | 回杆前停顿 | 1–1.5 | 秒 |

设计取舍

  1. 拖拽方向即击球方向:真实球杆是「往后拉、往前打」,本作简化为「朝目标方向拖」——触屏上省掉一次方向反转的心智转换,代价是牺牲拉杆的拟真感。
  2. 力度由拖拽距离直接映射:不用蓄力条,短拖轻推、长拖爆杆,出杆没有等待;力度不足 0.5 视为取消,防误触。
  3. 全停统一结算替代逐球即时结算:所有球停稳后才判犯规、定归属、分胜负,规则只写一遍、顺序确定;代价是玩家必须等球停才能看到本杆评价,节奏上偏向「一杆一景」而非爽快连打。
  4. AI 带随机误差:AI 不做精确解算,瞄准加误差来模拟水平,让对局有来有回;当前只有单档强度,未暴露难度选择。
  5. 二级菜单收纳:主界面只留暂停与帮助两个按钮,音量滑块收进暂停菜单,球桌区域保持干净。

实现要点

  • 状态机:boot → menu → gameplay ⇄ paused → result,迁移事件为 game.start / game.pause / game.resume / game.over / game.restart,由蓝图 stateMachine 声明。
  • 事件流:shot.aim → shot.drag → shot.release(力度 > 0.5 才发)→ pool-model.takeShot;球全停触发回合结算,currentPlayer = 2 时 AI 自动回杆,形成完整轮转。
  • 数据模型:balls(16 颗球的位置/速度/入袋态)+ currentPlayer / 双方球组与入袋清单 + foul / ballInHand / placingCueBall + aiming / power / aimAngle,约 20 个键撑起全场。
  • 模块分工:8-ball/pool-model 单模块承载物理模拟、8 球规则与 AI 击球;__OV.Games.EightBallPool.Game 是场景私有渲染器——PIXI 画球台、球体与球杆,并把指针事件翻译成瞄准手势;common-ui 管暂停与帮助按钮。
  • 一次真实竞态修复:LogicGraph 的事件处理是异步的,渲染模块若在 emit shot.release 后同步重置 aiming,规则的 if (aiming == true) 会被抢先判否,真实拖拽永远打不出杆;改为宏任务兜底重置后才打通。脚本直发事件的 e2e 测不出这类问题,只有真实指针输入能暴露。
  • 运行时:PIXI 渲染 + v2-kernel 驱动 LogicGraph,整包导出为纯静态 demo,离线可玩;文末 iframe 即真实导出产物。

这一版的价值在于把「手感」和「规则」彻底解耦:手势只管产生角度与力度两个参数,物理与规则模块只看数据键——每层都可以单独替换或调参,而一杆击球的因果链始终清晰可验。