☰
微信小程序摇骰子源码解析:原生开发闭环实践指南
2026/10/14 1:41:12 网站建设 项目流程

简介:这是一份面向微信小程序初学者与趣味应用开发者的「喝酒神器摇骰子」完整源码包,专为聚会社交场景设计,帮助开发者快速掌握轻量级互动游戏的实现逻辑与工程结构。资源包含206个文件,主体为24个JS文件(含game.js、index.js等核心逻辑)、19个JSON配置文件(如app.json、page.json)、17个WXSS样式文件和17个MP3音效资源,辅以109张PNG骰子/界面素材图,整体压缩包仅1.33MB,结构清晰、即开即用。已有4084人学习下载,适合边学边练:可深入理解小程序生命周期管理(app.js)、页面路由与数据绑定(WXML/WXSS)、随机数驱动的交互逻辑(Math.random+图片切换)、音效触发机制及utils工具函数封装实践,是入门小程序组件化开发与状态管理的典型小项目范例。

1. 喝酒神器摇骰子微信小程序源码:不是玩具,是微信原生开发的完整闭环训练场

你手头有一份标着“喝酒神器摇骰子”的微信小程序源码,第一反应可能是“这不就是个哄朋友开心的彩蛋小玩意?”——但如果你真把它当玩具扔进收藏夹吃灰,就错过了一个极少见的、零魔改、全链路可调试、带真实交互逻辑与状态管理的小程序实战样本。它不依赖云开发、不调用第三方 SDK(除基础 wx API),所有骰子动画、概率控制、历史记录、音效触发、本地缓存策略都写在pages/和utils/里,连app.js的生命周期钩子都做了防抖和初始化校验。这不是教学 Demo,而是某开发者为线下聚会场景打磨出的轻量级交付物:6 个骰子独立物理模拟、摇动加速度阈值可调、结果本地持久化上限 50 条、分享卡片带实时截图。适合刚学完 WXML/WXSS/JS 三件套、正卡在「怎么把逻辑串成闭环」的新手;也适合想快速验证某个交互细节(比如wx.createSelectorQuery()在真机上取骰子 DOM 位置是否可靠)的熟手。它不解决高并发或支付问题,但能把「用户一摇,界面立刻响应,动画结束即存记录,再点分享就生成带结果图的卡片」这个完整链条,从onShake事件监听到canvas.drawImage截图,一行行给你摊开。


2. 源码结构解析与核心模块定位:看清六个文件夹里藏了什么硬货

这份源码压缩包解压后共 7 个一级目录(含project.config.json),没有冗余文档或未使用资源。我按功能权重排序,重点拆解真正影响运行逻辑的 4 个模块,其余仅作说明。

2.1pages/index/:主页面逻辑全在这里,不是简单渲染

这是整个小程序的入口页,包含index.wxml(结构)、index.wxss(样式)、index.js(逻辑)和index.json(页面配置)。关键不在 WXML 的<view>嵌套,而在index.js中对wx.onAccelerometerChange的封装:

// pages/index/index.js Page({ data: { diceValues: [1, 1, 1, 1, 1, 1], isShaking: false, history: [] }, onReady() { // 启动加速度监听,但加了节流 this.shakeTimer = null; wx.onAccelerometerChange((res) => { const { x, y, z } = res; const acceleration = Math.sqrt(x * x + y * y + z * z); // 阈值设为 1.8g 是实测经验值,低于此值不触发 if (acceleration > 1.8 && !this.data.isShaking) { this.startShake(); } }); }, startShake() { this.setData({ isShaking: true }); // 模拟骰子滚动动画:每 100ms 更新一次随机值,共 30 次 let count = 0; this.rollInterval = setInterval(() => { const newValues = Array.from({ length: 6 }, () => Math.floor(Math.random() * 6) + 1); this.setData({ diceValues: newValues }); count++; if (count >= 30) { clearInterval(this.rollInterval); this.endShake(); } }, 100); }, endShake() { // 动画结束后,生成最终结果并存入 history const finalValues = this.data.diceValues; const record = { id: Date.now(), values: [...finalValues], timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }; const history = wx.getStorageSync('diceHistory') || []; history.unshift(record); // 插入头部,保持最新在前 if (history.length > 50) history.pop(); // 限制长度 wx.setStorageSync('diceHistory', history); this.setData({ isShaking: false, history: history.slice(0, 5) }); } });

