Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解

Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解

文章目录

  • [Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解](#Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解)

一、前言

在贪吃蛇游戏中,蛇的移动是最核心的机制。本文将详细讲解如何在Flutter for OpenHarmony中实现蛇的自动移动,包括坐标系统设计、Timer定时器使用、方向控制算法以及防自杀机制。

二、坐标系统设计

2.1 30x20网格坐标系

我们采用长方形网格,而非传统的正方形:

dart 复制代码
static const int gridWidth = 30;   // 30列
static const int gridHeight = 20;  // 20行

为什么选择长方形?

  • 更符合现代手机屏幕比例(16:9)
  • 游戏空间更大(30×20=600格,比20×20=400格多50%)
  • 增加游戏趣味性

坐标规则:

  • 原点(0,0)在左上角
  • x轴向右增长(0→29)
  • y轴向下增长(0→19)
  • 每个坐标点对应一个格子

2.2 坐标与像素映射

在CustomPainter中,坐标需要映射到实际像素:

dart 复制代码
final cellWidth = size.width / gridWidth;    // 每格宽度
final cellHeight = size.height / gridHeight; // 每格高度

示例计算:

  • 如果Canvas宽度为360px,则每格宽 = 360/30 = 12px
  • 如果Canvas高度为240px,则每格高 = 240/20 = 12px

2.3 Point类实现

dart 复制代码
class Point {
  final int x;
  final int y;

  Point(this.x, this.y);
}

使用示例:

dart 复制代码
// 创建蛇头位置
Point head = Point(5, 10);

// 创建食物位置
Point food = Point(15, 8);

三、Timer定时器实现自动移动

3.1 Timer.periodic原理

Flutter的Timer类用于定时任务:

dart 复制代码
void _startGame() {
  gameTimer?.cancel();  // 先取消之前的定时器
  gameTimer = Timer.periodic(Duration(milliseconds: speed), (timer) {
    if (!isPaused && !isGameOver) {
      _update();  // 每200ms执行一次更新
    }
  });
}

Timer.periodic工作原理:

  • 创建一个周期性定时器
  • 每隔指定时间间隔执行回调函数
  • 返回Timer对象,可用于取消定时器

3.2 速度控制变量

dart 复制代码
int speed = 200;  // 初始速度200ms

速度变化规则:

  • 初始:200ms/次(每秒5格)
  • 吃到食物:speed -= 5(加速5ms)
  • 最快:80ms/次(每秒12.5格)
dart 复制代码
if (newHead.x == food!.x && newHead.y == food!.y) {
  score += 10;
  if (speed > 80) {  // 限制最快速度
    speed -= 5;
    gameTimer?.cancel();
    _startGame();  // 重新创建定时器
  }
  _spawnFood();
}


3.3 定时器生命周期

dart 复制代码
// 创建:游戏初始化时
@override
void initState() {
  super.initState();
  _initGame();
}

// 重建:速度变化时
gameTimer?.cancel();
gameTimer = Timer.periodic(Duration(milliseconds: speed), (timer) {...});

// 销毁:游戏结束或组件销毁时
@override
void dispose() {
  gameTimer?.cancel();  // 必须取消,防止内存泄漏
  super.dispose();
}

重要提示: 一定要在dispose中取消定时器,否则会导致内存泄漏。

四、方向控制核心算法

4.1 四方向移动计算

蛇的移动本质是计算新的头部坐标:

dart 复制代码
Point _getNewHead() {
  Point head = snake.first;  // 获取当前头部
  switch (direction) {
    case Direction.up:
      return Point(head.x, head.y - 1);  // 向上,y减1
    case Direction.down:
      return Point(head.x, head.y + 1);  // 向下,y加1
    case Direction.left:
      return Point(head.x - 1, head.y);  // 向左,x减1
    case Direction.right:
      return Point(head.x + 1, head.y);  // 向右,x加1
  }
}

坐标变化规则:

方向 x变化 y变化
0 -1
0 +1
-1 0
+1 0

4.2 nextDirection缓冲机制

问题:如果玩家快速按下"上→左",而蛇当前向右移动,会发生什么?

没有缓冲的情况:

  1. 当前方向:右
  2. 按下"上",direction变为上
  3. 按下"左",direction变为左
  4. 问题:从右直接变左是180度转向,蛇头会撞到自己的脖子

解决方案:使用nextDirection缓冲

dart 复制代码
Direction direction = Direction.right;   // 当前实际方向
Direction? nextDirection;                // 下一步预存方向

void _update() {
  if (nextDirection != null) {
    direction = nextDirection!;  // 更新时才应用新方向
  }
  // ...
}

void _changeDirection(Direction newDirection) {
  // 基于当前方向判断,防止180度转向
  if ((direction == Direction.up && newDirection != Direction.down) ||
      (direction == Direction.down && newDirection != Direction.up) ||
      (direction == Direction.left && newDirection != Direction.right) ||
      (direction == Direction.right && newDirection != Direction.left)) {
    nextDirection = newDirection;  // 存储到nextDirection
  }
}

4.3 防止180度转向判断逻辑

核心思想:只能向左或向右转90度,不能掉头。

dart 复制代码
void _changeDirection(Direction newDirection) {
  // 当前向上时,不能向下(但可以向左或向右)
  if (direction == Direction.up && newDirection == Direction.down) {
    return;  // 拒绝掉头
  }
  // 当前向下时,不能向上
  if (direction == Direction.down && newDirection == Direction.up) {
    return;
  }
  // 当前向左时,不能向右
  if (direction == Direction.left && newDirection == Direction.right) {
    return;
  }
  // 当前向右时,不能向左
  if (direction == Direction.right && newDirection == Direction.left) {
    return;
  }

  nextDirection = newDirection;  // 允许转向
}

状态转换表:

当前方向 可转向 禁止转向
左、右
左、右
上、下
上、下

五、蛇身移动实现

5.1 新头部插入算法

dart 复制代码
void _update() {
  // 1. 获取新头部位置
  Point newHead = _getNewHead();

  // 2. 检查碰撞
  if (_checkCollision(newHead)) {
    _gameOver();
    return;
  }

  // 3. 插入新头部
  snake.insert(0, newHead);  // 在索引0位置插入

  // 4. 处理食物或移除尾部
  if (newHead.x == food!.x && newHead.y == food!.y) {
    // 吃到食物:不移除尾部,蛇变长
    score += 10;
    _spawnFood();
  } else {
    // 没吃到食物:移除尾部,保持长度
    snake.removeLast();
  }

  setState(() {});  // 更新UI
}

5.2 尾部移除逻辑

为什么需要移除尾部?

蛇移动的本质是:

  • 头部增加一格
  • 尾部减少一格
  • 整体向移动方向平移一格

如果不移除尾部,蛇会不断变长,只有吃食物时才应该变长。

dart 复制代码
snake.removeLast();  // 移除列表最后一个元素(尾部)

5.3 完整_update()方法解析

dart 复制代码
void _update() {
  // 步骤1:应用缓冲的方向
  if (nextDirection != null) {
    direction = nextDirection!;
  }

  // 步骤2:计算新头部位置
  Point newHead = _getNewHead();

  // 步骤3:碰撞检测
  if (_checkCollision(newHead)) {
    _gameOver();
    return;
  }

  // 步骤4:插入新头部
  snake.insert(0, newHead);

  // 步骤5:处理食物
  if (newHead.x == food!.x && newHead.y == food!.y) {
    // 吃到食物
    score += 10;

    // 速度递增
    if (speed > 80) {
      speed -= 5;
      gameTimer?.cancel();
      _startGame();
    }

    _spawnFood();
  } else {
    // 没吃到食物,移除尾部
    snake.removeLast();
  }

  // 步骤6:更新UI
  setState(() {});
}


六、完整代码实现

以下是移动相关的核心代码:

dart 复制代码
class _GameHomePageState extends State<GameHomePage> {
  // ... 其他变量

  void _startGame() {
    gameTimer?.cancel();
    gameTimer = Timer.periodic(Duration(milliseconds: speed), (timer) {
      if (!isPaused && !isGameOver) {
        _update();
      }
    });
  }

  Point _getNewHead() {
    Point head = snake.first;
    switch (direction) {
      case Direction.up:
        return Point(head.x, head.y - 1);
      case Direction.down:
        return Point(head.x, head.y + 1);
      case Direction.left:
        return Point(head.x - 1, head.y);
      case Direction.right:
        return Point(head.x + 1, head.y);
    }
  }

  void _changeDirection(Direction newDirection) {
    if ((direction == Direction.up && newDirection != Direction.down) ||
        (direction == Direction.down && newDirection != Direction.up) ||
        (direction == Direction.left && newDirection != Direction.right) ||
        (direction == Direction.right && newDirection != Direction.left)) {
      nextDirection = newDirection;
    }
  }

  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. 坐标系统:30x20长方形网格设计
  2. Timer定时器:实现自动移动和速度控制
  3. 方向控制:nextDirection缓冲防止180度转向
  4. 移动算法:头部插入、尾部移除的经典实现

核心要点:

  • 使用List的insert(0)和removeLast()高效管理蛇身
  • nextDirection缓冲是防止快速按键导致自杀的关键
  • Timer必须在dispose中取消,避免内存泄漏

下篇预告:《Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理》

社区支持

欢迎加入开源 OpenHarmony 跨平台社区,获取更多技术支持和资源:

如果本文对您有帮助,欢迎点赞、收藏和评论。您的支持是我持续创作的动力!

相关推荐
晚霞的不甘2 小时前
Flutter for OpenHarmony:从零到一:构建购物APP的骨架与精美UI
前端·javascript·flutter·ui·前端框架·鸿蒙
kirk_wang2 小时前
Flutter艺术探索-Freezed代码生成:不可变数据模型实战
flutter·移动开发·flutter教程·移动开发教程
灵感菇_2 小时前
全面解析 Retrofit 网络框架
android·kotlin·网络请求·retrofit
李慕婉学姐2 小时前
【开题答辩过程】以《基于uniapp的养宠互助服务程序设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
android·mysql·uni-app
b2077212 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 运动分析实现
python·flutter·harmonyos
移幻漂流2 小时前
JNI的本质解析:Android Framework视角下的Java-Native衔接机制
android·java·开发语言
浪客川3 小时前
1972 GODOT 入门案例
android·java·godot
子春一3 小时前
Flutter for OpenHarmony:用 Flutter 构建一个数字猜谜游戏:从零开始的交互式应用开发
javascript·flutter·游戏
zilikew3 小时前
Flutter框架跨平台鸿蒙开发——高尔夫计分器APP的开发流程
flutter·华为·harmonyos·鸿蒙