☰
微信小程序小游戏源码拆解:从游戏循环到碰撞检测实战指南
2026/10/3 11:31:36 网站建设 项目流程

经常有人问我能不能分享一些基于微信小程序开发的小游戏源码,说想自己学着写一个,或者课程设计、毕业设计要交个能跑的小游戏demo。我硬盘里确实存了不少早年跑过的源码,贪吃蛇、俄罗斯方块、2048、飞机大战、打地鼠都有,这次重新翻出来逐个跑了一遍,把其中值得反复拆解的核心逻辑、跑源码时会踩的坑、以及怎么把一份“别人写的源码”改造成你自己的作品,一次性整理出来。

这篇文章不是丢个下载链接就完事,而是把源码背后的运行机制讲清楚。适合两类人看:一是刚接触微信小程序、想搞明白小程序和小游戏到底什么关系的初学者;二是手头需要快速出一个可演示小游戏demo、但不想从零手写渲染循环和碰撞检测的开发者。我会从源码选型、环境搭建、核心逻辑拆解、坑点排查讲到后续上线路线,尽量让你拿到源码后不只是“能跑”,而是真正懂它怎么跑起来的。

1. 小游戏源码怎么选:先分清微信小程序的“游戏”和“应用”

1.1 “小程序”和“小游戏”是两套运行时

很多人一开始就把小程序和小游戏混为一谈,实际上它们在微信里是两套完全不同的运行环境。普通小程序走的是Page + WXML + WXSS + JS这套结构,页面由组件树渲染,开发者可以像写网页一样操作DOM层面的逻辑;但小游戏不一样,它没有WXML、WXSS和组件树,只有一个全屏的Canvas和一个全局的canvas上下文,所有画面都是你用代码一帧一帧画出来的。

这意味着,你平常积累的那套小程序开发经验,在写小游戏时大多用不上。比如你没法在wxml里放一个<view>当游戏背景,也没法用wx.createSelectorQuery去查某个元素的宽高,你需要自己管理渲染坐标、帧循环和触摸事件。反过来也一样,如果你找的源码是“xxx小游戏源码”但是里面全是.wxml文件,那它大概率只是一个模拟游戏界面、用定时器改数据的小程序应用,并不是真正基于Canvas渲染的小游戏。

我整理源码时会把这两类分开,本文说的小游戏,特指基于Canvas直接绘制、入口是game.js的那一类。

1.2 我筛选源码的三条标准

网上一搜“微信小程序小游戏源码”能出来一大堆,但质量参差不齐,很多下载下来根本跑不起来。我筛选并保留到现在的源码,基本都满足下面三条标准,你可以按同样的思路去筛你要的库:

  • 游戏玩法经典,逻辑足够简单直白。贪吃蛇、2048、俄罗斯方块这类游戏的核心规则用几十行代码就能说清楚,非常适合用来理解小游戏的基本架构。像那种大型RPG或者卡牌对战,源码动辄几百个文件,新手看一晚上只会更懵。
  • 纯前端实现,不依赖后端接口。一跑起来就要连服务器、要websocket的源码,本地根本调试不了,而且现在很多免费接口已经失效,拿回来反而是负担。
  • 包体小、入口清晰。一个合格的小游戏源码,根目录下必须有game.js、game.json和project.config.json,这是微信开发者工具识别项目的关键。如果连这三个文件都没有,那它顶多算一段“代码片段”,不是完整项目。

1.3 建议的源码学习顺序

如果你是完全没写过小游戏的新人,我建议按照“贪吃蛇 → 2048 → 飞机大战”这个顺序去看源码。贪吃蛇最能帮你建立“游戏循环 + 状态更新 + 重绘”的整体认知;2048的核心是二维数组的合并算法,能训练你处理数据逻辑;飞机大战则会涉及到碰撞检测、对象池、触控操作这些更进阶的东西。等把这三种都跑通一遍,你再去看Cocos、Laya这类引擎生成的工程或者Unity导出的小游戏,心里就有底了,因为底层无非还是那套渲染循环。