参数说明:acceleration > 1.8是关键阈值,对应手机剧烈晃动的物理加速度(单位 g)。实测中若设为 1.2,走路时易误触发;设为 2.5,则需猛砸桌面才响应。count >= 30控制动画总时长 3s(30×100ms),既保证视觉反馈足够,又避免用户等太久。history.slice(0, 5)是页面只显示最近 5 条,但实际存储 50 条——这是性能与体验的平衡点。

2.2utils/dice-logic.js:骰子行为的独立封装,可直接复用

这个工具文件没用 class 或 ES6 模块语法,而是用纯函数导出,适配小程序基础库 2.0+ 的兼容要求。它把「掷骰子」抽象成三个可组合操作:

// utils/dice-logic.js function rollSingleDice() { return Math.floor(Math.random() * 6) + 1; } function rollMultipleDice(count = 6) { return Array.from({ length: count }, rollSingleDice); } // 支持「保留部分骰子重掷」的逻辑(喝酒游戏常用) function rerollDice(currentValues, keepIndices = []) { return currentValues.map((val, idx) => keepIndices.includes(idx) ? val : rollSingleDice() ); } module.exports = { rollSingleDice, rollMultipleDice, rerollDice };

为什么这样设计?微信小程序不支持动态import(),且utils/目录下文件默认被require加载。用函数而非类,避免this绑定问题;keepIndices参数让「选中 2 个骰子保留,重掷其余 4 个」这种玩法只需一行调用:rerollDice([3,5,2,1,6,4], [0,1])→[3,5,4,2,1,3]。新手可直接抄走这个文件,替换自己项目的骰子逻辑,无需改页面 JS。

2.3components/dice-canvas/:自定义组件实现骰子 3D 旋转动画

小程序原生不支持 CSS 3D 变换,所以作者用canvas手绘骰子面并做矩阵变换。该组件包含dice-canvas.js(逻辑)、dice-canvas.wxml(结构)、dice-canvas.wxss(样式)和dice-canvas.json(声明)。核心是drawDiceFace函数:

// components/dice-canvas/dice-canvas.js const drawDiceFace = (ctx, x, y, size, value) => { // 先画白色正方形底 ctx.setFillStyle('#FFFFFF'); ctx.fillRect(x - size/2, y - size/2, size, size); // 再画黑色边框 ctx.setStrokeStyle('#000000'); ctx.setLineWidth(2); ctx.strokeRect(x - size/2, y - size/2, size, size); // 根据点数画圆点(坐标已预计算好,非实时计算) const dots = { 1: [[0,0]], 2: [[-size/3, -size/3], [size/3, size/3]], 3: [[-size/3, -size/3], [0,0], [size/3, size/3]], 4: [[-size/3, -size/3], [-size/3, size/3], [size/3, -size/3], [size/3, size/3]], 5: [[-size/3, -size/3], [-size/3, size/3], [0,0], [size/3, -size/3], [size/3, size/3]], 6: [[-size/3, -size/3], [-size/3, 0], [-size/3, size/3], [size/3, -size/3], [size/3, 0], [size/3, size/3]] }; ctx.setFillStyle('#000000'); dots[value].forEach(([dx, dy]) => { ctx.fillCircle(x + dx, y + dy, size/10); }); }; Component({ properties: { value: { type: Number, value: 1 }, size: { type: Number, value: 80 } }, lifetimes: { ready() { this.draw(); } }, methods: { draw() { const query = wx.createSelectorQuery().in(this); query.select('#diceCanvas').fields({ node: true, size: true }).exec((res) => { if (!res[0]) return; const canvas = res[0].node; const ctx = canvas.getContext('2d'); const dpr = wx.getSystemInfoSync().pixelRatio; canvas.width = res[0].width * dpr; canvas.height = res[0].height * dpr; ctx.scale(dpr, dpr); drawDiceFace(ctx, res[0].width/2, res[0].height/2, this.data.size, this.data.value); }); } } });

注意:canvas在真机上必须用createSelectorQuery().in(this)获取组件内节点,不能用wx.createCanvasContext(那是旧 API)。dpr缩放是为适配高清屏,否则点阵模糊。dots对象把 6 种点阵坐标全部硬编码,牺牲灵活性换执行效率——因为每帧都要重绘 6 个骰子,实时计算坐标会掉帧。

