小程序游戏开发:使用Canvas实现简单游戏
关键词:小程序开发、Canvas、2D游戏、游戏循环、碰撞检测
摘要:本文将带您从0到1用微信小程序的Canvas开发一个简单的2D游戏。我们会用”小球吃星星”作为实战案例,从核心概念讲解到代码实现,一步步拆解小程序游戏开发的关键技术点。即使您是刚接触游戏开发的新手,也能通过这篇文章掌握Canvas绘图、游戏循环控制、碰撞检测等核心技能。
背景介绍
目的和范围
随着微信小程序用户突破12亿,基于小程序的轻量级游戏成为开发者的新赛道。本文聚焦”如何用小程序的Canvas API开发简单2D游戏”,覆盖从环境搭建到完整游戏实现的全流程,适合想入门小程序游戏开发的开发者。
预期读者
有基础的小程序开发者(了解wxml/wxss/js基本语法)
对2D游戏开发感兴趣的前端工程师
想尝试轻量级游戏开发的独立开发者
文档结构概述
本文采用”概念讲解→原理分析→实战开发”的递进结构:先理解Canvas在小程序中的特性,再掌握游戏循环、碰撞检测等核心机制,最后通过”小球吃星星”案例完整实现一个可运行的游戏。
术语表
核心术语定义
Canvas:HTML5提供的2D绘图API,小程序中通过wx.createCanvasContext调用
游戏循环:游戏的”心跳”,包含”更新→绘制→等待”三个阶段的无限循环
碰撞检测:判断两个游戏对象是否接触的算法(本文用矩形包围盒检测)
精灵(Sprite):游戏中的可移动对象(如本文的小球和星星)
缩略词列表
API:Application Programming Interface(应用程序接口)
FPS:Frames Per Second(每秒帧数,衡量游戏流畅度)
核心概念与联系
故事引入:画家的”动态画卷”
想象你是一个古代画家,皇帝让你画一幅会动的画卷:里面有一颗会跑的小球,还有会随机出现的星星,小球碰到星星星星就会消失。你会怎么做?
首先需要一张能反复修改的”魔法画布”(对应Canvas)
然后需要一个”秒表”控制每秒钟画多少张新画(对应游戏循环)
最后需要一个”裁判”判断小球有没有碰到星星(对应碰撞检测)
这三个工具,就是我们开发小程序游戏的核心。
核心概念解释(像给小学生讲故事)
1. Canvas:会”擦除重画”的魔法画布
小程序的Canvas就像一块特殊的画布,你可以用它画圆、画矩形、写文字。但和普通画布不同的是,它有”自动擦除”功能——每次调用ctx.clearRect()就能清空之前的画,然后重新画新内容。就像你用白板笔在白板上画画,画完一页后用板擦一擦,又能画新的一页。
2. 游戏循环:控制”翻页速度”的秒表
我们的游戏画面不是一张静态图,而是由很多张图片快速切换形成的(就像电影的帧)。游戏循环就是控制每秒钟切换多少张图片的机制。小程序中可以用requestAnimationFrame实现,它会告诉浏览器:“下一次重绘前帮我执行这个函数”,这样就能保证画面流畅。
3. 碰撞检测:判断”有没有碰到”的裁判
游戏里经常需要判断两个物体是否接触(比如小球有没有吃到星星)。最简单的方法是”矩形包围盒检测”:给每个物体画一个虚拟的矩形框,只要两个矩形框有重叠,就认为它们碰撞了。就像判断两个盒子有没有叠在一起,只需要看它们的上下左右边是否交叉。
核心概念之间的关系(用小学生能理解的比喻)
Canvas和游戏循环的关系:Canvas是画家的画布,游戏循环是画家的秒表。秒表每滴答一次(比如每16ms),画家就擦干净画布(clearRect),然后在画布上画新的内容(小球的新位置、星星的新位置)。
游戏循环和碰撞检测的关系:游戏循环是”导演”,每次循环都会先让”裁判”(碰撞检测)检查有没有发生碰撞,然后根据结果调整游戏状态(比如星星消失、分数增加),最后再让画家(Canvas)画出新画面。
Canvas和碰撞检测的关系:Canvas负责把物体画在正确的位置(记录每个物体的x/y坐标),碰撞检测则用这些坐标计算是否碰撞。就像你在纸上画了两个气球,然后用尺子量它们的位置,判断有没有碰到。
核心概念原理和架构的文本示意图
游戏主循环
├─ 更新阶段(Update)
│ ├─ 处理用户输入(如触摸移动)
│ ├─ 更新物体位置(小球移动、星星移动)
│ └─ 碰撞检测(判断是否吃到星星)
├─ 绘制阶段(Draw)
│ ├─ 清空画布(clearRect)
│ ├─ 绘制背景
│ ├─ 绘制小球
│ └─ 绘制星星
└─ 循环控制(requestAnimationFrame)
Mermaid 流程图
graph TD
A[游戏开始] --> B[进入游戏循环]
B --> C[更新阶段]
C --> D[处理触摸输入]
D --> E[更新物体位置]
E --> F[碰撞检测]
F --> G[更新游戏状态(如分数)]
G --> H[绘制阶段]
H --> I[清空画布]
I --> J[绘制背景]
J --> K[绘制小球]
K --> L[绘制星星]
L --> M[调用requestAnimationFrame]
M --> B
核心算法原理 & 具体操作步骤
1. Canvas基础操作(小程序版)
小程序的Canvas分为”传统Canvas”(canvas组件)和”自定义组件Canvas”(wx.createCanvasContext),本文使用更灵活的后者。
关键API说明:
wx.createCanvasContext('canvasId'):创建画布上下文(相当于拿到画笔)
ctx.beginPath():开始绘制路径
ctx.arc(x, y, r, sAngle, eAngle):画圆(x/y圆心坐标,r半径)
ctx.fillStyle:设置填充颜色
ctx.fill():填充路径
ctx.clearRect(x, y, width, height):清空指定区域(x/y起点,width/height尺寸)
示例:用Canvas画一个红色小球
// 在Page的onLoad中初始化
onLoad() {
this.ctx = wx.createCanvasContext('gameCanvas'); // 获取画布上下文
this.drawBall(100, 100); // 在(100,100)位置画小球
},
drawBall(x, y) {
this.ctx.beginPath();
this.ctx.arc(x, y, 20, 0, 2 * Math.PI); // 圆心(100,100),半径20
this.ctx.fillStyle = '#FF4D4F'; // 红色
this.ctx.fill();
this.ctx.draw(); // 提交绘制命令
}
2. 游戏循环实现
游戏循环的核心是通过requestAnimationFrame实现”更新→绘制”的无限循环,目标是保持60FPS(每16ms执行一次)。
代码结构:
startGameLoop() {
// 更新阶段:处理逻辑
this.update();
// 绘制阶段:渲染画面
this.draw();
// 循环调用(告诉浏览器下一次重绘前执行)
this.animationId = requestAnimationFrame(() => this.startGameLoop());
}
// 游戏停止时需要取消循环(避免内存泄漏)
stopGameLoop() {
cancelAnimationFrame(this.animationId);
}
3. 碰撞检测(矩形包围盒算法)
假设小球和星星都是矩形(即使它们画成圆形,也可以用外接矩形检测),判断两个矩形是否相交的条件:
{ 小球左边界 < 星星右边界 小球右边界 > 星星左边界 小球上边界 < 星星下边界 小球下边界 > 星星上边界 egin{cases} 小球左边界 < 星星右边界 \ 小球右边界 > 星星左边界 \ 小球上边界 < 星星下边界 \ 小球下边界 > 星星上边界 end{cases} ⎩
⎨
⎧小球左边界<星星右边界小球右边界>星星左边界小球上边界<星星下边界小球下边界>星星上边界
代码实现:
// 定义物体的结构(x,y为中心点坐标,width/height为尺寸)
const ball = {
x: 100, y: 100, width: 40, height: 40 };
const star = {
x: 200, y: 200, width: 30, height: 30 };
// 计算边界
const ballLeft = ball.x - ball.width/2;
const ballRight = ball.x + ball.width/2;
const ballTop = ball.y - ball.height/2;
const ballBottom = ball.y + ball.height/2;
const starLeft = star.x - star.width/2;
const starRight = star.x + star.width/2;
const starTop = star.y - star.height/2;
const starBottom = star.y + star.height/2;
// 判断碰撞
const isCollide = (
ballLeft < starRight &&
ballRight > starLeft &&
ballTop < starBottom &&
ballBottom > starTop
);
数学模型和公式 & 详细讲解 & 举例说明
1. 物体移动的速度向量
小球的移动可以用速度向量(dx, dy)表示,每次更新位置时:
x = x + d x y = y + d y x = x + dx \ y = y + dy x=x+dxy=y+dy
例如:小球初始位置(100,100),速度(5,3),则1秒后(假设60FPS,执行60次更新)的位置是:
x = 100 + 5 ∗ 60 = 400 y = 100 + 3 ∗ 60 = 280 x = 100 + 5*60 = 400 \ y = 100 + 3*60 = 280 x=100+5∗60=400y=100+3∗60=280
2. 随机生成星星的位置
为了让星星在画布内随机出现,需要计算有效范围(假设画布宽375px,高667px,星星尺寸30px):
x m i n = 15 ( 星星左边界不超出画布左边缘 ) x m a x = 375 − 15 = 360 ( 星星右边界不超出画布右边缘 ) y m i n = 15 y m a x = 667 − 15 = 652 x_{min} = 15 quad (星星左边界不超出画布左边缘) \ x_{max} = 375 – 15 = 360 quad (星星右边界不超出画布右边缘) \ y_{min} = 15 \ y_{max} = 667 – 15 = 652 xmin=15(星星左边界不超出画布左边缘)xmax=375−15=360(星星右边界不超出画布右边缘)ymin=15ymax=667−15=652
用Math.random()生成随机坐标:
x = x m i n + M a t h . r a n d o m ( ) ∗ ( x m a x − x m i n ) y = y m i n + M a t h . r a n d o m ( ) ∗ ( y m a x − y m i n ) x = x_{min} + Math.random()*(x_{max} – x_{min}) \ y = y_{min} + Math.random()*(y_{max} – y_{min}) x=xmin+Math.random()∗(xmax−xmin)y=ymin+Math.random()∗(ymax−ymin)
项目实战:代码实际案例和详细解释说明(小球吃星星游戏)
开发环境搭建
下载安装微信开发者工具
新建小程序项目(选择”不使用云服务”,后端服务暂时不需要)
在pages目录下新建game页面(会自动生成game.wxml、game.wxss、game.js、game.json四个文件)
源代码详细实现和代码解读
1. wxml结构(游戏界面)
<!-- pages/game/game.wxml -->
<view class="container">
<!-- 游戏画布(注意canvasId要和js中对应) -->
<canvas canvas-id="gameCanvas" class="game-canvas" bindtouchmove="onTouchMove" />
<!-- 分数显示 -->
<view class="score">分数:{
{score}}</view>
</view>
2. wxss样式(布局和样式)
/* pages/game/game.wxss */
.container {
position: relative;
width: 100%;
height: 100vh;
}
.game-canvas {
width: 100%;
height: 100%;
background: #F0F2F5; /* 浅灰色背景 */
}
.score {
position: absolute;
top: 20rpx;
left: 20rpx;
font-size: 32rpx;
color: #333;
font-weight: bold;
}
3. js逻辑(核心游戏代码)
// pages/game/game.js
Page({
data: {
score: 0, // 当前分数
ball: {
x: 187.5, y: 333.5, r: 20, color: '#FF4D4F' }, // 小球(圆心x/y,半径,颜色)
stars: [], // 星星数组(每个元素包含x/y/r/color)
canvasWidth: 375, // 画布宽度(根据手机屏幕适配)
canvasHeight: 667, // 画布高度
starSize: 15, // 星星半径
starColor: '#FFD700', // 星星颜色
isGameRunning: false // 游戏是否运行中
},
onLoad() {
// 获取设备屏幕尺寸(适配不同手机)
wx.getSystemInfo({
success: (res) => {
this.setData({
canvasWidth: res.windowWidth,
canvasHeight: res.windowHeight
});
// 初始化小球位置(屏幕中心)
this.data.ball.x = res.windowWidth / 2;
this.data.ball.y = res.windowHeight / 2;
}
});
// 创建画布上下文
this.ctx = wx.createCanvasContext('gameCanvas');
},
// 开始游戏(绑定到页面的"开始游戏"按钮,这里简化为onShow时自动开始)
startGame() {
this.setData({
score: 0, stars: [] }); // 重置分数和星星
this.data.isGameRunning = true;
this.generateStar(); // 生成第一个星星
this.startGameLoop(); // 启动游戏循环
},
// 生成随机星星
generateStar() {
const {
canvasWidth, canvasHeight, starSize, starColor } = this.data;
const star = {
x: Math.random() * (canvasWidth - 2 * starSize) + starSize, // x范围:[starSize, canvasWidth - starSize]
y: Math.random() * (canvasHeight - 2 * starSize) + starSize, // y范围:[starSize, canvasHeight - starSize]
r: starSize,
color: starColor
};
this.data.stars.push(star);
},
// 游戏循环(更新→绘制)
startGameLoop() {
if (!this.data.isGameRunning) return;
// 更新阶段
this.update();
// 绘制阶段
this.draw();
// 递归调用(保持60FPS)
this.animationId = requestAnimationFrame(() => this.startGameLoop());
},
// 更新游戏状态
update() {
// 碰撞检测:检查小球和所有星星
const {
ball, stars } = this.data;
const newStars = stars.filter(star => {
// 计算小球和星星的包围盒
const ballLeft = ball.x - ball.r;
const ballRight = ball.x + ball.r;
const ballTop = ball.y - ball.r;
const ballBottom = ball.y + ball.r;
const starLeft = star.x - star.r;
const starRight = star.x + star.r;
const starTop = star.y - star.r;
const starBottom = star.y + star.r;
// 判断是否碰撞
const isCollide = (
ballLeft < starRight &&
ballRight > starLeft &&
ballTop < starBottom &&
ballBottom > starTop
);
if (isCollide) {
this.setData({
score: this.data.score + 1 }); // 分数+1
this.generateStar(); // 生成新星星
}
return !isCollide; // 保留未碰撞的星星
});
this.data.stars = newStars;
},
// 绘制所有物体
draw() {
const {
ctx, ball, stars, canvasWidth, canvasHeight } = this.data;
// 清空画布(覆盖整个屏幕)
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// 绘制小球
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, 2 * Math.PI);
ctx.fillStyle = ball.color;
ctx.fill();
// 绘制所有星星
stars.forEach(star => {
ctx.beginPath();
ctx.arc(star.x, star.y, star.r, 0, 2 * Math.PI);
ctx.fillStyle = star.color;
ctx.fill();
});
// 提交绘制命令
ctx.draw();
},
// 处理触摸移动事件(控制小球移动)
onTouchMove(e) {
const {
touches } = e;
if (touches.length > 0) {
// 将触摸坐标赋值给小球(限制在画布内)
const x = Math.max(this.data.ball.r, Math.min(touches[0].x, this.data.canvasWidth - this.data.ball.r));
const y = Math.max(this.data.ball.r, Math.min(touches[0].y, this.data.canvasHeight - this.data.ball.r));
this.data.ball.x = x;
this.data.ball.y = y;
}
},
onUnload() {
// 页面卸载时停止循环(避免内存泄漏)
this.data.isGameRunning = false;
cancelAnimationFrame(this.animationId);
}
});
代码解读与分析
初始化:在onLoad中获取屏幕尺寸,初始化小球位置到屏幕中心
游戏循环:通过requestAnimationFrame实现60FPS的更新绘制循环
碰撞检测:使用矩形包围盒算法过滤被吃掉的星星,同时增加分数并生成新星星
触摸控制:通过bindtouchmove事件监听手指移动,实时更新小球位置(限制在画布内防止越界)
绘制逻辑:每次循环先清空画布,再重新绘制小球和所有未被吃掉的星星
实际应用场景
轻量级游戏:如”跳一跳”、”飞机大战”等,适合用Canvas快速实现2D玩法
互动营销活动:品牌可以开发H5小游戏嵌入小程序,通过Canvas实现抽奖、拼图等互动效果
教育类应用:用Canvas开发数学/物理可视化工具(如小球碰撞模拟、几何图形绘制)
数据可视化:虽然不是游戏,但Canvas的高性能绘图能力可以用于实时数据图表(如股票走势)
工具和资源推荐
官方文档:微信小程序Canvas文档(必看!)
学习网站:MDN Canvas教程(理解HTML5 Canvas的通用特性)
性能优化:小程序性能优化指南(避免Canvas绘制卡顿)
扩展框架:LayaAir(支持小程序的游戏引擎,适合复杂游戏开发)
未来发展趋势与挑战
趋势
WebGL支持:小程序已逐步开放WebGL能力(如wx.createWebGLContext),未来可以开发3D游戏
跨端复用:基于uni-app等框架,Canvas代码可以同时运行在微信/支付宝/抖音等多端小程序
AI互动:结合小程序的AI能力(如手势识别、图像识别),开发更智能的游戏交互
挑战
性能限制:小程序的JS引擎和渲染线程分离,频繁的Canvas绘制可能导致卡顿(需优化绘制频率)
跨设备适配:不同手机的屏幕尺寸、GPU性能差异大,需要做好坐标适配和性能降级
用户留存:轻量级游戏用户流失快,需要设计更吸引人的玩法和社交分享机制
总结:学到了什么?
核心概念回顾
Canvas:小程序的2D绘图工具,通过createCanvasContext操作
游戏循环:通过requestAnimationFrame实现”更新→绘制”的无限循环,保证画面流畅
碰撞检测:用矩形包围盒算法判断物体是否接触,是游戏逻辑的关键
概念关系回顾
Canvas负责”画”,游戏循环负责”控制画的频率”,碰撞检测负责”判断画中的物体是否互动”。三者就像电影的”摄影师”(Canvas)、“导演”(游戏循环)和”动作指导”(碰撞检测),共同完成游戏的呈现。
思考题:动动小脑筋
现在的游戏中,星星只会被生成一次(吃到一个生成一个)。如果想让星星每隔2秒自动生成一个(不管是否被吃到),应该怎么修改代码?(提示:使用setInterval)
小球现在只能通过触摸移动,如果想增加”自动移动+重力感应控制”的混合模式,需要用到小程序的哪些API?(提示:wx.onAccelerometerChange)
游戏的分数现在只是显示数字,如果想添加”吃到星星时弹出+1动画”,应该如何用Canvas实现?(提示:记录动画的位置和透明度,在绘制阶段额外绘制)
附录:常见问题与解答
Q:Canvas绘制卡顿怎么办?
A:1. 减少绘制次数(保持60FPS即可,不要过度绘制);2. 合并绘制操作(比如先绘制所有星星再绘制小球);3. 避免在绘制循环中执行复杂计算(移到更新阶段)。
Q:为什么触摸移动时小球会抖动?
A:可能是触摸事件的坐标更新频率和游戏循环不同步。可以尝试在onTouchMove中直接更新小球位置,而不是在更新阶段处理。
Q:如何让星星有动画效果(比如闪烁)?
A:在星星对象中添加alpha属性(透明度),在更新阶段修改alpha值(0→1→0循环),绘制时设置ctx.globalAlpha = star.alpha。
扩展阅读 & 参考资料
《HTML5 Canvas核心技术》(出版书籍,深入理解Canvas绘图原理)
微信小程序游戏开发官方示例(官方提供的游戏Demo)
游戏循环的三种实现方式(深入理解游戏循环的时间控制)

















暂无评论内容