2. 新手30分钟跑通第一份小游戏源码

2.1 需要准备的工具和AppID

跑通小游戏源码需要三个东西:微信开发者工具、一个小游戏类目的AppID、以及源码本身。微信开发者工具直接在官网下载稳定版就行,安装时选“小游戏”项目类型即可。AppID需要到微信公众平台注册一个账号,然后在后台“设置→基本设置→服务类目”里选择“小游戏”,会自动生成一串以wx开头的AppID。

如果你只是本地调试、不涉及真机预览和上传,也可以先选“测试号”来跑,但请注意测试号在使用云开发、支付、登录等接口时是有一定限制的。我自己的习惯是注册一个个人小游戏AppID,虽然个人主体在类目上有一些限制,但用来跑源码、做 demo 演示完全够用。

2.2 导入源码后必做的三步检查

拿到源码后不要急着点“编译”,先做三步检查能帮你省下不少排错时间。

第一步,看根目录结构。你的项目根目录下应该有game.js,这是小游戏的入口文件,相当于网页里的index.html。如果源码里既有game.js又有app.js,那说明可能是混着普通小程序写的,需要你搞清楚哪个才是真正入口。

第二步,看game.json。这个文件里配置了小游戏的页面朝向、渲染模式、分包等信息,常见的字段包括:

{ "deviceOrientation": "portrait", "showStatusBar": false, "networkTimeout": { "request": 5000 } }

showStatusBar控制是否显示状态栏,做全屏类游戏一般设为false。如果文件不存在或格式错误,工具会直接报错,这是最常见的白屏原因之一。

第三步,确认project.config.json里的appid是不是你自己的。这个文件记录了项目级配置,如果你用自己的AppID导入,通常工具里重新填一下即可。你只需要知道:小游戏里决定“项目能不能跑”的是game.json和project.config.json,决定“代码入口在哪儿”的是game.js。

2.3 真机预览与基础库设置

在开发者工具里能跑起来只是第一步,小游戏很多问题只有真机才暴露。真机预览前,先到详情面板把“本地设置”里的调试基础库切高一点,小游戏需要用Canvas、WebGL相关的API,基础库版本太低会出现接口找不到的情况。我通常直接选当前最新稳定版,然后开启“ES6转ES5”和“上传代码时样式自动补全”,这两个选项能避免不少兼容性坑。

真机预览点工具栏里的“预览”按钮,手机会弹出二维码,扫码后微信里打开就是小游戏的线上体验版本。如果遇到“打开失败”的情况,先检查AppID是否正确,再看基础库版本,这两个问题占了绝大多数比例。

3. 源码拆解:游戏循环、碰撞检测与数据存储

3.1 小游戏的主循环与帧率控制

所有小游戏源码里最核心的一段代码就是主循环。小游戏没有浏览器里现成的DOM事件驱动,而是一帧一帧地主动刷新画面。微信官方提供的requestAnimationFrame接口就是为了实现这个循环的。

看一段典型的简易主循环实现:

// game.js const canvas = wx.createCanvas() const ctx = canvas.getContext('2d') let lastTime = 0 function gameLoop(time) { const delta = time - lastTime lastTime = time // 更新逻辑 update(delta) // 渲染 render(ctx) requestAnimationFrame(gameLoop) } requestAnimationFrame(gameLoop)

这里的delta是距离上一帧的时间差,用来做物理运动和动画插值。没有这个差值,游戏速度会受设备帧率影响,同一段代码在60Hz和120Hz屏幕上表现完全不同。

顺带一提,网上有些分享的源码会用setInterval(fn, 1000 / 60)来模拟帧循环,这在浏览器端可以,但在微信小游戏里不建议这么做。setInterval的最小间隔精度、以及小程序切到后台时的行为都不可控,官方也不推荐。你拿到源码后如果看到这个模式,建议改成requestAnimationFrame。

3.2 碰撞检测的朴素实现

