简介:黄金矿工HTML5游戏源码是一份面向前端开发者和游戏开发新手的完整学习项目,基于网页绘图技术实现经典玩法,适合用来理解浏览器游戏从页面搭建、逻辑编写到资源组织的完整过程。压缩包内共有63个文件,主体为一个入口页面、一份游戏逻辑脚本,以及以png、jpg和gif为主的图片与动效素材;整个压缩包仅1.13MB,结构紧凑,便于逐文件对照学习。目前已有1289人下载学习,运行前需要配置简单服务器环境,通过浏览器加载即可体验完整游戏流程。通过阅读代码,可以掌握游戏循环、定时器与事件监听、画布动态绘图、得分计算和动画帧率控制等核心技巧,同时也能学习项目文件的组织方式,为后续修改关卡、替换素材或扩展新功能打下基础。
1. 黄金矿工HTML5游戏源码:一台浏览器就能跑起来的抓钩物理课
如果你翻过几套所谓的“HTML5游戏源码”,大概率见过两种货色:要么是压缩成一团的混淆代码,改名改不了逻辑,要么是只做了个画面上线就崩的demo。这套黄金矿工源码属于少数能让你真正“拆开玩”的那类——核心玩法是抓钩的伸缩与摆动,金币钻石按重量和分值区分,炸药、商店、关卡一应俱全,而且整包跑在纯HTML5+Canvas上,不需要后端、不需要构建工具、不需要装Node环境。打开浏览器就能跑,按F12能逐行调试,适合三拨人:想复刻经典玩法做课程设计的在校生、需要一份可二次开发的2D物理交互范例的前端从业者、以及想把童年游戏做成小样给自家孩子看的非专业玩家。我拆这份源码时最大的感受是:它的钩爪摆动模型写得比很多商业教程干净,值得照着推一遍。
2. 抓钩物理与游戏循环:先看懂最核心的几十行代码
2.1 钩爪的伸缩模型:为什么是速度而不是位置在控制抓取
黄金矿工的所有乐趣都建立在一根绳子上。这根绳子不是简单的“伸长→缩短”,而是两种状态切换:扔钩阶段钩子按固定角速度向外甩,抓到东西后按携带重量反向收回。源码里控制伸缩的核心变量通常就三个:hookAngle(当前角度)、hookLength(当前绳长)、hookSpeed(伸缩速度)。这里最有价值的细节是收绳速度不是常量,而是随负载变化的——这也是“手感”的来源。
// 钩爪更新逻辑(伪代码参考,实际变量名以源码为准) function updateHook() { // 抛出阶段:钩子向外延伸 if (state === 'THROW') { hookLength += throwSpeed * deltaTime; if (hookLength >= maxLength) state = 'RETRACT'; } // 收回阶段:速度受负载影响 if (state === 'RETRACT') { // 负载越大,收回越慢;空钩收回最快 let speedFactor = 1 - Math.min(cargoWeight / maxCargoWeight, 0.85); hookLength -= retractSpeed * speedFactor * deltaTime; if (hookLength <= 0) { state = 'READY'; cargoWeight = 0; // 放下货物 } } }这段逻辑的关键在speedFactor的取值。MATH.min(weight / max, 0.85)把负载对速度的影响限制在15%到100%之间,既保证了大金块拖起来明显吃力,又不会出现完全拖不动的死局。deltaTime的存在意味着游戏帧率变化不影响物理表现,这一点如果你直接抄网上的“每帧固定减1”的写法,高刷新率屏幕上就会看到钩子飞得飞快。
2.2 碰撞判定与抓取优先级:先判石块还是先判黄金
钩子碰到东西后的处理顺序是这份源码里最容易被忽略的坑。我先说结论:应该先检测钩子尖端是否回到原路径,再判断撞到的是石块还是矿物。如果反过来,就会出现在收回过程中撞到侧壁矿物时错误触发“抓取”的bug。
function checkCollision(hookTipX, hookTipY) { // 遍历当前关卡的物件列表(按y坐标排序优化) for (let item of sortedItems) { // 距离判定:钩尖与物件中心距离小于半径即命中 let dist = Math.hypot(hookTipX - item.x, hookTipY - item.y); if (dist < item.radius) { if (item.type === 'ROCK') { // 石块:碰撞后直接进入收回状态,且速度减半 state = 'RETRACT'; retractSpeed *= 0.5; return; } else { // 矿物:挂载到钩子上,下轮开始按重量减速收回 state = 'RETRACT'; cargoWeight = item.weight; item.attached = true; return; } } } }注意这里石块与矿物的处理差异:石块不挂载,只削减收绳速度;矿物才计入负载。这也对应了原版游戏里的物理直觉——钩尖蹭到石头边缘会“泄力”,但不会把石头带回来。源码里对判定顺序的处理是天然合理的,但如果你自己改的时候把遍历顺序调成了“先矿物后石块”,就会出现隔着石头抓金块的穿模效果。别问我怎么知道的,我自己改过。
3. 从下载到跑起来:目录结构和三种启动方式
3.1 这套源码的典型目录长什么样
拿到压缩包解压后,一般会看到这样的结构(不同打包者整理的细节略有差别,但主干一致):
| 路径 | 作用 | 是否需要改动 |
|---|---|---|
index.html | 入口页面,挂载Canvas画布 | 基本不用 |
css/style.css | 游戏界面与商店UI样式 | 可选 |
js/main.js | 主循环,帧率控制 | 需要改 |
js/player.js | 玩家金币、炸药、生命值状态 | 需要改 |
js/hook.js | 钩爪物理逻辑 | 需要改 |
js/items.js | 矿物/石块数据定义 | 需要改 |
js/levels.js | 关卡配置 | 需要改 |
assets/ | 图片与音效资源 | 可选 |
之所以把这些单独拆成文件而不是一个几千行的单文件,是为了让你能分模块调试。常见的盗版资源喜欢把逻辑全塞进一个压缩成两行的game.min.js里,那基本没有二次开发价值——你看到这个目录结构,说明它至少是给人看的源码。
3.2 启动方式:别用双击文件,用本地HTTP服务
直接双击index.html在浏览器里打开,很多时候会碰上图片资源加载失败或音效报错。原因是浏览器的安全策略:file://协议下跨目录读取本地文件有限制。我试过三种启动方式,最省事的是Python自带的HTTP服务器,前提是你的电脑装了Python。如果你没有Python环境,用VS Code的Live Server插件或者npx serve也可以。
# 方式一:Python 3 自带HTTP服务器(推荐) cd 解压后的项目目录 python -m http.server 8080 # 方式二:Node环境用npx(一次性,不需要安装) npx serve -l 8080 # 方式三:VS Code里装Live Server,右键index.html选Open with Live Server三个命令的区别要说清楚:python -m http.server 8080是在当前目录起一个静态文件服务,浏览器访问http://localhost:8080就能进游戏。npx serve会帮你找静态目录,但需要Node.js环境。Live Server自带自动刷新,改代码保存后页面自动重载,调试体验最好。我个人调试物理参数时用Live Server,因为每次改完数值立刻能看到钩子的手感变化。
3.3 启动后先检查这些配置
游戏能跑起来之后,别急着玩,先把开发者工具(F12)打开,看Console有没有红色报错。我只说最常出现的三个问题:Uncaught TypeError: Cannot read property 'x' of undefined通常是关卡数据没加载成功;Failed to load resource: net::ERR_FILE_NOT_FOUND是资源路径写错了;AudioContext was not allowed to start是浏览器自动播放策略导致音频没初始化。前两个按路径排查,最后一个在代码里加一行document.addEventListener('click', initAudio)就能解决。
4. 数值体系拆解:金币价值、抓取判定和商店升级逻辑
4.1 矿物价值表:为什么金块不是越重越划算
黄金矿工的经典设定里,每类矿物的分值和重量有一个固定表:小金块重量1分值30,中金块重量2分值50,大金块重量4分值120,钻石重量3分值300,石头不计分只拖速度。这份源码沿用了这套数值,但它的精妙之处在于分值/重量比——钻石每单位重量100分,大金块每单位重量30分,银矿每单位重量约17分。也就是说,同样占用一次抓取机会,挖钻石的收益远高于挖大金块,这就是为什么有经验的玩家宁可多甩几次钩子也要抓钻石。
| 矿物类型 | 重量 | 分值 | 分值/重量比 | 收绳速度衰减 |
|---|---|---|---|---|
| 小金块 | 1 | 30 | 30 | 无 |
| 中金块 | 2 | 50 | 25 | 轻微 |
| 大金块 | 4 | 120 | 30 | 明显 |
| 钻石 | 3 | 300 | 100 | 中等 |
| 银矿 | 2 | 35 | 17.5 | 轻微 |
| 石块 | 3 | 0 | 0 | 收回减速50% |
这份表格在源码里通常以数组形式定义,帮你找到它们能省不少事:
// 矿物定义(常见写法,具体数值以源码为准) const ITEM_TYPES = { gold_small: { weight: 1, value: 30, color: '#FFD700', radius: 12 }, gold_medium: { weight: 2, value: 50, color: '#FFC125', radius: 16 }, gold_large: { weight: 4, value: 120, color: '#FFB90F', radius: 22 }, diamond: { weight: 3, value: 300, color: '#00FFFF', radius: 14 }, silver: { weight: 2, value: 35, color: '#C0C0C0', radius: 14 }, rock: { weight: 3, value: 0, color: '#808080', radius: 18 } };注意radius参数直接影响抓取难度,因为钩尖碰撞判定用的是钩尖坐标到矿物中心的距离,小于半径就算命中。半径调大会更好抓,但会让画面里矿物之间出现重叠;调小则会明显增加挫败感。新手调试建议保持原值,想降低难度先改radius而不是改角度范围。
4.2 关卡时间与目标分:数值边界决定了游戏节奏
关卡配置的另一个关键维度是时间限制和目标分数。源码里常见写法如下:
// 关卡配置示例 const LEVELS = [ { timeLimit: 60, targetScore: 500, mineralMix: 'light', rockRatio: 0.2 }, { timeLimit: 60, targetScore: 800, mineralMix: 'medium', rockRatio: 0.35 }, { timeLimit: 45, targetScore: 1200, mineralMix: 'heavy', rockRatio: 0.45 }, // ...后面的关卡依次递进 ];rockRatio是石块占矿物总量的比例,从0.2提高到0.45,意味着后期关卡里你平均每三次抓取就有一次可能拖回石头白忙一场。而timeLimit从60秒压缩到45秒,配合目标分数的提升,逼迫玩家必须在抓取前预判矿物的分值重量比变化。这里有个容易被忽略的数值关系:如果targetScore超过当前关卡理论上限的70%,玩家就必须至少抓到一次钻石或大金块才能过关——源码的关卡设计刻意保证了这个比例,让“赌一把大的”成为常态策略而非偶发选择。
4.3 商店升级系统:性价比参数藏在哪
商店里的三项升级——炸药威力、抓钩速度、幸运草——在源码里对应一组全局变量。以炸药为例,升级一次消耗的金币数递增,而效果是爆炸半径增大。常见的实现是这样的:
// 商店升级配置 const UPGRADES = { dynamite: { baseCost: 100, costIncreasePerLevel: 50, maxLevel: 5, effectPerLevel: 15 // 爆炸半径 +15px }, hookSpeed: { baseCost: 150, costIncreasePerLevel: 100, maxLevel: 3, effectPerLevel: 0.15 // 伸缩速度倍率 +0.15 }, luckyClover: { baseCost: 200, costIncreasePerLevel: 150, maxLevel: 3, effectPerLevel: 0.1 // 出现钻石概率 +10% } };effectPerLevel这个参数是二次开发时最值得动的数值。默认的炸药半径每级+15px,到满级也就75px,足够炸掉一片密集的小型矿物但炸不动大金块;如果你想让炸药在后期更“爽”,改成每级+25px就会明显影响关卡平衡。而hookSpeed的每级0.15倍看似不起眼,叠满3级后收绳速度变成1.45倍,配合重物减速公式,原先抓大金块要6秒的变成4秒左右——这个时间差在限时关卡里的价值远大于账面数字。
5. 避坑与常见问题:把我看过的翻车案例直接列给你
5.1 现象:画面出现残影,矿物拖出尾迹
原因:游戏循环里没有调用Canvas清屏函数。很多改过源码的人把ctx.clearRect()误删或注释掉了。解决:在主循环的帧首恢复前一帧画面后清空整个画布,即ctx.clearRect(0, 0, canvas.width, canvas.height)。这里有个细节:清屏应该在重绘之前,顺序反了就是黑屏。
5.2 现象:钩子摆动角度不对,明明按了方向键却往反方向甩
原因:坐标系方向搞反了。Canvas的y轴向下为正方向,而部分物理公式是按数学坐标系写的。你在源码里搜Math.sin(hookAngle),如果它加在了x坐标上而Math.cos加在了y坐标上,那方向就是错的。正确的做法是:钩尖x位置用水平方向的Math.cos,y位置用垂直方向的Math.sin,并且要考虑钩子初始方向朝上时的角度偏移。解决:用x = centerX + length * Math.cos(angle),y = centerY - length * Math.sin(angle),注意那个减号是Canvas坐标系的功课。
5.3 现象:抓到的矿物在收回过程中丢失,钩子空手而归
原因:部分源码在收回过程中对矿物对象执行了“每帧更新位置”,而矿物的坐标更新逻辑用的是绝对坐标,没有跟随钩尖坐标变化。解决:收回阶段矿物坐标应每帧重设为钩尖坐标,即item.x = hookTipX; item.y = hookTipY,而不是让矿物按自己的速度移动。
5.4 现象:音效只在第一次点击时响,之后再无声音
原因:浏览器自动播放策略下,AudioContext在用户首次交互前不初始化。解决:在页面任意位置的第一次点击事件里创建并恢复AudioContext实例,后续所有音效复用它。有的源码把这个初始化放在了window.onload里,这在部分浏览器上会被拦截——改成用户手势触发就稳了。
5.5 现象:游戏在手机上跑得很卡,但电脑上正常
原因:Canvas尺寸没有适配移动端DPR(设备像素比),导致在高分屏上以低分辨率渲染然后被拉伸。解决:设置canvas.width = canvas.clientWidth * window.devicePixelRatio,同时把ctx.scale(devicePixelRatio, devicePixelRatio)应用一遍。如果你懒得改,直接在meta viewport里加user-scalable=no能解决一部分缩放问题但治标不治本。
6. 二次开发捷径:改配置、加道具、给钩子换手感
拿到一份源码,最高效的开发方式不是从头读逻辑,而是先定位“数据驱动”的部分——也就是把数值抽出来单独定义的地方,然后通过改数值看变化,反向理解逻辑。这套源码里最先值得动的三个数据入口:关卡时间、矿物分值、钩子的最大长度和摆角速度。改法极其简单,在js/levels.js里把timeLimit从60改成75,游戏立即变得宽松;把hookAngleRange从90度改成120度,可抓取面积肉眼可见地变大。这种“先改后懂”的方式比从头读代码快得多。
如果你想要真正的功能级改动,加一个“磁铁”道具是性价比最高的练手方向。实现思路是:在商店里增加一个magnet配置项,效果是当钩子收回经过矿物附近时,距离小于某个阈值的矿物自动吸附到钩子上。核心改动在checkCollision函数:原来只检测钩尖与矿物的碰撞,现在还需要检测钩绳线段与矿物的距离。
// 磁铁道具:检测线段(钩绳)与矿物的距离 function magnetDetect(hookStartX, hookStartY, hookTipX, hookTipY, item) { // 把线段表示为向量,计算点到线段的最短距离 let dx = hookTipX - hookStartX; let dy = hookTipY - hookStartY; let lenSq = dx * dx + dy * dy; let t = Math.max(0, Math.min(1, ((item.x - hookStartX) * dx + (item.y - hookStartY) * dy) / lenSq)); let closestX = hookStartX + t * dx; let closestY = hookStartY + t * dy; let dist = Math.hypot(item.x - closestX, item.y - closestY); return dist < item.radius + MAGNET_RANGE; // MAGNET_RANGE 建议设 20~30 }这段代码的原理是向量投影:把矿物的位置投影到钩绳线段上,算投影点是否落在线段范围内,再量投影距离。很多人在这一步会直接用“点到直线距离”公式,但那算出来的是无限长直线的距离,会出现在钩子还没甩到那个位置时矿物就飞过来的穿模场景。用t参数限制投影点必须在线段端点之间,才不会在钩子初始位置就误触发吸附。
最后说一个我自己的习惯:所有数值类的参数改动,我都会在源码里用// TUNE:注释标记,方便后期回滚。比如// TUNE: magnet range 25、// TUNE: throwSpeed 3.2,这样隔一个月再打开这个项目,看到的是带上下文的数值而不是一串来历不明的魔法数字。至于手感这东西——网上给再多参数表都不如你自己把throwSpeed从2改到4试一把,钩子甩出去的那一下是“干脆利落”还是“软绵绵”,十秒钟就有答案。希望这份源码的拆解笔记能帮你在自己的项目里少踩几个我踩过的坑。
本文还有配套的精品资源,点击获取