Flutter for OpenHarmony游戏集合App实战之2048方块颜色
通过网盘分享的文件:game_flutter_openharmony.zip
链接: https://pan.baidu.com/s/1ryUS1A0zcvXGrDaStu530w 提取码: tqip
前言
2048是一个数字合并游戏,滑动方块,相同数字合并翻倍。这个游戏有个特点:不同数字的方块颜色不一样。
2是浅色,4稍微深一点,8、16、32越来越深,到2048就是金黄色。这种颜色渐变让玩家一眼就能看出方块的大小。
这篇来聊聊2048的方块颜色怎么实现。
方块的基本结构
Widget _buildTile(int value) {
return Container(
decoration: BoxDecoration(color: _getTileColor(value), borderRadius: BorderRadius.circular(4)),
child: Center(
child: Text(value == 0 ? '' : '$value',
style: TextStyle(fontSize: value > 512 ? 20 : 28, fontWeight: FontWeight.bold, color: value <= 4 ? Colors.grey[700] : Colors.white)),
),
);
}
每个方块是一个Container,里面放数字。
圆角
borderRadius: BorderRadius.circular(4),
4像素的小圆角,让方块看起来柔和一些。
数字显示
Text(value == 0 ? '' : '$value',
值为0时不显示(空格子),否则显示数字。
字号自适应
fontSize: value > 512 ? 20 : 28,
数字大了位数多,字号要小一点才能放得下。
512以下用28号字,512以上用20号字。1024、2048这些四位数用小字号。
💡 512这个分界点是试出来的。一开始用1000,但1024显示不全。改成512刚好。
文字颜色
color: value <= 4 ? Colors.grey[700] : Colors.white,
2和4的方块颜色很浅,用深灰色文字。8以上的方块颜色深,用白色文字。
这样保证文字在任何背景上都清晰可读。
_getTileColor方法
颜色映射的核心:
Color _getTileColor(int value) {
return {0: Colors.brown[200], 2: Colors.orange[50], 4: Colors.orange[100], 8: Colors.orange[300],
16: Colors.orange[400], 32: Colors.orange[500], 64: Colors.orange[600], 128: Colors.yellow[300],
256: Colors.yellow[400], 512: Colors.yellow[500], 1024: Colors.yellow[600], 2048: Colors.yellow[700],
}[value] ?? Colors.yellow[800]!;
}
用Map把数字映射到颜色。
空格子
0: Colors.brown[200],
空格子用浅棕色,和背景接近但能区分。
2和4
2: Colors.orange[50],
4: Colors.orange[100],
最浅的橙色,几乎是白色。这是游戏开始时最常见的方块。
8到64
8: Colors.orange[300],
16: Colors.orange[400],
32: Colors.orange[500],
64: Colors.orange[600],
橙色逐渐加深。这个阶段是游戏中期,方块开始有明显颜色了。
128到2048
128: Colors.yellow[300],
256: Colors.yellow[400],
512: Colors.yellow[500],
1024: Colors.yellow[600],
2048: Colors.yellow[700],
切换到黄色系,代表高分方块。2048是金黄色,很有成就感。
超过2048
}[value] ?? Colors.yellow[800]!;
如果数字不在Map里(比如4096、8192),用最深的黄色。
??是空值合并运算符,Map里没找到就用后面的默认值。
颜色渐变的设计思路
为什么用这套颜色?
- 从浅到深:数字越大颜色越深,直观
- 橙色到黄色:暖色调,有活力
- 和原版一致:2048原版游戏就是这个配色,玩家熟悉
颜色不是随便选的,是参考原版游戏调的。
棋盘背景
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.brown[300], borderRadius: BorderRadius.circular(8)),
棋盘用棕色背景,和方块的橙黄色形成对比。
margin和padding
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(8),
margin让棋盘不贴着屏幕边缘,padding让方块不贴着棋盘边缘。
圆角
borderRadius: BorderRadius.circular(8),
棋盘圆角比方块大一点(8 vs 4),层次分明。
GridView布局
GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4, mainAxisSpacing: 8, crossAxisSpacing: 8),
itemCount: 16,
itemBuilder: (_, i) => _buildTile(grid[i ~/ 4][i % 4]),
),
禁止滚动
physics: const NeverScrollableScrollPhysics(),
2048的棋盘是固定的,不需要滚动。
4x4网格
crossAxisCount: 4,
每行4个方块。
间距
mainAxisSpacing: 8,
crossAxisSpacing: 8,
方块之间8像素间距,露出棕色背景,形成网格线效果。
索引转换
grid[i ~/ 4][i % 4]
GridView用线性索引(0-15),要转成二维坐标访问grid数组。
i ~/ 4是行号,i % 4是列号。
AspectRatio保持正方形
AspectRatio(
aspectRatio: 1,
child: Container(
棋盘必须是正方形,用AspectRatio强制1:1比例。
分数显示
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('分数: $score', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
if (gameOver) const Text('游戏结束!', style: TextStyle(fontSize: 20, color: Colors.red)),
],
),
),
分数在棋盘上方,大字号加粗。
游戏结束时显示红色提示。
数据结构
static const int gridSize = 4;
late List<List<int>> grid;
4x4的二维数组,存储每个格子的数字。0表示空。
初始化
void _initGame() {
grid = List.generate(gridSize, (_) => List.filled(gridSize, 0));
score = 0;
gameOver = false;
_addRandomTile();
_addRandomTile();
}
全部填0,然后随机放两个方块。
添加随机方块
void _addRandomTile() {
List<Point<int>> empty = [];
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
if (grid[i][j] == 0) empty.add(Point(i, j));
}
}
if (empty.isEmpty) return;
Point<int> pos = empty[random.nextInt(empty.length)];
grid[pos.x][pos.y] = random.nextDouble() < 0.9 ? 2 : 4;
}
找出所有空格子,随机选一个,90%概率放2,10%概率放4。
这是2048的标准规则。
颜色的可扩展性
如果想支持更大的数字,可以用计算的方式:
Color _getTileColor(int value) {
if (value == 0) return Colors.brown[200]!;
int log = (log2(value)).floor(); // 2->1, 4->2, 8->3, ...
if (log <= 6) {
return Colors.orange[(log * 100).clamp(50, 600)]!;
} else {
return Colors.yellow[((log - 6) * 100 + 300).clamp(300, 900)]!;
}
}
但Map的方式更直观,而且2048游戏很少能玩到4096以上。
颜色的心理学
为什么2048用橙黄色系?这不是随便选的。
暖色调的活力
橙色和黄色都是暖色调,给人温暖、活力、积极的感觉。
玩2048是一个不断挑战、不断进步的过程,暖色调能激发玩家的斗志。
如果用冷色调(蓝色、绿色),游戏会显得冷静、理性,少了一些激情。
金色的成就感
2048方块是金黄色,这是有意为之的。
金色在人类文化中代表"珍贵"、“成功”、“胜利”。当玩家终于合成2048时,看到金黄色的方块,成就感油然而生。
这是游戏设计中的正向反馈,用视觉奖励玩家的努力。
渐变的进度感
从浅橙到深黄的渐变,让玩家有一种"进度"的感觉。
颜色越深,说明数字越大,离目标越近。这种视觉反馈帮助玩家判断当前局势。
颜色与可访问性
设计颜色时要考虑色盲用户。
红绿色盲
最常见的色盲是红绿色盲,患者难以区分红色和绿色。
2048用的是橙黄色系,不涉及红绿对比,对红绿色盲用户友好。
对比度
文字和背景的对比度要足够高,否则看不清。
color: value <= 4 ? Colors.grey[700] : Colors.white,
浅背景用深色字(grey[700]),深背景用白色字,保证对比度。
WCAG(Web内容无障碍指南)建议正文对比度至少4.5:1。虽然这是Web标准,但移动端也可以参考。
不只依赖颜色
好的设计不只依赖颜色传递信息。2048的方块上有数字,即使看不清颜色,也能通过数字知道方块的值。
这是冗余设计,用多种方式传递同一信息,提高可访问性。
动画效果
当前实现没有动画,方块是瞬间出现的。如果想加动画,可以用AnimatedContainer:
AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: _getTileColor(value),
borderRadius: BorderRadius.circular(4),
),
child: Center(child: Text(...)),
)
AnimatedContainer会自动对属性变化做动画。当颜色变化时(比如2变成4),会有平滑过渡。
缩放动画
新方块出现时可以加缩放动画:
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 200),
builder: (context, scale, child) {
return Transform.scale(scale: scale, child: child);
},
child: _buildTile(value),
)
方块从0缩放到1,有一个"弹出"的效果。
但动画会增加复杂度,当前实现简化了。
主题切换
如果想支持多种配色主题,可以把颜色映射抽成配置:
class TileTheme {
final Map<int, Color> colors;
final Color textLightColor;
final Color textDarkColor;
const TileTheme({
required this.colors,
required this.textLightColor,
required this.textDarkColor,
});
}
final classicTheme = TileTheme(
colors: {0: Colors.brown[200]!, 2: Colors.orange[50]!, ...},
textLightColor: Colors.grey[700]!,
textDarkColor: Colors.white,
);
final darkTheme = TileTheme(
colors: {0: Colors.grey[800]!, 2: Colors.blueGrey[700]!, ...},
textLightColor: Colors.white70,
textDarkColor: Colors.white,
);
然后根据用户选择的主题使用不同的TileTheme。
这样可以支持经典主题、暗黑主题、护眼主题等多种配色。
性能优化
const优化
borderRadius: BorderRadius.circular(4),
这行代码每次build都会创建新的BorderRadius对象。可以优化成:
static final _borderRadius = BorderRadius.circular(4);
...
borderRadius: _borderRadius,
用static final缓存,避免重复创建。
颜色缓存
Color _getTileColor(int value) {
return {...}[value] ?? Colors.yellow[800]!;
}
每次调用都创建一个新的Map。可以优化成:
static final _tileColors = {
0: Colors.brown[200]!,
2: Colors.orange[50]!,
// ...
};
Color _getTileColor(int value) {
return _tileColors[value] ?? Colors.yellow[800]!;
}
用static final缓存Map,只创建一次。
对于2048这种简单游戏,这些优化不是必须的,但养成好习惯有益无害。
小结
这篇讲了2048的方块颜色,核心知识点:
- Map颜色映射:数字到颜色的对应关系,简洁直观
- 颜色渐变:从浅橙到深黄,数字越大越深,有进度感
- 文字颜色适配:浅背景用深色字,深背景用白色字,保证对比度
- 字号自适应:大数字用小字号,确保显示完整
- GridView布局:4x4网格,8像素间距,形成网格线效果
- AspectRatio:保持棋盘正方形,不会因屏幕比例变形
- 空值合并运算符:处理Map中不存在的key,提供默认值
- 颜色心理学:暖色调激发斗志,金色带来成就感
- 可访问性:考虑色盲用户,保证对比度,不只依赖颜色
颜色是2048的重要视觉元素,好的配色让游戏更有层次感,也能给玩家带来更好的体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)