碰撞检测是小游戏绕不开的一环,飞机大战打中敌机、贪吃蛇吃到食物、俄罗斯方块落到底部,本质上都要回答一个问题:“两个矩形有没有重叠”。在小游戏里,Canvas上绘制的精灵图大多用矩形包围盒做粗略碰撞,也就是AABB碰撞检测,实现起来非常直接:

function hitTest(a, b) { return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y }

这段代码的意思是:如果a矩形的左边缘在b矩形右边缘的左边、a矩形的右边缘在b矩形左边缘的右边、a矩形的上边缘在b矩形下边缘的上边、a矩形的下边缘在b矩形上边缘的下边,那它们一定重叠了。对于大多数入门级小游戏,这个精度完全够用,不需要做像素级碰撞检测。

实际开发中,为了让玩家体验更好,我们通常不会用整个精灵图的外框,而是把碰撞盒缩小一点。比如飞机大战里机身触碰到子弹判定死亡,但飞机贴图的机翼是不算碰撞范围的,所以你要在源码里看到类似{x: this.x + 10, y: this.y + 10, width: this.width - 20, height: this.height - 20}这种写法,别觉得奇怪,这是在给玩家“作弊”。

3.3 2048的合并算法

2048这款游戏特别适合练习数据抽象。它看起来是在处理方块的移动和合并,本质上就是维护一个4x4的二维数组,每一次滑动都是在处理数组里非零数字的聚集和相邻相等数的合并。很多源码里会单独抽出一个board.js来管这些逻辑,我建议你也这么做,把游戏逻辑和渲染分离,代码会清爽很多。

核心的合并逻辑长这样:

