ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

拆解中秋知识竞赛H5源码:前端游戏开发与部署实战

拆解中秋知识竞赛H5源码:前端游戏开发与部署实战 简介这是一份面向HTML5初学者的中秋知识竞赛游戏源码属于前端游戏开发练习型项目适合学习并复现已有的答题互动场景。资源共90个文件压缩包约8.8MB其中77张PNG和5张JPG构成界面与题目图片素材3个JavaScript文件负责答题逻辑与交互控制1个HTML页面承载游戏结构1个MP3文件提供背景音乐另附1个Python脚本可用于题目图片的批量格式化处理。代码覆盖Canvas绘图、事件监听、本地存储、音频控制等HTML5常用API并将答题判断、得分反馈、题目切换和背景音乐串联为完整流程。通过阅读源码可以理解答题类小游戏的前端结构掌握图片素材与游戏界面的对应关系也能为后续尝试Phaser等游戏框架或接入排行榜、用户数据等服务器功能打下基础。工程结构便于在本地或服务器环境运行可结合静态资源托管、跨域配置等知识理解部署链路。目前已有176人学习适合作为前端入门阶段的项目参考。1. 中秋知识竞赛这类HTML5游戏源码拆开看就是一个模板每年中秋前一两周总有人翻出这类“中秋知识竞赛HTML5游戏源码”来找模板。这个资源包体积不大没有重型框架一个index.htmlmain.js、common.js、lib.js三个脚本分工明确配上一套按题号命名的Q系列图片和bg.mp3背景音乐就是一套能直接跑的答题游戏。它真正的价值不在那十几道中秋题里而在于它把答题游戏的“题目数据、判定逻辑、视觉反馈、音频控制”拆成了可以整体替换的模块——换题库、换皮、换个节日主题就能变成别的场景的闯关答题。适合两类人想完整走一遍H5交互流程的初中级前端以及需要快速交付运营小游戏的工程师。2. index.html 与目录HTML5 小游戏的入口和资源编排2.1 解压后先别删文件看清四类资源的归属拿到zqzsjs.rar解压后目录里大约四十个文件初看杂乱按用途划分其实就五类类型文件职责入口index.html页面骨架引入脚本挂载容器逻辑脚本lib.js、common.js、main.js工具库、题目数据、主流程控制界面素材start.png、welcome.jpg、loading.jpg、over.jpg、over2.jpg加载页、欢迎页、结算页题目素材Q1_1.png、Q1_a.png、Q1_答案正确.png 等题面、选项、答题反馈音频与附属bg.mp3、share.png、0_out_js_format.py、.DS_Store背景音乐、分享入口、格式化脚本、系统垃圾文件.DS_Store是 macOS Finder 自动生成的索引文件上传到服务器前直接删。我一般用find . -name .DS_Store -delete清理。0_out_js_format.py这个Python脚本后面单独讲它在二次开发时能派上用场。2.2 从 index.html 看脚本加载顺序和页面骨架打开 index.html结构是典型的单页游戏入口没有多路由所有界面切换都靠替换图片和显隐按钮完成。页面骨架大概长这样!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title中秋知识竞赛/title /head body div idgame img idstage srcres/welcome.jpg alt button idstartBtn styledisplay:none开始答题/button /div script srcres/lib.js/script script srcres/common.js/script script srcres/main.js/script /body /html三个脚本的加载顺序值得注意lib.js 最先加载因为它提供底层工具函数common.js 其次它把题目数据和全局配置暴露出去main.js 最后执行因为它要同时引用前两个文件里定义的对象。这里有个高频报错点如果脚本顺序写反main.js 里调用lib.preload()时控制台会直接抛出Cannot read properties of undefined这就是lib对象还没定义就执行了依赖代码。排查这个问题的顺序永远是先看script标签顺序再看文件内部是否用了var而不是let/const导致变量提升混乱。2.3 本地调试必须起静态服务器别直接双击index.html 里的资源路径写成res/bg.mp3这种相对路径双击HTML文件用file://协议打开时现代浏览器会施加严格限制。最常见的是音频无法播放、某些情况下图片缓存异常更麻烦的是如果代码里用了fetch()加载外部题库第五章会讲到这种改造file://协议下几乎必然失败。这个项目本身就是设计成跑在HTTP环境里的所以本地调试最好起一个静态服务器。cd zqzsjs python3 -m http.server 8080然后浏览器访问http://localhost:8080就能看到游戏。端口选 8080 是因为它基本不会和常用的 80、3000 冲突。如果要用 nginx 做生产环境托管配置也很短server { listen 8080; root /data/www/zqzsjs; index index.html; }这里的关键点是根目录必须指向解压目录本身因为代码里用的图片路径是相对于 index.html 的res/前缀。如果把res子目录内容单独挪到另一个目录nginx 的location块又没跟上就会出现一堆404。3. main.js / common.js / lib.js 的分工与答题主流程设计3.1 先看 main.js入口逻辑和页面状态切换main.js 是主控脚本负责游戏的状态机流转加载中 → 欢迎页 → 答题中 → 答题反馈 → 结算。整个页面的可见元素无非是 img 和 buttonmain.js 做的事情就是根据当前状态把合适的图片显示到合适的位置。从常规工程结构推断它的启动过程大致如下window.addEventListener(load, function () { showLoading(); preloadAssets(function () { showWelcome(); }); }); function showWelcome() { document.getElementById(stage).src res/welcome.jpg; document.getElementById(startBtn).style.display block; } document.getElementById(startBtn).addEventListener(click, function () { bgm.play(); // 必须在用户手势里触发见第 4 章 startGame(); });window.load事件要比DOMContentLoaded晚触发它要等页面上的图片、样式等资源全部加载完。第一次进入如果图片多网速慢页面会有一段白屏所以这个项目在资源包里放了loading.jpg配合showLoading()先展示加载图让玩家感知到进度。这种“先显示加载态再切主界面”的做法在H5小游戏里是通用套路loading.jpg不止是装饰它让用户知道程序在跑而不是卡死了。3.2 common.js 里放的题目结构和全局配置common.js 从这个文件名就能猜到它承载的是公共数据和配置项。这里体现了一个对答题类游戏很重要的设计决策题库数据和主流程分开。题目对象的结构大约是四个字段var QUIZ [ { id: 1, question: 中秋节和哪个传统习俗关系最紧密, options: [赏月吃月饼, 赛龙舟, 登高插茱萸, 猜灯谜], answer: 0 }, { id: 2, question: 但愿人长久千里共婵娟出自谁, options: [李白, 苏轼, 杜甫, 白居易], answer: 1 } ]; var CONFIG { totalQuestions: 10, answerTimeout: 15, // 单题限时秒数 useTimer: true, bgmEnabled: true };注意answer字段用的是数字下标而不是选项文本。这个设计省掉了很多麻烦判分时只需要比较用户点击的选项索引和answer是否相等不需要做字符串匹配也不怕选项文本里有特殊字符。而且它和图片命名Q1_a.png、Q1_b.png天然对应a就是下标 0b就是下标 1渲染和判分共用同一套索引不会出现“图片显示的选项和代码判断的选项对不上”的乌龙。3.3 lib.js 里的预加载函数和工具方法lib.js 是工具函数库最核心的职责是图片预加载。答题游戏的体验要求是玩家点完选项后反馈图要立刻出现。如果反馈图是点击瞬间才去请求网络稍有延迟就会白屏闪烁非常影响节奏。所以加载阶段就要把后面用到的图全部预热进浏览器缓存。var lib { preload: function (images, callback) { var total images.length; var loaded 0; var failed 0; images.forEach(function (src) { var img new Image(); img.onload function () { loaded; if (loaded failed total) callback(); }; img.onerror function () { failed; if (loaded failed total) callback(); }; img.src src; }); } };这里的关键点是new Image()主动加载而不是把所有img标签先塞进 DOM 里让浏览器去拉。主动加载能精确知道每一张图的状态失败时也能单独计数。onerror里同样执行failed并参与完成判断这是为了防止某张图资源缺失时回调永远不触发整个游戏卡在加载页。做完预加载后后续所有img.src res/Q1_a.png的赋值都是直接命中缓存不会闪烁。3.4 答题主流程不重复出题、判分和结算游戏一共10道题核心需求是“随机出题但不能重复”。这里维护一个题目池每出一道题就把它从池子里抽走是答题类游戏里的常见做法var questionPool QUIZ.map(function (_, i) { return i; }); var score 0; var currentQIndex -1; function nextQuestion() { if (questionPool.length 0) { showResult(score); return; } var idx Math.floor(Math.random() * questionPool.length); currentQIndex questionPool.splice(idx, 1)[0]; renderQuestion(QUIZ[currentQIndex]); } function onAnswer(selectedIndex) { var current QUIZ[currentQIndex]; if (selectedIndex current.answer) { score; showFeedback(true); } else { showFeedback(false); } setTimeout(nextQuestion, 500); }注意splice(idx, 1)返回的是一个数组数组里装着被删除的元素所以[0]取出的是题目下标而不是题目对象本身。这个细节很容易写错写成questionPool.splice(idx, 1)不带[0]的话拿到的就是一个数组后面的QUIZ[currentQIndex]会直接得到undefined。setTimeout(nextQuestion, 500)里为什么是500毫秒这个参数决定了玩家能看反馈图多久。太短看不清对错太长答题节奏会拖沓500ms 是一个比较平衡的中间值。如果后面要加“下一题按钮”资源包里确实有next.png可以把这里的setTimeout改成按钮显隐控制让玩家自己掌控节奏——通常是限时答题模式用前者练习模式用后者。两种模式切换只改这段延迟逻辑即可不用动其他模块。4. 题目图片命名、答案反馈与 bg.mp3 音频控制4.1 Q1_a.png 这种命名规则把题目数据映射到了文件系统资源包中题目图片的命名非常有规律Q1_a.png、Q2_b.png、Q10_c.png前半部分是题号后半部分是选项字母与题库options数组的下标一一对应。同时存在Q1_1.png、Q2_2.png、Q4_4.png这种格式通常对应题干文字图或题号展示图。说明这个项目选择把题目文字渲染成图片而不是写在 HTML 里用 CSS 排版。这样做的好处是视觉统一不受手机字体影响运营换题时只要按命名规范覆盖图片即可不用碰代码。渲染题目时按此规则拼路径function renderQuestion(q) { document.getElementById(questionImg).src res/Q q.id _1.png; var letters [a, b, c, d]; letters.forEach(function (letter, index) { var img document.getElementById(option_ letter); img.src res/Q q.id _ letter .png; img.dataset.index index; }); }字母映射在 DOM 元素的dataset.index里点击选项时直接从event.target.dataset.index取下标去和answer比较。万一某张图缺失onerror里可以给容器加一个可见的默认底色避免玩家看到一个空白区域不知该不该点。4.2 答案正确与答案错误的反馈切换答题反馈图同样按题号拆Q1_答案正确.png、Q1_答案错误.png、Q2_答案正确.png一套题配两张反馈图。点击选项后的视觉顺序是先高亮选中项再覆盖反馈图最后延迟进入下一题。有一个容易踩的坑反馈图是用“覆盖”而不是“替换”的方式展示否则玩家看不清自己刚才点了哪个选项。function showFeedback(correct) { var feedbackImg correct ? res/Q QUIZ[currentQIndex].id _答案正确.png : res/Q QUIZ[currentQIndex].id _答案错误.png; var layer document.getElementById(feedbackLayer); layer.src feedbackImg; layer.style.display block; }反馈层用display:block切换显隐而不是opacity:0/1渐变。因为display切换后就彻底脱离布局流不会残留透明元素挡住下面的按钮点击。还有一个容易遗漏的动作进入下一题前要把反馈层重新隐藏否则上一题的错误反馈会残留到新题目上。这个复位动作放在nextQuestion()的开头执行function nextQuestion() { document.getElementById(feedbackLayer).style.display none; // 其余出题逻辑 }4.3 bg.mp3 的背景音乐与浏览器自动播放策略资源包里的音频只有bg.mp3没有独立的答题音效说明这个版本选择只保留背景音乐。背景音乐的控制核心不是播放API本身而是要绕开浏览器的自动播放拦截策略。Chrome、Safari 和微信内置浏览器都要求audio.play()必须发生在一次用户手势click、touchstart之后页面初始化时直接调用会被拒绝并抛出一个 Promise 的 rejection。var bgm new Audio(res/bg.mp3); bgm.loop true; document.getElementById(startBtn).addEventListener(click, function () { bgm.volume 0.5; bgm.play().catch(function (err) { console.warn(音频播放被拦截等待用户手势重试, err); }); });play()在移动端返回的是 Promise不catch的话一旦被拦截就会在控制台刷一个未处理的 Promise 异常。loop true让音乐自动循环比监听ended事件手动currentTime 0再播更省事也不会产生无缝衔接时可能出现的短暂停顿。音量用volume属性设置取值范围 0 到 10.5 就是百分之五十音量。如果游戏跑在微信里可以再加一个WeixinJSBridgeReady事件处理微信环境的音频初始化经常依赖这个事件PC 端则不用。4.4 share.png移动端分享的最小接入方式资源包里出现share.png说明预留了分享入口。微信公众号生态里的 H5 分享理想方案是调 JSSDK 的updateAppMessageShareData接口但这需要服务端生成签名纯静态页面做不到。常见的降级方案是结算页显示分享按钮玩家点击后引导用户使用右上角菜单主动分享分享时默认截取页面标题和页面截图作为卡片信息。图标文件share.png和icon.png存在的意义就在这里——作为分享引导按钮的视觉入口以及页面缩略图的兜底。5. 实战格式化脚本、题库替换与 HTML5 游戏部署避坑5.1 用 0_out_js_format.py 还原压缩过的 JS 文件资源包里附带的0_out_js_format.py定位就是格式化JavaScript输出。这种脚本通常在线上代码是压缩版时使用——变量名被缩短成单个字母、缩进全部丢失、几千行代码挤成几行肉眼很难定位逻辑。常见做法是脚本接收一个输入文件参数处理完输出一个新文件。我这边测试时一般这样调用python3 0_out_js_format.py res/main.js res/main.formatted.js脚本核心只有几行读文件、重排、写文件import sys def format_js(src_path, dst_path): with open(src_path, r, encodingutf-8) as f: content f.read() # 这里简化演示实际会用 jsbeautifier 或自写缩进恢复 formatted content.replace(;, ;\n).replace({, {\n ) with open(dst_path, w, encodingutf-8) as f: f.write(formatted) if __name__ __main__: format_js(sys.argv[1], sys.argv[2])没有装 jsbeautifier 的话先pip install jsbeautifier再引入会更可靠。格式化后的 main.js 行数会翻倍别被吓到缩进恢复后if/else嵌套和函数边界一目了然定位题目逻辑比在压缩代码里数括号快得多。格式化只改变排版不改变代码含义所以格式化后可以直接覆盖原文件也可以保留一份对照排查。5.2 把中秋题库换成自己的通用题库换题库是这个源码最实用的二次开发场景。理论上只改 common.js 里的 QUIZ 数组即可但要注意原项目是“图片即题目”的渲染方案沿用的话必须按Q1_a.png的命名规范补齐整套图片。如果不想做图可以改成文字渲染模式把 renderQuestion 里的图片赋值换成文本填充function renderQuestion(q) { document.getElementById(questionText).textContent q.question; document.querySelectorAll(.option).forEach(function (node, index) { node.textContent q.options[index]; node.dataset.index index; }); }这样题库可以直接外置成 JSON 文件通过fetch(questions.json)动态加载运营改数据完全不碰代码。但要注意fetch只在 HTTP 协议下可用本地调试必须走静态服务器双击打开 HTML 是拿不到数据的。把题库外置后common.js 里只保留 CONFIG 配置主逻辑 main.js 不用动因为渲染函数变化被隔离在了renderQuestion内部。5.3 部署到服务器前的验收清单最后过一遍最容易翻车的检查项检查项验证方式常见问题.DS_Store 清理find . -name .DS_Store无关文件暴露本地目录信息文件名大小写对比代码引用和实际文件名Linux 服务器严格区分大小写Q1_A.png不等于Q1_a.png图片路径前缀检查是否都以res/开头改动目录结构会直接批量404音频自动播放首次点击开始按钮后再验证不绑定用户手势会被拦截题目不重复连续玩三局确认无重复出题splice 逻辑写错会拿到 undefined反馈层复位答对一题再答一题观察反馈图残留下一题前未隐藏 feedbackLayer部署后用curl -I http://your-domain/index.html看响应码是否为 200再用浏览器开发者工具 Network 面板过一遍全部图片和音频资源红色项就是要修的。这个验证流程比直接点开始按钮更能定位问题因为有些资源加载失败不会阻断脚本执行要等到答题环节才会暴露。到这里这个中秋知识竞赛的HTML5游戏源码就算拆完了。从 index.html 的引用顺序到三个脚本的分工再到图片命名规则、音频播放策略和 Python 格式化脚本的用处核心链路已经完整过了一遍。下次再拿到同类答题游戏源码按这套拆法先看入口、再理状态流、最后对照资源命名规则就能快速判断哪些能改、要改哪里。本文还有配套的精品资源点击获取
返回列表