1. Flutter游戏开发实战:CustomPaint绘制俄罗斯方块全解析
作为一名移动端开发者,我最近尝试用Flutter开发一款经典的俄罗斯方块游戏。在上一篇文章中,我们已经完成了游戏的核心数据结构和逻辑算法。今天,我将重点分享如何使用Flutter的CustomPaint组件来实现游戏画面的绘制,这是游戏开发中最关键也最具挑战性的部分之一。
1.1 为什么选择CustomPaint?
在开始编码之前,我首先评估了多种实现方案。最直观的想法是使用常规的Flutter Widget(如Container、Row、Column等)来构建游戏界面。但经过实践发现,这种方式存在几个严重问题:
-
性能瓶颈:一个标准的俄罗斯方块棋盘有20行×10列=200个格子,如果用Widget树实现,每次更新都需要重建整个Widget结构,这在60fps的游戏场景下会造成严重的性能问题。
-
灵活性不足:游戏需要精确控制每个像素的绘制效果,包括颜色渐变、边框样式、阴影效果等,这些在常规Widget体系中实现起来非常困难。
-
代码复杂度高:嵌套200个Widget会导致代码结构极其复杂,难以维护和扩展。
相比之下,CustomPaint提供了直接访问Canvas的能力,让我们可以像在原生开发中一样自由绘制图形。下面是一个简单的性能对比:
| 特性 | Widget实现 | CustomPaint实现 |
|---|---|---|
| 绘制性能 | 较差(Widget树重建) | 优秀(直接Canvas绘制) |
| 图形控制精度 | 有限 | 像素级精确控制 |
| 代码复杂度 | 高(嵌套层级深) | 低(集中绘制逻辑) |
| 特殊效果实现难度 | 困难 | 容易 |
| 适合场景 | 常规UI界面 | 游戏、图表等复杂图形场景 |
1.2 CustomPaint核心概念解析
在深入代码之前,我们需要理解几个核心概念:
Canvas:可以理解为一块画布,提供了各种绘制方法(如drawRect、drawCircle等)。
Paint:定义了绘制的样式,包括颜色、线条粗细、填充模式等。
CustomPainter:自定义绘制的主要类,需要实现paint()和shouldRepaint()方法。
下面是一个最简单的CustomPaint使用示例:
dart复制class SimplePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// 使用
CustomPaint(
painter: SimplePainter(),
size: Size(200, 200),
)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 游戏棋盘绘制实现
2.1 棋盘Painter类设计
我们首先创建一个专门用于绘制游戏棋盘的GameBoardPainter类:
dart复制class GameBoardPainter extends CustomPainter {
final List<List<int>> board; // 棋盘状态数据
final List<List<int>>? currentPiece; // 当前下落方块
final int currentX; // 当前方块X坐标
final int currentY; // 当前方块Y坐标
final double cellPadding = 1.0; // 格子间距
GameBoardPainter({
required this.board,
this.currentPiece,
this.currentX = 0,
this.currentY = 0,
});
@override
void paint(Canvas canvas, Size size) {
_drawBackground(canvas, size);
_drawGrid(canvas, size);
_drawFixedBlocks(canvas, size);
_drawCurrentPiece(canvas, size);
}
@override
bool shouldRepaint(covariant GameBoardPainter oldDelegate) {
return board != oldDelegate.board ||
currentPiece != oldDelegate.currentPiece ||
currentX != oldDelegate.currentX ||
currentY != oldDelegate.currentY;
}
// 其他绘制方法将在下面实现...
}
2.2 背景与网格绘制
首先实现背景和网格线的绘制:
dart复制void _drawBackground(Canvas canvas, Size size) {
final bgPaint = Paint()..color = Color(0xFF1E1E1E);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPaint);
}
void _drawGrid(Canvas canvas, Size size) {
final cellWidth = size.width / board[0].length;
final cellHeight = size.height / board.length;
final gridPaint = Paint()
..color = Color(0xFF2D2D2D)
..style = PaintingStyle.stroke
..strokeWidth = 0.5;
for (int y = 0; y <= board.length; y++) {
// 横线
canvas.drawLine(
Offset(0, y * cellHeight),
