
本文详解如何修复无限递归滚动的骰子动画,添加手动停止机制,并将最终点数正确写入 SugarCube 的 State.variables,确保视觉效果与游戏逻辑完全一致。
本文详解如何修复无限递归滚动的骰子动画,添加手动停止机制,并将最终点数正确写入 sugarcube 的 `state.variables`,确保视觉效果与游戏逻辑完全一致。
在 SugarCube 2 中集成 CSS 骰子动画时,常见误区是将 setTimeout 错误地写为 setTimeout(rollDice(), 1000) —— 这会导致函数立即执行并返回 undefined,进而引发 Maximum call stack size exceeded 错误;而若直接保留 setTimeout(rollDice, 1000) 且无退出条件,则形成无限递归调用,骰子永不停止。
✅ 正确做法是:分离“动画循环”与“结果锁定”逻辑。动画阶段持续刷新 DOM 类名以模拟滚动;当用户触发停止(或设定固定滚动次数)后,终止定时器,并将最终随机值写入 SugarCube 变量系统。
以下是完整、可直接嵌入 .tw-passage 的解决方案:
用于 inference.sh 的 JavaScript/TypeScript SDK,可运行 AI 应用、构建代理、集成 150+ 模型。包名:@inferencesh/sdk(npm install),完整 TypeScript 支持。
<!-- HTML 结构(确保元素 ID 匹配) -->
<div id="dice1" class="die"></div>
<button id="startRollButton">开始掷骰</button>
<button id="stopRollButton">停止并确认</button>
<span id="resultDisplay">结果:等待中...</span>
<script>
// 初始化控制标志
let isRolling = false;
let currentRoll = 1;
// 掷骰主函数(不自动启动,由按钮触发)
window.rollDice = function () {
if (isRolling) return; // 防重复启动
isRolling = true;
const dieEl = document.getElementById('dice1');
const resultEl = document.getElementById('resultDisplay');
// 清除旧状态类
for (let i = 1; i <= 6; i++) {
dieEl.classList.remove(`show-${i}`);
}
// 启动动画循环
const rollInterval = setInterval(() => {
const roll = Math.floor(Math.random() * 6) + 1;
// 切换显示面(假设你已定义 .show-1 ~ .show-6 CSS)
for (let i = 1; i <= 6; i++) {
dieEl.classList.remove(`show-${i}`);
}
dieEl.classList.add(`show-${roll}`);
currentRoll = roll;
}, 150); // 每150ms刷新一次,营造滚动感
// 绑定停止按钮事件(仅绑定一次,避免重复监听)
document.getElementById('stopRollButton').onclick = function () {
clearInterval(rollInterval);
isRolling = false;
// ✅ 关键:将结果写入 SugarCube 变量系统
if (typeof SugarCube !== 'undefined' && SugarCube.State) {
SugarCube.State.variables.rollOutcome = currentRoll;
resultEl.textContent = `结果:${currentRoll}`;
console.log(`SugarCube 变量 rollOutcome 已设为 ${currentRoll}`);
} else {
resultEl.textContent = "警告:SugarCube 未就绪";
}
};
};
// 页面加载后自动绑定启动按钮(推荐放在 passage 开头)
document.addEventListener('DOMContentLoaded', function () {
const startBtn = document.getElementById('startRollButton');
if (startBtn) {
startBtn.onclick = window.rollDice;
}
});
</script>? 重要注意事项:
立即学习“前端免费学习笔记(深入)”;
- CSS 类名需提前定义:确保你的样式表中包含 .die.show-1 至 .die.show-6 的 3D 翻转规则(参考原 CodePen 的 transform/rotateY 实现);
- SugarCube 变量写入时机:必须在 clearInterval() 后、且确认 SugarCube.State 存在时再赋值,否则会报 Cannot set property 'rollOutcome' of undefined;
- 避免全局污染:currentRoll 作为闭包内变量更安全,比 window.currentRoll 更可靠;
- 增强健壮性:可在 stopRollButton 点击后禁用自身,防止重复提交。
? 扩展建议:如需支持多次掷骰,可将 rollOutcome 设为数组(State.variables.rollHistory.push(currentRoll)),或添加重置按钮清空历史。最终,该方案兼顾视觉流畅性、逻辑可控性与 SugarCube 生态兼容性,是 Twine 互动叙事中 dice mechanic 的生产级实践。

