function mergeRow(row) { // 去掉所有0,把数字前移 let arr = row.filter(n => n !== 0) // 相邻相等则合并 for (let i = 0; i < arr.length - 1; i++) { if (arr[i] === arr[i + 1]) { arr[i] *= 2 arr.splice(i + 1, 1) } } // 末尾补0,恢复固定长度 while (arr.length < 4) { arr.push(0) } return arr }

你如果拿到了2048的源码,可以重点看它四个方向的滑动是怎么统一处理的。一般会先把二维数组旋转到同一个方向,合并完再旋转回去,这样就不需要写上下左右四套逻辑了。

3.4 最高分与存档

小游戏运行在小程序环境中,天然自带一套本地存储接口。保存最高分最方便的方式是wx.setStorageSync和wx.getStorageSync,它们是同步方法,传进去的值会被自动序列化,简单场景完全够用:

// 保存 wx.setStorageSync('bestScore', currentScore) // 读取 const bestScore = wx.getStorageSync('bestScore') || 0

有些源码还会用wx.getFileSystemManager配合wx.env.USER_DATA_PATH来写自定义文件存存档,这种方式可以保存复杂的游戏状态,比如关卡进度、背包数据,比单纯的Storage更灵活。wx.env.USER_DATA_PATH是小游戏专属的用户目录路径,类似于沙盒目录的绝对路径,可以用来保存图片、文本等文件:

const fs = wx.getFileSystemManager() const filePath = `${wx.env.USER_DATA_PATH}/save.json` // 写文件 fs.writeFileSync(filePath, JSON.stringify(gameData), 'utf-8') // 读文件 const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'))

需要留意的是,小游戏本地缓存空间是有限制的,不要无脑往里塞数据,滥用会把空间撑爆。

3.5 给源码加一个排行榜

排行榜是小游戏拉升活跃度最常用也是需求最多排行功能之一。最简单的方案是纯本地排行榜,把玩家历史最高分按降序排,展示前10名,这种适合单机自嗨的demo。如果想要让不同玩家之间比分数,就需要一个后端存储,这时微信云开发是最省事的方案。

云开发里建一个score集合,把玩家昵称、分数、时间写入,再用云函数查询Top10返回即可。你需要申请开通云开发环境,然后在源码里调用wx.cloud.callFunction。这套链路虽然看起来多,但每一步都有微信官方文档,不会比你自己买服务器再写接口慢多少。

4. 把源码改造成你的游戏:10个高频坑与排查思路

4.1 白屏与入口文件报错

导入源码后最常见的问题就是白屏。白屏的原因大多在入口文件执行报错,但你打开控制台却什么都没打印,这种情况先确认你有没有选中“小游戏”类型的项目模板。工具会默认创建普通小程序模板,而普通小程序模板的入口是app.js,根本不会找到game.js,自然白屏。

另一种可能是game.json里配置了不存在的分包或者引用路径,导致首屏资源加载失败。解法是把源码根目录重新整理一下,确保game.js在根目录,并且在控制台看有没有红色报错日志。

4.2 图片加载是异步的

小游戏里用wx.createImage加载本地图片资源时,要注意图片加载完成是异步的。如果你在img.src赋值后立刻去ctx.drawImage,大概率画出来的是空白。必须把绘制放在onload回调里:

const bg = wx.createImage() bg.onload = () => { ctx.drawImage(bg, 0, 0, canvas.width, canvas.height) } bg.src = 'images/bg.png'

很多源码不严谨,直接在初始化时用了图片对象,导致真机上偶发白屏、图片闪烁,其实就是没等onload。我建议拿到源码后做一次全面体检,把所有图片资源都走一遍onload再进主循环,你会发现稳定性提升明显。

4.3 音效不播放

音效不播放的问题,十有八九不是代码问题,而是微信对音频播放时机的限制。简洁说就是:用户没有跟小程序产生交互前,不能主动播放音频。所以游戏在拿到用户第一次点击后,再去初始化背景音乐和音效,基本都能解决。代码层面,用wx.createInnerAudioContext这样的方式创建音频上下文,然后循环播放入口:

const bgm = wx.createInnerAudioContext() bgm.src = 'audio/bgm.mp3' bgm.loop = true // 用户第一次点击后调用 wx.onTouchStart(() => { bgm.play() })

另外注意iOS端音频格式最好用AAC/MP3,有些设备上WAV格式支持不佳。

4.4 长按拖拽与触摸坐标的换算

在小游戏里做拖拽操作时,很多人直接拿e.touches[0].clientX当游戏坐标,结果发现拖拽的物体总是偏移。这是因为clientX和clientY是相对屏幕可视区域的坐标,而游戏画布可能因为设备屏幕尺寸比例不一致,被拉伸或留有黑边,导致坐标被缩放。

正确的做法是把触摸坐标换算成Canvas的逻辑坐标。你在拿到触摸坐标后用Canvas的宽高比做归一化处理,或者提前计算好Canvas左上角在屏幕上的偏移,再做减法。还有一个常见的坑是长按没反应——小游戏对长按的默认处理比较敏感,如果你发现系统弹出了文本选择菜单,需要在game.json里把"disableLongPress"之类的配置打开,具体字段以当前基础库文档为准。

4.5 顶部导航栏高度与安全区适配

虽然小游戏默认全屏绘制,但顶部状态栏、底部横条这些系统区域仍然存在。如果你在真机上发现游戏操作按钮被刘海屏挡住,或者顶部文字被状态栏盖住,就需要获取安全区信息来做适配:

const { safeArea } = wx.getSystemInfoSync()

safeArea会给出屏幕上安全区域的top、bottom等值,你要手动调整UI元素的位置。拿到这个值后,把游戏里最重要的操作按钮和计分文字布局在安全区内,边缘装饰则可以放心铺满。如果你做的是普通小程序而不是小游戏,顶部导航栏高度又是另一套逻辑,要区分清楚。

4.6 卡顿与Canvas优化

小游戏跑起来越来越卡,大多是因为每帧都创建了新的对象、执行了不必要的绘制。常见优化手段包括:物体回收复用、脏矩形重绘(只更新变化区域)、避免每帧ctx.fillStyle赋值后再来一次全局清屏。Canvas绘制是同步的,频繁调用fillRect会非常消耗性能。

我实践下来最粗暴有效的一招是:主循环里把不变的背景图单独画在不参与频繁更新的离屏Canvas上,每帧先把背景图整块贴上去,再画动态物体,这样能有效减少绘制次数。如果你的游戏里同屏敌机很多,用对象池管理子弹和敌机,也比反复new和销毁对象流畅得多。

4.7 uni-app/Cocos/Unity 打包小游戏的注意点

越来越多开发者选择用uni-app或Cocos/Unity这类跨端引擎来开发小游戏,然后导出微信小游戏版本。这个思路本身没问题,但要注意两点。

一是包体大小。微信小游戏主包体积目前限制是4M,超过部分必须走分包加载。Unity导出的小游戏通常光引擎运行时就好几兆,所以要对资源做压缩,把图片压成WebP、音频用AAC,减少首包体积。二是渲染适配。微信小游戏的渲染机制和浏览器不完全一样,iOS端对WebGL的支持和Android也有差异,Unity打包后一定要在真机上分别测一遍,只在开发者工具里预览是远远不够的。

如果只是做简单2D小游戏,我个人的建议是不要一上来就用Unity这种重型引擎,直接用Canvas手写或者用Cocos的2D模式,折腾成本会低很多。

4.8 基础库版本兼容与渲染引擎适配

微信小游戏的运行时一直在更新,开发工具默认用的基础库也许很新,但用户手机上可能还跑着老版本微信,导致一些新API不生效。处理办法是在开发工具里把调试基础库切到恰当版本测试,同时在你用的关键API文档里确认它要求的最低基础库版本。

另外,微信最近几年在推进新版渲染引擎,并且对普通小程序的渲染机制做了不少升级,但小游戏作为Canvas方案相对稳定,不过Cocos/Unity这类引擎导出的工程要特别关注引擎版本适配说明。遇到问题时,优先看控制台输出的API报错信息,它一般会直接告诉你哪个接口需要什么基础库版本。

5. 从源码到上线的进阶路线

5.1 接入云开发,让游戏不再“单机”

前面提到的排行榜只是一种场景,云开发在小游戏里的可能性远不止这些。登录态、每日签到、好友对战、动态更新关卡数据,都可以用云开发实现,完全不用自己买服务器。对小游戏这种流量波动大、冷启动频繁的场景,云开发的按量付费模式对小团队非常友好。

接入方式很简单,先在game.js顶部初始化:

wx.cloud.init({ env: 'your-env-id', traceUser: true })

然后就能在小游戏里调用wx.cloud.callFunction、wx.cloud.database()了。源码里如果有接口调用,通常第一步都是这个init,不要漏掉。

5.2 上线审核注意点

游戏可玩、测试通过后,要正式发布还需要走微信小游戏的审核流程。审核过程中最容易出现的问题是:游戏内容涉及随机抽取但未做概率公示、虚拟支付未经允许、以及素材版权问题。个人开发者建议先严格使用免费可商用素材,避免字体、音乐、图片方面的侵权风险。

另外在填写类目时,小游戏有单独的类目体系,别选错。审核一般一两天内会出结果,如果被驳回,照着驳回理由逐条改,大多数都是文案提示类问题,不涉及代码改动。

5.3 埋点与数据驱动

源码跑通、上线发布,这才只是游戏的开始。我见过很多开发者把游戏做好上线就撒手不管了,结果日活几天后归零还找不到原因。建议你在源码里就规划好数据埋点:启动次数、关卡失败率、分享按钮点击率、新增用户次日留存,这些数据能告诉你玩家的流失点在哪里。

微信开发者工具里的“数据助手”和“实验室”功能已经能提供大量基础指标,如果你的游戏有自定义事件,也可以通过wx.reportEvent上报。多改一版、多看一次数据,比闷头写新玩法更能稳住你的用户盘子。

我个人在实际操作中的体会是:一套好的小游戏源码,价值不在于“拿来就能跑”,而在于它把游戏最底层的运行框架、数据结构和渲染逻辑摊开给你看。你花一晚上反复改其中某个参数、给角色换皮、调碰撞盒大小,学到的比看十篇教程都多。这份源码整理我会持续更新,下次再跑通新的游戏类型,再继续把心得补充进来。

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

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

立即咨询