Flutter for OpenHarmony 实战:打砖块游戏物理引擎与碰撞检测

欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区

前言

在这里插入图片描述

打砖块游戏的核心魅力在于球体运动的物理模拟和精确的碰撞检测。本文将深入分析如何实现完整的2D物理引擎,包括球体运动系统、墙壁反弹、挡板交互角度控制、砖块碰撞方向判断等核心技术,这些都是创造流畅游戏体验的基础。

一、游戏常量与物理参数

1.1 尺寸参数定义

static const double gameWidth = 400;
static const double gameHeight = 600;
static const int rows = 5;
static const int cols = 8;
static const double brickWidth = gameWidth / cols;
static const double brickHeight = 25;
static const double paddleWidth = 80;
static const double paddleHeight = 15;
static const double ballSize = 15;

游戏区域固定为400x600像素,5行8列共40块砖,每块砖宽50像素、高25像素。挡板宽80像素,球体直径15像素。这些尺寸经过精心设计,确保游戏的视觉平衡和玩节奏。

1.2 物理速度参数

late double ballVelocityX;
late double ballVelocityY;

ballVelocityX = 3;
ballVelocityY = -3;

球体初始速度为每帧3像素,垂直方向向上(负值),水平方向向右。这个速度提供了适中的游戏节奏,既不会太快让玩家反应不及,也不会太慢失去挑战性。

二、球体运动系统

2.1 位置更新机制

void updateGame() {
  ballX += ballVelocityX;
  ballY += ballVelocityY;

  if (ballX <= 0 || ballX >= gameWidth - ballSize) {
    ballVelocityX = -ballVelocityX;
  }
  if (ballY <= 0) {
    ballVelocityY = -ballVelocityY;
  }
}

每帧更新球的位置,然后检查边界碰撞。碰到左右墙壁时反转X速度,碰到顶部墙壁时反转Y速度。这种简单的速度反转实现了完美的弹性碰撞效果。

2.2 底部出界处理

在这里插入图片描述

if (ballY >= gameHeight) {
  lives--;
  if (lives <= 0) {
    gameOver = true;
    gameTimer?.cancel();
  } else {
    resetBall();
  }
}

球掉落到底部时扣除生命,生命值为0则游戏结束,否则重置球位置。这种设计给玩家多次机会,提高了游戏的宽容度。

2.3 球体重置函数

void resetBall() {
  ballX = gameWidth / 2;
  ballY = gameHeight - 100;
  ballVelocityX = 3;
  ballVelocityY = -3;
}

球回到屏幕中央上方,恢复初始速度。这个位置给玩家足够的反应时间准备接球。

三、挡板碰撞与角度控制

3.1 AABB碰撞检测

bool _checkPaddleCollision() {
  return ballY + ballSize >= gameHeight - 50 &&
      ballY + ballSize <= gameHeight - 50 + paddleHeight &&
      ballX + ballSize >= paddleX &&
      ballX <= paddleX + paddleWidth;
}

使用轴对齐包围盒(AABB)算法检测碰撞。检查球体底部是否进入挡板顶部区域,以及球体水平位置是否与挡板重叠。这种检测方法高效且精确。

3.2 碰撞响应与角度控制

if (_checkPaddleCollision()) {
  ballVelocityY = -ballVelocityY.abs();
  double hitPos = (ballX + ballSize / 2 - paddleX) / paddleWidth;
  ballVelocityX = (hitPos - 0.5) * 8;
}

碰撞时首先反转Y速度,使用abs()确保向上反弹。然后根据碰撞点位置调整X速度:hitPos为0到1之间的值,表示碰撞点在挡板上的相对位置。乘以8后X速度范围为-4到4,实现了精确的角度控制。

3.3 角度控制原理

double hitPos = (ballX + ballSize / 2 - paddleX) / paddleWidth;
ballVelocityX = (hitPos - 0.5) * 8;

hitPos计算过程:

  • 球中心X坐标:ballX + ballSize / 2
  • 减去挡板左边缘:ballX + ballSize / 2 - paddleX
  • 除以挡板宽度得到相对位置:hitPos

然后hitPos - 0.5将范围从[0,1]转换为[-0.5,0.5],乘以8得到[-4,4]的X速度。这种设计让玩家可以通过控制接球位置来改变球的反弹角度。

四、砖块碰撞检测

4.1 砖块数据结构

class Brick {
  double x;
  double y;
  double width;
  double height;
  Color color;
  bool visible;

  Brick({
    required this.x,
    required this.y,
    required this.width,
    required this.height,
    required this.color,
    this.visible = true,
  });
}

