真正會翻滾的骰子:全新 3D 擲骰
棋盤上的骰子現在是真正的 3D 立方體,會蓄力、翻滾,並落在你的點數上——沒有假延遲,也照顧到減少動態效果的需求。

通俗版
以前擲骰子是平面的:點數只是翻過來而已。現在兩顆移動骰子——還有極速骰子——都是真正的 3D 立方體,會先下蹲、起跳,在空中翻滾,然後正面朝上落在桌面上。
精彩之處在這裡:骰子只在遊戲計算你的走法時持續旋轉,然後落下並停在你擲出的確切點數上。沒有刻意填充的假停頓——結果一準備好,骰子就落下。在網路流暢時,整個過程一眨眼就結束了。
- 你一點選擲骰子,立刻就有快速的蓄力動作,讓操作馬上有響應感。
- 在計算你的走法時,骰子會在空中真正翻滾——若伺服器較慢,它會自然延長,而不是顯示佔位畫面。
- 平穩落在你的實際點數上,骰子一停穩,你的棋子就開始移動。
- 極速骰子也加入其中,並且在兩次擲骰之間仍顯示一個不顯眼的「?」,讓你隨時知道它正在參與。

這純粹是視覺上的改動——規則、機率和棋盤的玩法都和以前完全一樣。只是骰子終於有了骰子的感覺。
技術細節
每顆骰子都是一個六面的 3D CSS 立方體,由一個小型狀態機驅動,依次經過 idle → windup → loop → settle → jitter → landed。朝向以 quaternion 儲存,因此我們可以繞任意軸旋轉,並在每一幀轉換成單個 CSS rotate3d();FACE_Q 將每個 1–6 的點數對映到能讓那一面點數朝前的朝向。
// apps/web/app/[locale]/game/[id]/dice3d.ts
// quaternion per face → one CSS rotate3d() at rest
const FACE_Q: Record<DieValue, Quat> = {
1: Q.identity,
2: Q.axis([0, 1, 0], +Math.PI / 2),
3: Q.axis([0, 1, 0], -Math.PI / 2),
4: Q.axis([1, 0, 0], +Math.PI / 2),
5: Q.axis([1, 0, 0], -Math.PI / 2),
6: Q.axis([1, 0, 0], Math.PI),
};擲骰採用樂觀更新:一次點選會遞增一個樂觀 nonce,從而啟動 180ms 的 windup 和一個無限的翻滾 loop。當權威的擲骰結果到達時(rollSeq 遞增),旋轉中的骰子會立即沿最短的向前旋轉停到 FACE_Q[value] 並落下——沒有最小旋轉下限,因此快速的響應就會帶來快速的擲骰。
// authoritative values arrive → settle now, no spin floor
onRollSeq(values) {
values.forEach((v, i) => {
if (controller[i].isSpinning) controller[i].setResult(v, arrival + i * 78);
else controller[i].startRoll(arrival), controller[i].setResult(v, /* after windup */);
});
}根據所有者的決定,移交方案中逐漸衰減的落地彈跳被取消了(restitution 為 0),因此停穩過程是一次平滑加速的下落,精確觸及點數,只留下短暫的阻尼晃動。一個共享的 rAF loop 僅在有骰子正在動畫時執行,每個階段都使用 performance.now() 的絕對時間戳(這樣後臺標籤頁能正確快進),若沒有擲骰結果到達,則有一個兜底機制停到 store 中的當前點數。減少動態效果會完全跳過樂觀 nonce,只是切換骰面。僅限網頁端:引擎、daemon 和資料庫均未改動。