ARTICLE DETAIL

资讯详情

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

微信小程序Canvas游戏开发实战:飞翔的小鸟源码解析与Java后端对接

微信小程序Canvas游戏开发实战:飞翔的小鸟源码解析与Java后端对接 简介这份资源是一套微信小程序完整实战Demo源码以经典游戏「飞翔的小鸟」为主题采用Canvas绘制游戏画面并附带Java后端代码适合正在学习微信小程序开发、想通过完整项目理解Canvas动画与前后端协作的初学者和进阶开发者参考。压缩包共35个文件约284KB包含7个js逻辑脚本、4个xml与2个wxml页面结构、3个wxss样式、3个json配置以及8个png、3个jpg图片素材和1个java后端文件另附README与LICENSE说明目录按pages、images、utils、java等模块划分结构清晰。目前已有541人学习下载。读者可从中获取Canvas游戏循环、碰撞检测、分数统计等核心实现思路参考小程序页面注册、配置与资源组织方式并对照Java后端代码理解接口对接流程适合作为课程设计或练手项目的完整参考模板。1. 从一份「飞翔的小鸟」源码说起canvas 渲染和 Java 后端到底怎么配合微信小程序里做游戏很多人第一反应是「小程序能跑游戏吗」。能而且不用引擎也能跑。我手上这份「飞翔的小鸟」完整 demo就是最典型的入门样本前端用 canvas 2D 逐帧画小鸟、管道和地面后端用 Java 提供分数上报和排行榜接口。它解决的不是「怎么做一个爆款游戏」而是「怎么把小程序游戏开发的最小闭环跑通」——渲染循环、碰撞检测、状态管理、网络请求四件事一个不少。适合谁看如果你已经会写微信小程序的页面和wx.request但没碰过 canvas 动画或者你是 Java 方向想找一个能跟小程序前端对接的练手项目这份 demo 的骨架刚好够用。它不依赖 uniapp也不依赖任何游戏引擎纯原生 canvas 加 Java Servlet 风格的后端代码量可控改起来不玄学。接下来我按「先跑起来、再拆渲染、再拆后端、最后避坑」的顺序把这份源码里真正值得抄的部分讲清楚。2. 把 demo 跑起来环境、目录和第一帧2.1 前端目录结构与 canvas 初始化拿到源码先别急着改逻辑第一步是让第一帧画出来。小程序里 canvas 的用法和 H5 有区别老版本用wx.createCanvasContext新版本推荐type2d的 Canvas 2D 接口。这份 demo 用的是后者因为 2D 接口支持requestAnimationFrame帧率更稳。目录大致是这样miniprogram/ pages/game/ game.js // 游戏主逻辑 game.wxml // canvas 节点 game.wxss utils/ request.js // 请求封装 server/ src/main/java/...game.wxml里只需要一个 canvas 节点canvas type2d idgameCanvas bindtouchstartonTouchStart stylewidth:100%;height:100%; /canvas逻辑说明type2d是关键不加这个属性拿到的是旧版 contextrequestAnimationFrame用不了。bindtouchstart绑定点击事件小鸟的上升动作就靠它触发。参数上canvas 的style宽高决定显示尺寸真正影响清晰度的是后面代码里设置的canvas.width两者要按设备像素比换算否则在高分屏上会糊。2.2 初始化 canvas 与拿到 2D context在game.js的onReady里初始化这是整个渲染的起点Page({ onReady() { const query wx.createSelectorQuery() query.select(#gameCanvas) .fields({ node: true, size: true }) .exec((res) { 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) this.canvas canvas this.ctx ctx this.initGame() }) } })逻辑说明createSelectorQuery是异步的必须等回调拿到node才能getContext。ctx.scale(dpr, dpr)这一步很多人漏掉结果就是画面发虚。参数上pixelRatio在 iPhone 上通常是 2 或 3安卓机型差异大所以不能写死。initGame里做的是加载图片资源、初始化小鸟坐标和管道数组资源没加载完就开画会报错所以图片加载要用canvas.createImage()并监听onload。2.3 用 requestAnimationFrame 驱动游戏循环游戏循环是 canvas 游戏的心脏这份 demo 用的是递归调用loop() { this.update() // 更新物理状态 this.render() // 重绘画面 this.canvas.requestAnimationFrame(this.loop.bind(this)) }逻辑说明update负责改数据小鸟速度、管道位置、碰撞判定render只负责画两者分离是基本纪律。requestAnimationFrame挂在 canvas 节点上而不是全局这是小程序 2D 接口的要求。参数上帧率由系统决定一般 60fps但不要假设每帧间隔固定物理计算里最好用时间差deltaTime而不是固定步长否则不同机型速度会不一致——这是新手最容易翻车的地方。3. 拆开渲染层小鸟、管道和碰撞检测怎么写3.1 小鸟的物理运动与点击响应小鸟的核心就两个变量垂直速度velocity和重力gravity。点击时给一个向上的瞬时速度每帧再叠加重力onTouchStart() { this.bird.velocity -6 // 向上冲 }, update() { this.bird.velocity this.gravity // 重力持续下拉 this.bird.y this.bird.velocity // 边界处理 if (this.bird.y 0) { this.bird.y 0 this.bird.velocity 0 } }逻辑说明velocity为负表示向上因为 canvas 的 y 轴向下。gravity一般取 0.3 到 0.5 之间太大手感发沉太小像在太空飘。参数上-6这个初速度是我调过比较跟手的值配合gravity 0.4小鸟的上升下落节奏接近原版。注意边界处理只处理顶部底部撞地应该直接判定游戏结束而不是简单归零。3.2 管道的生成、移动与回收管道是成对出现的上下各一根中间留缺口。demo 里用一个数组维护所有管道每帧左移updatePipes() { // 每隔一定帧数生成新管道 if (this.frameCount % 90 0) { const gap 120 const topHeight Math.random() * (this.canvasHeight - gap - 100) 50 this.pipes.push({ x: this.canvasWidth, top: topHeight, bottom: topHeight gap, passed: false }) } // 移动并回收 this.pipes.forEach(p p.x - this.speed) this.pipes this.pipes.filter(p p.x -60) }逻辑说明frameCount % 90控制生成间隔90 帧在 60fps 下约 1.5 秒一根节奏合适。gap是缺口高度120 像素是能过但不轻松的值。passed标记用来判断是否加分避免重复计分。回收用filter把移出屏幕的管道删掉不删的话数组越来越长跑几分钟就卡了——这是内存泄漏的常见来源。3.3 碰撞检测矩形相交就够用小鸟和管道都是矩形碰撞检测用 AABB轴对齐矩形包围盒最简单checkCollision() { const b this.bird for (const p of this.pipes) { // 小鸟在管道水平范围内 if (b.x b.w p.x b.x p.x this.pipeWidth) { // 撞到上管道或下管道 if (b.y p.top || b.y b.h p.bottom) { return true } } } return false }逻辑说明先判断水平方向是否重叠再判断垂直方向是否在缺口外两个条件同时满足才算撞上。参数上小鸟的w和h建议比实际图片略小一圈留一点容错否则玩家会觉得「明明没碰到就死了」这是手感优化的小技巧。碰撞检测每帧都要跑所以逻辑要轻别在里面做复杂计算。4. Java 后端分数上报和排行榜接口怎么搭4.1 接口设计与请求封装后端只做两件事接收分数、返回排行榜。接口设计越简单越好接口方法参数返回/api/scorePOSTuserId, score{code, msg}/api/rankGETlimit{code, data:[{userId,score}]}小程序端的请求封装放在utils/request.jsconst BASE_URL https://your-domain.com function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { content-type: application/json }, success: (res) { if (res.data.code 0) resolve(res.data) else reject(res.data) }, fail: reject }) }) } module.exports { request }逻辑说明用 Promise 包一层调用处就能用async/await比回调清爽。BASE_URL必须换成你自己的域名小程序正式环境要求 HTTPS 且域名要在后台配置白名单本地调试可以在开发者工具里勾选「不校验合法域名」。参数上header里的content-type要和后端接收方式一致否则后端可能收不到 body。4.2 Java 端接收分数与写库后端用最朴素的 Servlet 或 Spring Boot 都行核心是接收 JSON 并落库。以 Spring Boot 为例RestController RequestMapping(/api) public class GameController { Autowired private ScoreService scoreService; PostMapping(/score) public Result submitScore(RequestBody ScoreDTO dto) { // 简单校验防止明显异常值 if (dto.getScore() 0 || dto.getScore() 100000) { return Result.fail(分数异常); } scoreService.save(dto.getUserId(), dto.getScore()); return Result.ok(); } GetMapping(/rank) public Result rank(RequestParam(defaultValue 10) int limit) { return Result.ok(scoreService.topN(limit)); } }逻辑说明RequestBody把 JSON 映射成对象字段名要和前端传的一致。分数校验不能省否则有人直接构造请求刷榜这是血泪经验。topN查询用ORDER BY score DESC LIMIT ?数据量大了要给score字段加索引。参数上limit默认 10前端传多少就查多少但要设上限防止一次拉几万条把接口拖垮。4.3 数据一致性分数上报失败怎么办小程序网络不稳定分数上报可能失败。常见做法是本地先存成功后再清async function submitScore(score) { wx.setStorageSync(pendingScore, score) try { await request(/api/score, POST, { userId: getUserId(), score }) wx.removeStorageSync(pendingScore) } catch (e) { // 失败就留着下次进游戏时重试 console.log(上报失败已缓存) } }逻辑说明先写本地缓存再发请求成功才清。这样即使请求失败分数也不会丢下次启动时检查pendingScore并重试。参数上userId可以用wx.login换取的 openid也可以用本地生成的 UUID看你要不要跨设备同步。注意重试要有次数上限否则一直失败会反复请求耗电又费流量。5. 避坑与排查那些让我重跑三遍的问题5.1 现象画面模糊、边缘发虚原因canvas 的width/height没按pixelRatio放大或者放大了但没ctx.scale。解决按 2.2 的写法先设canvas.width 显示宽 * dpr再ctx.scale(dpr, dpr)两步缺一不可。5.2 现象游戏越玩越卡帧率往下掉原因管道数组或粒子数组只增不减内存和绘制量持续上涨。解决每帧用filter回收移出屏幕的对象绘制时只画可见范围内的元素。另外检查有没有在update里创建新对象能复用就复用。5.3 现象不同手机小鸟速度不一样原因物理计算用了固定步长但不同设备帧率不同。解决用deltaTime缩放位移this.bird.y this.bird.velocity * deltaTime其中deltaTime是上一帧到这一帧的毫秒数除以基准值。这样 60fps 和 30fps 的设备速度才一致。5.4 现象后端收不到参数dto全是 null原因前端header的content-type写成了application/x-www-form-urlencoded或者后端用了RequestParam而不是RequestBody。解决前端统一application/json后端接收 JSON 用RequestBody接收表单用RequestParam两边对齐。5.5 现象真机上请求全部失败开发者工具却正常原因正式环境要求 HTTPS 且域名在白名单里开发者工具默认不校验。解决在微信公众平台配置request合法域名本地调试时在开发者工具「详情-本地设置」里勾选不校验。注意这个勾选只在开发阶段有效上线前必须配好域名。6. 进阶把这份 demo 改成能长期维护的骨架跑通之后别停在「能玩」就完事。我一般会做三件事让它更耐用。第一把游戏状态抽成独立模块running、paused、gameover用状态机管理而不是散落的布尔变量这样加暂停、加复活逻辑时不会到处改。第二把渲染和逻辑的帧率解耦逻辑固定 60 次每秒更新渲染跟着requestAnimationFrame走低端机上逻辑不会因为掉帧而变慢。第三后端加一层简单的防刷比如同一userId每分钟最多上报 5 次分数增幅超过阈值就标记待审。验证方法很直接在开发者工具的性能面板看帧率曲线真机上用wx.getPerformance打点重点看update和render的耗时。如果render超过 8ms说明绘制对象太多该做裁剪了。参数调优上gravity和点击初速度这两个值决定手感建议做成可配置项方便反复试。最后说个我自己的习惯每次改完物理参数我都会在真机上连玩十局记录最高分和平均存活时间。如果平均存活时间波动很大说明随机性太强或者难度曲线不平滑这时候调管道生成间隔比调重力更有效。这份 demo 的价值不在于它多完整而在于它把小程序 canvas 游戏的最小闭环摊开了你可以在上面加音效、加皮肤、加排行榜分页改着改着就摸清了小程序游戏开发的边界。希望帮到你。本文还有配套的精品资源点击获取
返回列表