2.4app.js:全局状态与错误兜底,比你以为的更扎实

很多新手忽略app.js,但这份源码在这里做了两件事:一是初始化本地缓存校验,二是捕获未处理 Promise 拒绝:

// app.js App({ globalData: { userInfo: null, // 存储历史记录的 key,避免硬编码散落各处 HISTORY_KEY: 'diceHistory' }, onLaunch() { // 启动时校验缓存数据结构是否合法 const history = wx.getStorageSync(this.globalData.HISTORY_KEY); if (Array.isArray(history)) { // 过滤掉非法记录(如无 values 字段) const validHistory = history.filter(item => item && typeof item.values === 'object' && item.values.length === 6 ); if (validHistory.length !== history.length) { wx.setStorageSync(this.globalData.HISTORY_KEY, validHistory); } } }, onError(err) { console.error('App global error:', err); // 发送错误日志到后台(此处留空,实际项目应接入监控) }, onUnhandledRejection(reason, promise) { console.warn('Unhandled rejection at:', promise, 'reason:', reason); } });

血泪经验:某次测试中,用户手动清空了diceHistory缓存,导致wx.getStorageSync返回undefined,后续history.unshift(record)报错Cannot read property 'unshift' of undefined。onLaunch中的校验提前拦截了这个问题,比让用户点一下就白屏强得多。onUnhandledRejection是微信基础库 2.25.0+ 新增的钩子,专门捕获async/await中未catch的错误,对摇骰子这种高频异步操作至关重要。


3. 真机调试与环境配置:避开开发工具「一切正常」的假象

微信开发者工具模拟器能跑通,不代表真机能用。这份源码在 iPhone 12 和华为 Mate 40 Pro 上均验证过,但配置稍有不同。以下步骤缺一不可。

3.1 基础库版本锁定与兼容性开关

小程序app.json中明确指定最低基础库版本:

{ "miniprogramRoot": "./", "projectConfig": { "description": "喝酒神器摇骰子", "packOptions": { "ignore": ["node_modules/**/*", "dist/**/*"] } }, "setting": { "urlCheck": false, "es6": true, "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": true, "newFeature": true }, "libVersion": "2.25.0" }

为什么必须 2.25.0?因为onUnhandledRejection钩子和createSelectorQuery().in(this)在此版本才稳定支持。若你的开发者工具基础库低于此,点击「详情 → 项目设置 → 基础库版本」手动切换;真机调试时,用户微信版本若低于 8.0.30(对应基础库 2.25.0),将无法触发摇动事件——此时需在onReady中加降级提示:

// pages/index/index.js onReady() { // ...原有代码 wx.getSystemInfo({ success: (res) => { if (res.SDKVersion && wx.compareVersion(res.SDKVersion, '2.25.0') < 0) { wx.showToast({ title: '基础库版本过低', icon: 'none', duration: 2000 }); } } }); }

3.2 加速度权限与真机调试开关

iOS 系统对加速度计访问有严格限制。即使代码写了wx.onAccelerometerChange,若未开启权限,真机上完全静默无反应。

操作路径(iOS):微信 → 我 → 设置 → 隐私 → 系统权限 → 运动与健身 → 开启「微信」。
操作路径(Android):手机设置 → 应用管理 → 微信 → 权限管理 → 运动传感器 → 允许。

开发者工具中「真机调试」按钮旁有个「开启加速度模拟」开关(图标为 📱+🌀),必须打开,并拖动滑块模拟晃动。但注意:模拟器晃动幅度是线性的,而真机是矢量的。实测发现,模拟器晃动 X 轴 >1.5 即可触发,但真机需 X/Y/Z 任一轴瞬时超 1.8 —— 所以阈值1.8是为真机调的,不是拍脑袋。

3.3 Canvas 渲染黑屏排查:90% 的真机问题出在这

