Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理
文章目录
- [Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理](#Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理)
-
- 一、前言
- 二、碰撞检测概述
-
- [2.1 墙壁碰撞](#2.1 墙壁碰撞)
- [2.2 自身碰撞](#2.2 自身碰撞)
- [2.3 检测时机](#2.3 检测时机)
- 三、墙壁碰撞检测
-
- [3.1 边界判断算法](#3.1 边界判断算法)
- [3.2 坐标越界示例](#3.2 坐标越界示例)
- [3.3 代码实现](#3.3 代码实现)
- 四、自身碰撞检测
-
- [4.1 蛇身遍历算法](#4.1 蛇身遍历算法)
- [4.2 头部与身体比较](#4.2 头部与身体比较)
- [4.3 时间复杂度分析](#4.3 时间复杂度分析)
- 五、碰撞响应机制
-
- [5.1 游戏结束状态设置](#5.1 游戏结束状态设置)
- [5.2 定时器清理流程](#5.2 定时器清理流程)
- [5.3 UI状态更新](#5.3 UI状态更新)
- 六、完整代码实现
- 七、测试用例
- 八、总结
- 社区支持
一、前言
碰撞检测是游戏开发中的核心机制。在贪吃蛇游戏中,需要检测两种碰撞:撞墙(边界碰撞)和撞自己(自身碰撞)。本文将详细讲解这两种碰撞的检测算法、游戏结束处理流程以及状态管理。
二、碰撞检测概述
贪吃蛇游戏中需要检测的碰撞类型:
2.1 墙壁碰撞
- 蛇头超出网格边界
- x坐标 < 0 或 x ≥ 30
- y坐标 < 0 或 y ≥ 20
2.2 自身碰撞
- 蛇头与蛇身任意一节坐标重合
- 不包括蛇头本身(索引0)
2.3 检测时机
每次蛇移动后,在插入新头部之前检测:
dart
void _update() {
Point newHead = _getNewHead(); // 计算新头部位置
if (_checkCollision(newHead)) { // 碰撞检测
_gameOver();
return;
}
snake.insert(0, newHead); // 无碰撞才插入
}
三、墙壁碰撞检测
3.1 边界判断算法
dart
bool _checkCollision(Point point) {
// 检查墙壁碰撞
if (point.x < 0 || point.x >= gridWidth ||
point.y < 0 || point.y >= gridHeight) {
return true;
}
// 检查自身碰撞(稍后讲解)
// ...
return false;
}
判断条件分析:
| 条件 | 含义 | 示例 |
|---|---|---|
| point.x < 0 | 超出左边界 | x=-1 |
| point.x >= gridWidth | 超出右边界 | x=30 |
| point.y < 0 | 超出上边界 | y=-1 |
| point.y >= gridHeight | 超出下边界 | y=20 |
为什么使用>=而不是>?
- 有效坐标范围:0到gridWidth-1(即0到29)
- gridWidth=30时,x=30已经超出边界
- 所以判断条件是 x >= 30
3.2 坐标越界示例
dart
// 示例1:蛇头在右边缘,向右移动
Point head = Point(29, 10); // 最右边
direction = Direction.right;
Point newHead = Point(30, 10); // 越界!
_checkCollision(newHead); // 返回true
// 示例2:蛇头在上边缘,向上移动
Point head = Point(15, 0); // 最上边
direction = Direction.up;
Point newHead = Point(15, -1); // 越界!
_checkCollision(newHead); // 返回true
3.3 代码实现
完整的墙壁碰撞检测:
dart
bool _checkCollision(Point point) {
// 墙壁碰撞检测
if (point.x < 0 || point.x >= gridWidth ||
point.y < 0 || point.y >= gridHeight) {
return true;
}
// 自身碰撞检测
for (var segment in snake) {
if (segment.x == point.x && segment.y == point.y) {
return true;
}
}
return false;
}

四、自身碰撞检测
4.1 蛇身遍历算法
dart
// 自身碰撞检测
for (var segment in snake) {
if (segment.x == point.x && segment.y == segment.y) {
return true; // 发现碰撞
}
}
算法说明:
- 遍历snake列表中的每一节
- 将新头部坐标与每节坐标比较
- 如果x和y都相等,说明碰撞
- 返回true表示碰撞
4.2 头部与身体比较
重要: 蛇头移动到的新位置,可能与蛇身的任意一节重合。
dart
// 示例:蛇身坐标
snake = [
Point(5, 10), // 索引0:当前头部
Point(4, 10), // 索引1:第二节
Point(3, 10), // 索引2:第三节
];
// 蛇向右移动,新头部
Point newHead = Point(6, 10);
// 检测:newHead与snake[0/1/2]比较
// (6,10) vs (5,10) → 不同
// (6,10) vs (4,10) → 不同
// (6,10) vs (3,10) → 不同
// 结果:无碰撞
碰撞示例:
dart
// 蛇绕成一圈
snake = [
Point(5, 10),
Point(5, 11),
Point(5, 12),
Point(6, 12),
Point(7, 12),
];
// 蛇头向上移动
Point newHead = Point(5, 9);
// 然后向右、向下移动...
Point newHead = Point(7, 11);
// 最终撞到自己的身体:(7,11) 与 (7,12) 相邻但y不同

4.3 时间复杂度分析
dart
for (var segment in snake) { // O(n)
if (segment.x == point.x && segment.y == segment.y) {
return true;
}
}
时间复杂度:O(n)
- n = snake.length(蛇的长度)
- 最坏情况:遍历整个蛇身
- 最好情况:第一节就碰撞(不太可能,因为蛇头不会撞到当前头部位置)
优化思路:
- 蛇较短时(<100节),O(n)可接受
- 蛇很长时,可以用HashSet存储身体坐标,O(1)查询
- 但贪吃蛇游戏中蛇一般不会太长,O(n)足够
五、碰撞响应机制
5.1 游戏结束状态设置
dart
void _gameOver() {
isGameOver = true;
gameTimer?.cancel();
setState(() {});
}
状态变化:
- isGameOver:false → true
- 停止定时器
- 更新UI显示游戏结束信息
5.2 定时器清理流程
dart
void _gameOver() {
isGameOver = true; // 标记游戏结束
gameTimer?.cancel(); // 取消定时器
setState(() {}); // 触发UI更新
}
为什么必须取消定时器?
- 定时器会持续触发_update()
- 如果不取消,会不断检测碰撞
- 导致_gameOver()被重复调用
- 浪费CPU资源
定时器清理时机:
- 游戏结束时:
gameTimer?.cancel(); - 速度变化时:先cancel再重新创建
- 组件销毁时:dispose中cancel

5.3 UI状态更新
dart
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: CustomPaint(
painter: GamePainter(...),
),
),
// 游戏结束时显示
if (isGameOver)
Container(
child: Column(
children: [
Text('游戏结束!'),
Text('最终得分: $score'),
ElevatedButton(
onPressed: () => _initGame(),
child: Text('重新开始'),
),
],
),
),
],
),
);
}
UI变化:
- 游戏区域:定格在最后状态
- 游戏结束面板:从隐藏变为显示
- 控制按钮:隐藏(isGameOver为true时不显示)
六、完整代码实现
dart
class _GameHomePageState extends State<GameHomePage> {
// ...
bool _checkCollision(Point point) {
// 墙壁碰撞检测
if (point.x < 0 || point.x >= gridWidth ||
point.y < 0 || point.y >= gridHeight) {
return true;
}
// 自身碰撞检测
for (var segment in snake) {
if (segment.x == point.x && segment.y == point.y) {
return true;
}
}
return false;
}
void _gameOver() {
isGameOver = true;
gameTimer?.cancel();
setState(() {});
}
void _update() {
if (nextDirection != null) {
direction = nextDirection!;
}
Point newHead = _getNewHead();
// 碰撞检测
if (_checkCollision(newHead)) {
_gameOver();
return;
}
snake.insert(0, newHead);
if (newHead.x == food!.x && newHead.y == food!.y) {
score += 10;
if (speed > 80) {
speed -= 5;
gameTimer?.cancel();
_startGame();
}
_spawnFood();
} else {
snake.removeLast();
}
setState(() {});
}
}
七、测试用例
测试1:墙壁碰撞
dart
// 边界测试
test('右边界碰撞', () {
snake = [Point(29, 10)];
direction = Direction.right;
Point newHead = Point(30, 10);
expect(_checkCollision(newHead), true);
});
test('上边界碰撞', () {
snake = [Point(15, 0)];
direction = Direction.up;
Point newHead = Point(15, -1);
expect(_checkCollision(newHead), true);
});
测试2:自身碰撞
dart
test('撞到身体', () {
snake = [
Point(5, 10),
Point(4, 10),
Point(3, 10),
];
// 蛇头移动到(3,10),与第三节重合
Point newHead = Point(3, 10);
expect(_checkCollision(newHead), true);
});
八、总结
本文讲解了碰撞检测算法:
- 墙壁碰撞:检测坐标是否超出0-29和0-19范围
- 自身碰撞:遍历蛇身,比较坐标
- 游戏结束:设置标志、取消定时器、更新UI
- 时间复杂度:O(n),n为蛇身长度
关键要点:
- 碰撞检测在移动前执行,防止无效移动
- 定时器必须清理,避免资源浪费
- 使用isGameOver标志控制UI状态
下篇预告:《Flutter for OpenHarmony 实战:食物生成算法与难度递增系统》
社区支持
欢迎加入开源 OpenHarmony 跨平台社区,获取更多技术支持和资源:
- 社区论坛 :开源 OpenHarmony 跨平台开发者社区
- 技术交流:参与社区讨论,分享开发经验
如果本文对您有帮助,欢迎点赞、收藏和评论。您的支持是我持续创作的动力!