每个砖块记录位置、尺寸、颜色和可见状态。visible标志控制是否显示和参与碰撞检测,实现砖块的"消失"效果。

4.2 AABB碰撞检测算法

void _checkBrickCollisions() {
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols; col++) {
      Brick brick = bricks[row][col];
      if (!brick.visible) continue;

      if (ballX < brick.x + brick.width &&
          ballX + ballSize > brick.x &&
          ballY < brick.y + brick.height &&
          ballY + ballSize > brick.y) {
        // 处理碰撞
      }
    }
  }
}

使用AABB算法检测球与砖块重叠。四个条件分别检查球的右边缘是否超过砖块左边缘、球的左边缘是否未超过砖块右边缘、球的下边缘是否超过砖块上边缘、球的上边缘是否未超过砖块下边缘。全部满足时表示碰撞。

4.3 碰撞方向判断

double ballCenterX = ballX + ballSize / 2;
double ballCenterY = ballY + ballSize / 2;
double brickCenterX = brick.x + brick.width / 2;
double brickCenterY = brick.y + brick.height / 2;

double dx = ballCenterX - brickCenterX;
double dy = ballCenterY - brickCenterY;

if ((dx / brick.width).abs() > (dy / brick.height).abs()) {
  ballVelocityX = -ballVelocityX;
} else {
  ballVelocityY = -ballVelocityY;
}

通过计算球心和砖块中心的相对位置判断碰撞方向。归一化后比较dx和dy的绝对值,如果dx更大则认为是左右碰撞,反转X速度;否则认为是上下碰撞,反转Y速度。这种判断方法在大多数情况下准确有效。

4.4 得分系统

在这里插入图片描述

brick.visible = false;
score += 10 * (rows - row);

砖块消失后根据行数计算得分,上面的砖块分数更高。行号越小(越靠上),rows - row越大,得分越高。这种设计鼓励玩家先击打上面的砖块。

五、挡板控制系统

5.1 键盘控制

void _handleKeyEvent(KeyEvent event) {
  if (event is KeyDownEvent) {
    final key = event.logicalKey;
    if (key == LogicalKeyboardKey.arrowLeft) {
      paddleX = max(0, paddleX - 30);
    } else if (key == LogicalKeyboardKey.arrowRight) {
      paddleX = min(gameWidth - paddleWidth, paddleX + 30);
    }
  }
}

左右箭头键移动挡板,每次移动30像素。使用max和min函数确保挡板不超出边界,这是简单有效的边界控制方法。

5.2 触摸控制

在这里插入图片描述

void _movePaddle(double dx) {
  setState(() {
    paddleX = (paddleX + dx).clamp(0.0, gameWidth - paddleWidth);
  });
}

_buildControlButton(Icons.arrow_back, () => _movePaddle(-30)),
_buildControlButton(Icons.arrow_forward, () => _movePaddle(30)),

屏幕按钮提供触摸控制,使用clamp方法将位置限制在有效范围内。这种方式与键盘控制保持一致,确保不同设备上的统一体验。

六、游戏循环与性能

6.1 高帧率循环

gameTimer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
  if (!gameOver && !won) {
    updateGame();
  }
});

每16毫秒执行一次更新,约60帧每秒,提供流畅的游戏体验。高帧率让球的运动更加平滑,碰撞检测也更加精确。

6.2 状态检查机制

if (!gameOver && !won) {
  updateGame();
}

只在游戏进行中更新,结束或胜利时停止更新但保留定时器。这种设计避免了不必要的计算,提高了性能。

6.3 资源清理


void dispose() {
  gameTimer?.cancel();
  super.dispose();
}

组件销毁时取消定时器,防止内存泄漏。这是Flutter开发中重要的资源管理实践。

七、胜利检测

void checkWin() {
  bool hasVisibleBricks = false;
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols; col++) {
      if (bricks[row][col].visible) {
        hasVisibleBricks = true;
        break;
      }
    }
    if (hasVisibleBricks) break;
  }

  if (!hasVisibleBricks) {
    won = true;
    gameTimer?.cancel();
  }
}

遍历所有砖块检查是否还有可见砖块,全部击碎后游戏胜利。使用break优化性能,找到可见砖块后立即停止检查。

总结

本文详细介绍了打砖块游戏的物理引擎和碰撞检测系统。从基础的球体运动到精确的角度控制,从高效的碰撞检测到智能的方向判断,每个技术点都直接影响游戏的手感和公平性。通过这些技术的综合应用,实现了流畅、有趣、具有挑战性的打砖块游戏体验。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