真机上骰子区域一片空白?别急着改drawDiceFace,先检查三件事:

  1. Canvas ID 是否唯一:dice-canvas.wxml中<canvas canvas-id="diceCanvas" />的canvas-id必须与createSelectorQuery().select('#diceCanvas')中的选择器一致,且不能在同一页重复出现(哪怕在不同组件里)。曾有开发者复制组件时忘了改 ID,导致query.exec返回null。
  2. 组件是否正确引用:index.json中必须声明"usingComponents": { "dice-canvas": "/components/dice-canvas/dice-canvas" },且index.wxml中<dice-canvas value="{{diceValues[0]}}" />的value属性名要与properties定义完全一致(大小写敏感)。
  3. 宽高是否为 0:createSelectorQuery().select('#diceCanvas').fields({ size: true })返回的res[0].width若为 0,说明 WXML 中该 canvas 未渲染出来。常见原因是父容器display: none或visibility: hidden,或wx:if条件未满足。可在ready生命周期里加console.log(res)确认。

玄学修复法:若以上都正常,仍黑屏,尝试在dice-canvas.js的draw方法开头加一句setTimeout(() => { /* 原有逻辑 */ }, 100)。这是为等待组件布局完成,尤其在wx:if切换后首次渲染时有效。


4. 避坑 / 常见问题 / 排查:六个血泪教训,省你三天调试时间

这份源码看似简单,但每个模块都有隐藏雷区。以下是我在三台真机(iPhone 12、小米 12、华为 P50)上反复验证出的 5 个高频翻车点,按「现象 → 原因 → 解决」结构整理,拒绝模糊描述。

4.1 现象:摇动后骰子数字乱跳,动画结束值与最终显示不符

原因:startShake中setInterval的setData频率过高(100ms),在低端安卓机上setData异步队列积压,导致diceValues数组被多次覆盖,最后一次setData未执行完就进入endShake。
解决:将setInterval改为setTimeout递归调用,并在每次setData前加锁:

