遇事不决,掷一掷

2025-08-04 Web

基于 Three.js 的 3D 骰子决策小游戏,支持自定义骰子文字。

项目简介

这是一个有趣的 3D 骰子决策小游戏。当你面临选择困难时,不妨让骰子帮你做决定。

项目使用 Three.js 实现了真实的 3D 骰子物理效果,包括抛掷、旋转、弹跳和落地动画。骰子的六个面可以自定义文字内容,让你可以根据不同场景定制决策选项。

游戏界面

游戏界面

核心特性

🎲 真实物理模拟

骰子动画分为三个阶段,模拟真实的物理效果:

  1. 抛起阶段(0-50%):骰子从桌面抛起,快速随机旋转
  2. 下落阶段(50-80%):旋转逐渐减速,向最终角度过渡
  3. 着陆阶段(80-100%):小幅弹跳后平稳落地
// 阶段1: 抛起和快速旋转
if (progress < 0.5) {
	const rotationSpeed = Math.sin(progress * Math.PI) * 1.2 + 0.8;
	const rotationProgress = progress * 2 * rotationSpeed;

	this.dice.rotation.x = initialRotation.x + totalRotation.x * rotationProgress;
	this.dice.rotation.y = initialRotation.y + totalRotation.y * rotationProgress;
	this.dice.rotation.z = initialRotation.z + totalRotation.z * rotationProgress;

	// 从桌面抛起
	const bounceHeight = 0.9 + 2.5 * Math.sin(progress * Math.PI);
	this.dice.position.y = bounceHeight;
}

🎨 自定义骰子文字

支持自定义骰子六个面的文字内容,可以输入任意长度的文本(骰子上显示前两个字符):

createTextTexture(text, color = '#000000') {
    const displayText = text.substring(0, 2);
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    // 绘制背景和边框
    context.fillStyle = '#ffffff';
    context.fillRect(0, 0, size, size);

    // 绘制文字
    context.fillStyle = color;
    context.font = 'bold 100px Arial, "Microsoft YaHei"';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(displayText, size / 2, size / 2);

    return new THREE.CanvasTexture(canvas);
}

📱 移动端优化

  • 支持向上滑动手势触发掷骰子
  • 响应式布局,适配不同屏幕尺寸
  • 横屏模式自动调整布局
/* 移动端适配 */
@media (max-width: 414px) {
	.game-container {
		max-width: 100%;
		padding: 15px;
	}

	#scene-container {
		height: 250px;
	}
}

/* 横屏模式 */
@media (orientation: landscape) and (max-height: 600px) {
	.game-container {
		flex-direction: row;
		flex-wrap: wrap;
	}
}

技术实现

Three.js 场景搭建

使用 Three.js 创建 3D 场景,包括相机、渲染器、光源和桌面:

createScene() {
    // 创建场景
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0x000020);

    // 创建相机 - 从斜上方观察
    this.camera = new THREE.PerspectiveCamera(40, width / height, 0.1, 1000);
    this.camera.position.set(3, 5, 8);
    this.camera.lookAt(0, 0.9, 0);

    // 创建渲染器
    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    this.renderer.shadowMap.enabled = true;
}

骰子材质与纹理

使用 Canvas 动态生成骰子每个面的纹理,支持中文显示:

createDice() {
    const geometry = new THREE.BoxGeometry(1.8, 1.8, 1.8);

    // 为六个面创建不同的材质
    const materials = [
        new THREE.MeshLambertMaterial({
            map: this.createTextTexture(this.faceTexts[4], this.getRandomColor())
        }),
        // ... 其他五个面
    ];

    this.dice = new THREE.Mesh(geometry, materials);
    this.dice.castShadow = true;
    this.dice.position.set(0, 0.9, 0);
}

角度插值算法

确保骰子旋转时选择最短路径,避免不自然的旋转:

lerpAngle(startAngle, endAngle, t) {
    // 标准化角度到-PI到PI之间
    const normalizeAngle = (angle) => {
        while (angle > Math.PI) angle -= Math.PI * 2;
        while (angle < -Math.PI) angle += Math.PI * 2;
        return angle;
    };

    // 计算最短路径的角度差
    let delta = normalizeAngle(endAngle - startAngle);

    // 线性插值
    return startAngle + delta * t;
}

性能优化

  1. Canvas 纹理缓存:动态生成的文字纹理使用 CanvasTexture,避免重复计算
  2. 阴影优化:使用 PCFSoftShadowMap 实现柔和阴影,同时控制阴影贴图大小
  3. 像素比限制:限制渲染器的像素比,避免在高分辨率设备上过度渲染
  4. 事件节流:窗口 resize 事件使用节流,减少重复计算

使用体验

  • 点击"摇起来"按钮或向上滑动屏幕触发掷骰子
  • 在"骰子配置"面板中自定义六个面的文字
  • 点击"更新骰子"按钮应用新的配置
  • 骰子会随机旋转 2-3 秒后落地,显示最终结果

应用场景

  • 日常决策辅助(吃什么、去哪玩)
  • 团队活动抽签
  • 游戏随机事件
  • 趣味互动工具

技术栈

  • Three.js r128:3D 场景渲染
  • 原生 JavaScript:游戏逻辑实现
  • CSS3:响应式布局和动画效果
  • Canvas API:动态纹理生成

未来规划

  • 支持多个骰子同时投掷
  • 添加音效和震动反馈
  • 支持自定义骰子颜色和材质
  • 添加历史记录功能
  • 支持分享结果到社交平台

技术栈

标签