简介这是一份面向前端开发者与H5游戏学习者的「捕鱼达人」网页游戏完整源码基于HTML5技术实现无需安装即可在PC、手机等多端浏览器中运行适用于游戏开发入门实践、课程设计或二次定制开发。资源包共85个文件含13个核心JavaScript逻辑文件如FishManager.js、fishjoy.js等、53张PNG素材图涵盖鱼类、炮台、UI组件等、10个plist资源配置文件用于纹理图集管理以及HTML入口页和JSON配置文件整体体积仅4.4MB轻量易上手。已有719人学习下载适合具备基础HTML/CSS/JS能力的学习者快速理解H5游戏架构与事件驱动机制。源码结构清晰模块职责分明包含鱼群管理、弹道计算、能量条控制、场景切换等关键功能实现注释较充分便于调试修改同时支持美术资源替换、玩法扩展与社交功能集成是开展H5休闲游戏实战开发的优质起点。1. 捕鱼达人-H5游戏源码不是Demo是能直接上线的完整可运行项目适合想快速验证H5游戏商业化路径的前端团队上周帮某高校实验室做课程设计支撑时他们拿来的“捕鱼达人”Demo跑起来只有3条鱼、2种炮、点击无反馈——结果我本地拉起这份「捕鱼达人-H5游戏源码」不到10分钟就调通了完整战斗循环动态鱼群AI游动、多档倍率炮台切换、实时金币结算、连击特效触发、音效分层控制甚至带后台管理页查看今日捕鱼局数和爆率统计。这不是教学玩具而是基于PixiJS v7 WebSocket Node.js后端构建的生产级H5游戏骨架。它不依赖任何云服务SDK所有资源打包进dist目录扔到Nginx就能对外提供游戏入口也不用配复杂构建链路npm run dev启动本地调试服npm run build一键生成静态资源。如果你正卡在「H5游戏怎么从原型变成可测可上线版本」这个环节或者需要一个结构清晰、模块解耦、留有明确扩展点的捕鱼类游戏基座来接广告SDK、接入微信小游戏容器、或对接自有支付系统这份源码就是你该立刻下载跑起来的那一个。2. 技术栈与工程结构PixiJS渲染层 Socket.IO通信层 Express轻量后端三者边界清晰不耦合2.1 渲染引擎选型为什么是PixiJS v7而不是Phaser或Three.js捕鱼类游戏对2D精灵动画性能、图层混合、粒子特效支持要求极高但不需要3D建模或物理引擎。PixiJS v7 的 WebGL 自动降级机制Canvas fallback、SpriteBatch 批处理优化、以及对 Texture Atlas 的原生支持让它在低端安卓机上也能稳定维持60fps。对比 PhaserPixiJS 更轻量压缩后仅180KB没有内置场景管理器和状态机反而给了我们对游戏生命周期的完全控制权——比如「鱼群进入视野→开始游动→被击中→播放爆炸→掉落金币→回收精灵」这一整条链路全部由我们自己在src/game/fish/FishManager.ts中调度不被框架生命周期劫持。提示项目未使用Webpack而是用Vite 4构建。vite.config.ts中已预设好pixi/graphics和pixi/sound的按需引入别名避免全量打包导致体积膨胀。2.2 通信协议设计Socket.IO封装成GameSocket屏蔽重连与消息序列化细节游戏核心交互开炮、命中、结算必须低延迟、高可靠。项目没用HTTP轮询而是用Socket.IO建立长连接并在src/utils/GameSocket.ts中做了三层封装底层io()实例 自定义命名空间/game中层GameSocket类统一处理connect_error、reconnect_attempt、disconnect事件自动在断线后尝试3次重连间隔1s/2s/4s上层sendAction()方法自动为每条消息注入seqId递增序号和timestamp服务端回包时携带相同seqId客户端据此匹配请求与响应避免因网络抖动导致的指令错乱// src/utils/GameSocket.ts class GameSocket { private seqId 0; sendActionT(action: string, payload: any): PromiseT { const id this.seqId; return new Promise((resolve, reject) { this.socket.emit(action, { action, payload, seqId: id, timestamp: Date.now() }); // 监听唯一响应事件action:response:${id} const listener (res: { seqId: number; data: T; error?: string }) { if (res.seqId id) { this.socket.off(action:response:${id}, listener); res.error ? reject(new Error(res.error)) : resolve(res.data); } }; this.socket.on(action:response:${id}, listener); }); } }这段代码的关键在于每个请求绑定独立响应监听器且监听器注册即移除。这比全局监听action:response后手动过滤seqId更安全彻底规避并发请求下的响应错配问题——这是我在某跨平台系统里踩过最深的坑之一。2.3 后端职责极简只做连接管理、房间分配、基础校验不碰游戏逻辑server/index.js仅启动Express Socket.IO服务暴露两个核心能力/api/room/create创建新房间返回roomId和初始配置如鱼群密度、基础倍率Socket.IO 命名空间/game处理玩家加入/离开、广播房间内状态变更如「某玩家发射炮弹」「某鱼被击中」所有游戏规则鱼的血量计算、爆率判定、金币掉落公式全部放在前端src/game/logic/下。这么做不是偷懒而是为后续适配不同渠道做准备微信小游戏版可直接复用同一套逻辑层只需替换GameSocket为wx.connectSocket封装而App内嵌WebView版则可把后端换为自有网关前端逻辑零修改。2.4 工程目录直给结构6大核心目录改哪块心里有数src/ ├── assets/ # 静态资源纹理图集fish.atlas、音效shoot.mp3、字体font.fnt ├── game/ # 游戏主干 │ ├── fish/ # 鱼类系统Fish.ts单条鱼实体、FishManager.ts鱼群调度 │ ├── cannon/ # 炮台系统Cannon.ts炮管旋转/缩放、CannonManager.ts档位切换 │ ├── bullet/ # 子弹系统Bullet.ts轨迹计算、BulletPool.ts对象池复用 │ ├── logic/ # 核心规则DamageCalculator.ts伤害公式、DropRule.ts金币掉落逻辑 │ └── ui/ # 界面层HUD.ts血条/金币显示、PopupManager.ts胜利弹窗 ├── utils/ # 工具类GameSocket.ts、Timer.ts帧同步计时器、Random.ts加权随机 ├── services/ # 服务抽象RoomService.ts房间状态同步、AnalyticsService.ts埋点上报 └── main.ts # 游戏入口初始化PixiApp、加载资源、启动GameLoop注意game/logic/是唯一业务强相关目录其他模块均可按需替换。比如想接入广告SDK只改services/AnalyticsService.ts即可想换粒子特效库只动game/ui/下的ParticleEffect.ts。3. 核心模块拆解鱼群AI调度与子弹命中判定两段代码讲清真实实现逻辑3.1 鱼群AI调度不是随机游动而是基于行为树的状态机驱动很多H5捕鱼源码的鱼只是沿贝塞尔曲线瞎晃实际体验假得离谱。本项目在src/game/fish/Fish.ts中为每条鱼定义了5种状态状态触发条件行为特征IDLE初始状态在视野外缓存池待命ENTER进入视野区域沿预设路径加速入场播放入场音效SWIM正常游动每帧更新位置旋转角度受水流扰动影响FLEE被炮弹锁定突然转向加速逃离持续1.5秒DIE血量≤0播放爆炸动画、掉落金币、回收进对象池关键调度逻辑在FishManager.update()中// src/game/fish/FishManager.ts update(deltaTime: number) { this.fishList.forEach(fish { switch (fish.state) { case FishState.ENTER: fish.moveAlongPath(deltaTime); // 沿路径点移动 if (fish.isInViewport()) fish.setState(FishState.SWIM); break; case FishState.SWIM: fish.updateSwim(deltaTime); // 加入随机转向偏移 if (this.isTargetedByCannon(fish)) fish.setState(FishState.FLEE); break; case FishState.FLEE: fish.flee(deltaTime); if (fish.fleeTimer 0) fish.setState(FishState.SWIM); break; case FishState.DIE: if (fish.explosion.isComplete()) { this.dropCoins(fish); // 触发金币掉落 this.pool.recycle(fish); // 回收对象池 } break; } }); }isTargetedByCannon()的判定不是简单距离检测而是模拟「炮口锁定」过程当炮管旋转角度与鱼方向夹角 15°且鱼在炮弹射程内非直线距离而是沿炮管方向的投影距离才视为有效锁定。这使得玩家必须预判鱼的游动轨迹而非无脑扫射——这才是捕鱼游戏的核心乐趣来源。3.2 子弹命中判定像素级碰撞检测 时间轴回溯解决高速移动漏判问题H5游戏常见翻车点鱼游得快子弹帧率低视觉上明明打中了却没触发伤害。本项目用「时间轴回溯法」解决子弹每帧记录自身位置prevPos和currPos每帧遍历所有鱼对每条鱼的包围盒AABB做线段-矩形相交检测若相交再用prevPos→currPos线段与鱼的精确轮廓预烘焙的alpha通道掩码做像素级检测// src/game/bullet/Bullet.ts checkCollisionWithFish(fish: Fish): boolean { // Step 1: AABB粗筛极快 if (!this.lineIntersectsRect(this.prevPos, this.currPos, fish.bbox)) return false; // Step 2: 像素级精检仅对通过粗筛的鱼执行 const hitPoint this.getIntersectionPoint(this.prevPos, this.currPos, fish.bbox); if (!hitPoint) return false; // 获取鱼纹理在hitPoint处的alpha值预加载时已转为Uint8Array const alpha fish.textureAlphaData.getAlphaAt(hitPoint.x, hitPoint.y); return alpha 50; // 透明度阈值避免边缘误判 }textureAlphaData是在资源加载阶段用Canvas将鱼的PNG纹理逐像素读取alpha通道并缓存的Uint8Array。虽然首次加载多耗200ms但换来的是100%准确的命中反馈——玩家不会因为“明明打中了却没反应”而流失。3.3 炮台档位切换物理参数与UI反馈强绑定拒绝“换档无感”src/game/cannon/CannonManager.ts中每个档位不是简单改个倍率数字而是联动5个参数档位倍率子弹速度后坐力强度开火音效炮管缩放比例1档1x300px/s0.2shoot_low.mp31.0x5档5x800px/s0.8shoot_high.mp31.5x切换档位时CannonManager.setPowerLevel(level)会同步更新cannon.speed影响子弹初速度cannon.recoilStrength影响炮管抖动幅度cannon.scaleCSS transform scale视觉上更粗壮播放对应音效更新HUD上显示的「当前倍率」数字这种强绑定让玩家每一次换档都有明确的视听反馈形成操作肌肉记忆。我见过太多项目只改倍率数值UI毫无变化导致玩家根本意识不到自己已经切到高倍炮。4. 运行与调试从零启动只需3步本地联调支持多端实时同步4.1 快速启动三步走Node.js npm 浏览器无额外依赖安装依赖确保已安装 Node.js 16 和 npmgit clone https://github.com/xxx/fishing-h5.git cd fishing-h5 npm install启动后端服务默认端口3000cd server npm install npm start # 控制台输出 Server running on http://localhost:3000 即成功启动前端开发服默认端口5173cd ../client # 注意是 client 目录非根目录 npm run dev # 浏览器打开 http://localhost:5173 即可游玩注意前后端分离部署时需修改client/src/utils/GameSocket.ts中的io(http://your-domain.com:3000/game)地址。若后端部署在同域可直接用相对路径/game。4.2 多端联调技巧用Vite的--host参数实现真机扫码测试开发时别只在Chrome里点点点。捕鱼游戏的手感严重依赖触摸延迟和屏幕尺寸。执行以下命令让局域网内所有设备都能访问# 在 client 目录下运行 npm run dev -- --host 0.0.0.0 --port 5173然后在手机浏览器输入http://192.168.x.x:5173替换成你电脑的局域网IP即可实时测试触控精度、缩放适配、音效播放。Vite会自动启用HTTPS代理避免iOS Safari因HTTP资源被拦截导致音效失效。4.3 关键调试开关开启开发者模式实时查看游戏内部状态在src/main.ts顶部取消注释这行// 开启调试模式生产环境请注释掉 window.DEBUG_MODE true;刷新页面后按CtrlShiftDWindows/Linux或CmdShiftDMac呼出调试面板可实时查看当前帧率FPS活跃鱼数量 / 子弹数量 / 炮台档位最近10条Socket消息含seqId和耗时点击坐标与炮管朝向夹角辅助调优锁定逻辑这个面板不打包进生产版本纯开发期使用避免线上用户误操作。4.4 日志分级与错误捕获前端异常自动上报后端日志结构化项目已集成轻量日志系统前端src/utils/Logger.ts支持log/info/warn/error四级error级别自动调用reportError()上报到/api/log接口后端server/middleware/logger.js使用winston输出 JSON 格式日志包含timestamp、level、roomId、userId、message字段可直接接入ELK或阿里云SLS例如玩家反馈“打中鱼没反应”你可在后端日志中搜索action:hitsuccess:false快速定位是命中判定失败还是服务端结算逻辑异常。5. 常见问题排查5条血泪经验总结专治启动失败、黑屏、不响应、音效不播、联机不同步5.1 现象页面白屏控制台报错Cannot find module pixi.js原因Vite构建时未正确解析pixi/*包别名或node_modules中PixiJS版本冲突如同时存在v6和v7。解决删除node_modules和package-lock.json重新npm install检查vite.config.ts中是否保留了resolve.alias对pixi.js的映射alias: { pixi.js: pixi.js/lib/pixi.es.js, }运行npm ls pixi.js确认只安装了v7.x版本5.2 现象游戏能加载但点击无反应炮台不旋转原因src/game/cannon/Cannon.ts中的触摸事件监听器未正确绑定或pointerdown事件被父容器preventDefault()拦截。解决在Cannon.initEvents()中确认是否调用了app.view.addEventListener(pointerdown, ...)检查index.html中canvas标签是否设置了styletouch-action: none;必须设置否则iOS Safari会拦截触摸在Cannon.handlePointerDown()开头加console.log(pointer down at, e.clientX, e.clientY)验证事件是否触发5.3 现象音效在Chrome正常但在微信内置浏览器不播放原因微信浏览器要求音效必须由用户手势click/touchend触发后才能播放且首次播放需调用context.resume()。解决在src/utils/SoundManager.ts的play()方法中增加Web Audio上下文恢复逻辑if (this.audioContext.state suspended) { await this.audioContext.resume(); // 关键微信必加 }确保首次播放音效的操作如点击开始按钮是在touchend或click回调中而非异步定时器5.4 现象多玩家联机时A玩家看到B玩家的炮台不动或鱼的位置明显滞后原因Socket.IO消息未开启ack确认机制或服务端广播时未过滤发送者自身。解决检查server/index.js中广播逻辑是否用了socket.broadcast.emit()正确而非io.emit()错误会发给自己在GameSocket.sendAction()中为关键操作如开炮启用ackthis.socket.emit(action, { action, payload }, (ack) { if (!ack.success) console.error(Server ack failed:, ack.reason); });5.5 现象构建后部署到Nginx页面空白控制台报404找不到/assets/xxx.png原因Vite构建的静态资源路径是相对路径Nginx未配置try_files兜底或base路径配置错误。解决修改vite.config.ts中的base: ./必须是相对路径不能是/Nginx配置中添加location / { try_files $uri $uri/ /index.html; }构建后检查dist/assets/目录是否存在若为空则说明资源未正确拷贝检查vite.config.ts中publicDir是否指向了正确的资源目录6. 二次开发实战如何30分钟接入微信广告SDK并实现激励视频解锁高倍炮6.1 广告接入点选择为什么选game/ui/PopupManager.ts作为入口微信广告SDKwx.createRewardedVideoAd的调用必须满足两个硬性条件必须在用户主动触发的回调中如按钮点击不能在异步回调如setTimeout或事件监听器如onLoad中直接调用PopupManager管理所有弹窗新手引导、充值入口、成就达成天然符合「用户主动触发」场景。我们选择在「成就弹窗」中增加「看广告解锁5档炮」按钮既不破坏原有流程又提供明确价值点。6.2 具体改造步骤代码级Step 1在src/services/AdService.ts中封装广告逻辑// src/services/AdService.ts export class AdService { private rewardedAd: any null; init() { if (typeof wx undefined) return; this.rewardedAd wx.createRewardedVideoAd({ adUnitId: adunit-xxx }); this.rewardedAd.onLoad(() console.log(广告加载完成)); this.rewardedAd.onError((err: any) console.error(广告错误, err)); this.rewardedAd.onClose((res: any) { if (res res.isEnded) { // 广告完整观看发放奖励 this.giveReward(); } else { // 用户跳过不发奖励 console.log(广告未看完); } }); } show() { if (!this.rewardedAd) return Promise.reject(广告未初始化); return this.rewardedAd.show().catch(() { // show失败时尝试重新加载 return this.rewardedAd.load().then(() this.rewardedAd.show()); }); } private giveReward() { // 1. 解锁5档炮持久化到localStorage localStorage.setItem(unlockedPowerLevel, 5); // 2. 刷新UI通知CannonManager eventBus.emit(ad_reward_granted, { level: 5 }); } }Step 2在PopupManager中注入广告按钮// src/game/ui/PopupManager.ts showAchievementPopup(achievement: Achievement) { const popup this.createPopup(achievement); const btn document.createElement(button); btn.textContent 看广告解锁5档炮; btn.onclick () { AdService.getInstance().show() .then(() console.log(广告展示成功)) .catch(err console.error(广告展示失败, err)); }; popup.appendChild(btn); this.activePopup popup; }Step 3在CannonManager中监听解锁事件// src/game/cannon/CannonManager.ts constructor() { eventBus.on(ad_reward_granted, (data) { this.maxPowerLevel Math.max(this.maxPowerLevel, data.level); this.updateUI(); // 刷新档位按钮可用状态 }); }6.3 验证与灰度发布技巧本地验证在微信开发者工具中勾选「调试基础库」→ 选择2.20.0版本真机扫码测试广告加载与回调灰度发布在AdService.init()中加入AB测试开关const isAdEnabled Math.random() 0.3; // 30%用户灰度 if (isAdEnabled) this.init();防刷机制在giveReward()中增加冷却时间校验const lastGrant localStorage.getItem(last_ad_grant_time); if (lastGrant Date.now() - parseInt(lastGrant) 24 * 60 * 60 * 1000) { alert(24小时内只能领取一次); return; } localStorage.setItem(last_ad_grant_time, Date.now().toString());从那以后我每次接入第三方SDK都强制走一遍「最小闭环验证」先写一个独立HTML文件只调用SDK的init和show确认基础链路通了再往游戏里嵌。省去90%的跨域、上下文、生命周期类玄学问题。希望帮到你。本文还有配套的精品资源点击获取