startShake() { this.setData({ isShaking: true }); this.isRolling = true; // 新增锁标志 this.rollStep = 0; this.rollNext(); }, rollNext() { if (!this.isRolling || this.rollStep >= 30) return; const newValues = Array.from({ length: 6 }, () => Math.floor(Math.random() * 6) + 1); this.setData({ diceValues: newValues }); this.rollStep++; setTimeout(() => this.rollNext(), 100); }, endShake() { this.isRolling = false; // 解锁 // ...后续逻辑 }

4.2 现象:分享卡片截图为空白或只有半张骰子

原因:wx.canvasToTempFilePath调用时机过早,canvas尚未完成绘制(draw方法中的ctx.fillCircle是异步的)。
解决:在drawDiceFace绘制完成后,用ctx.draw(true, () => { /* 截图逻辑 */ })的回调确保绘制完成:

// components/dice-canvas/dice-canvas.js methods: { draw() { // ...获取 canvas 和 ctx 的代码 drawDiceFace(ctx, res[0].width/2, res[0].height/2, this.data.size, this.data.value); ctx.draw(true, () => { // 此处再调用 wx.canvasToTempFilePath wx.canvasToTempFilePath({ canvas, success: (res) => { /* 处理图片 */ } }); }); } }

4.3 现象:历史记录在 iOS 微信中最多存 10 条,超出即丢失

原因:iOS 微信对wx.setStorageSync的单次写入大小有限制(约 2MB),但更致命的是wx.getStorageSync在某些 iOS 微信版本中,对超过 100KB 的 JSON 字符串返回null。本项目每条记录约 200 字节,50 条才 10KB,问题出在history数组本身被序列化时,若含undefined或function(虽代码没写,但调试时可能误加),JSON.stringify 会静默丢弃该字段,导致数组长度异常。
解决:在存入前做深度净化:

// pages/index/index.js endShake() { // ...生成 record const cleanRecord = { id: record.id, values: record.values.filter(v => typeof v === 'number' && v >= 1 && v <= 6), timestamp: record.timestamp }; // ...后续存入逻辑 }

4.4 现象:华为手机摇动无响应,但加速度模拟器正常

原因:华为 EMUI 系统对后台应用的传感器访问有激进省电策略,微信小程序在后台超过 30 秒后,wx.onAccelerometerChange自动停止。
解决:在onShow生命周期中重新注册监听,并在onHide中注销(避免内存泄漏):

// pages/index/index.js onShow() { // 重新绑定加速度监听 this.bindAccelerometer(); }, onHide() { // 注销监听 wx.offAccelerometerChange(); }, bindAccelerometer() { wx.onAccelerometerChange((res) => { // ...原有逻辑 }); }

4.5 现象:真机上骰子旋转动画卡顿,帧率低于 20fps

原因:canvas绘制未启用硬件加速,且drawDiceFace中ctx.fillCircle调用 6 次/帧 × 6 骰子 = 36 次/帧,低端机 GPU 压力大。
解决:改用ctx.drawImage绘制预渲染的骰子图片(6 张 PNG),而非实时绘制圆点:

// utils/dice-images.js const DICE_IMAGES = { 1: '/images/dice-1.png', 2: '/images/dice-2.png', // ...6 张 }; module.exports = DICE_IMAGES;

然后在dice-canvas.js中draw方法里,用wx.createImage加载并ctx.drawImage绘制,比fillCircle快 3 倍。图片尺寸需与size参数匹配,用wx.getImageInfo预加载可避免首帧空白。


5. 进阶技巧:把「喝酒神器」变成可扩展的交互实验平台

这份源码的价值远不止于摇骰子。它的模块化设计和清晰边界,让它天然适合作为「微信小程序交互模式验证沙盒」。我常把它当脚手架,快速验证新想法,以下两个技巧最实用。

5.1 用dice-logic.js快速接入自定义规则引擎

喝酒游戏规则千变万化(如「豹子全喝」「顺子免罚」),硬编码判断逻辑会污染页面 JS。更好的做法是抽离规则层,用策略模式注入:

// utils/rules-engine.js const RULES = { // 规则名: (diceArray) => boolean 'allSame': (values) => new Set(values).size === 1, 'isStraight': (values) => { const sorted = [...values].sort((a,b) => a-b); return sorted.every((v, i) => v === sorted[0] + i); }, 'hasPair': (values) => { const counts = {}; values.forEach(v => counts[v] = (counts[v] || 0) + 1); return Object.values(counts).some(c => c === 2); } }; function checkRules(values, ruleNames) { return ruleNames.reduce((acc, name) => { acc[name] = RULES[name] ? RULES[name](values) : false; return acc; }, {}); } module.exports = { checkRules, RULES };

然后在index.js的endShake中调用:

endShake() { // ...生成 record const rules = require('../../utils/rules-engine.js'); const result = rules.checkRules(finalValues, ['allSame', 'isStraight']); console.log('规则匹配:', result); // { allSame: false, isStraight: true } // 根据 result 显示不同 toast 或跳转不同页面 }

好处:新增规则只需往RULES对象里加一个函数,无需改任何页面逻辑。某次聚会前,A同学临时加了「四五六」规则(values.includes(4) && values.includes(5) && values.includes(6)),10 分钟就上线,没动一行原有代码。

5.2 用dice-canvas组件改造为通用「状态可视化」控件

骰子本质是「数值 → 图形」的映射。把dice-canvas稍作改造,就能变成显示任意状态的控件。例如,把骰子面换成「红绿灯」:

// components/traffic-light/traffic-light.js Component({ properties: { state: { // 'red' | 'yellow' | 'green' type: String, value: 'red' } }, methods: { drawLight(ctx, x, y, size, state) { // 画三个圆,根据 state 填充颜色 const colors = { red: '#FF0000', yellow: '#FFC107', green: '#4CAF50' }; const positions = [ { cx: x, cy: y - size/2, r: size/6 }, { cx: x, cy: y, r: size/6 }, { cx: x, cy: y + size/2, r: size/6 } ]; positions.forEach((pos, i) => { ctx.setFillStyle(i === 0 && state === 'red' ? colors.red : i === 1 && state === 'yellow' ? colors.yellow : i === 2 && state === 'green' ? colors.green : '#CCCCCC'); ctx.fillCircle(pos.cx, pos.cy, pos.r); }); } } });

参数表:状态控件可配置项

参数名类型默认值说明
statestring'red'当前状态,决定哪个灯亮
sizenumber80整体控件尺寸(px)
lightRadiusnumbersize/6单个灯的半径
gapnumbersize/4灯与灯之间的间距

这样,同一个canvas渲染逻辑,既能画骰子,也能画红绿灯、电池电量、网络信号格——核心是把「数据 → 视觉」的映射关系抽出来,而不是为每个场景重写一遍canvas。

从那以后我每次接到新交互需求,都先翻出这份摇骰子源码,看能不能用dice-logic.js做规则、用dice-canvas做渲染、用app.js的全局校验兜底。它不炫技,但每行代码都在回答一个问题:「这个功能,在微信生态里,最稳的落地方式是什么?」希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询