鸿蒙HarmonyOS 5小游戏实践:打砖块游戏(附:源代码)

发布于:2025-06-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

打砖块是一款经典的游戏,它简单易懂却又充满挑战性。本文将介绍如何使用ArkUI框架开发一个完整的打砖块游戏,涵盖游戏逻辑设计、UI实现和交互处理等核心内容。

游戏架构设计

我们的打砖块游戏采用了组件化设计,主要分为两个部分:

  1. BrickBreakerGame组件:负责游戏的核心逻辑和渲染
  2. BrickBreakerPage组件:作为游戏的主页面容器

游戏状态管理使用了枚举类型GameState来区分三种状态:

enum GameState {
  READY,   // 准备中
  PLAYING, // 游戏中
  OVER     // 游戏结束
}

核心游戏逻辑实现

1. 游戏初始化

initGame()方法中,我们初始化了游戏的所有元素:

  • 创建砖块矩阵(5行6列)
  • 设置挡板和球的初始位置
  • 重置分数和生命值
initGame() {
  this.bricks = [];
  for (let row = 0; row < BRICK_ROWS; row++) {
    let colArray: boolean[] = [];
    for (let col = 0; col < BRICK_COLS; col++) {
      colArray.push(true);
    }
    this.bricks.push(colArray);
  }
  // 其他初始化代码...
}

2. 游戏主循环

游戏主循环通过setInterval实现,每16毫秒执行一次,负责:

  • 更新球的位置
  • 检测碰撞(墙壁、挡板、砖块)
  • 处理游戏结束条件
gameLoop() {
  // 移动球
  this.ballX += this.ballDX;
  this.ballY += this.ballDY;
  
  // 碰撞检测逻辑...
}

3. 碰撞检测

碰撞检测是游戏的核心逻辑之一,我们实现了:

  • 球与墙壁碰撞:改变球的运动方向
  • 球与挡板碰撞:根据击中位置改变反弹角度
  • 球与砖块碰撞:消除砖块并增加分数

挡板碰撞处理特别加入了根据击中位置改变反弹角度的逻辑,增加了游戏的可玩性:

const hitPos = (this.ballX - this.paddleX) / PADDLE_WIDTH;
this.ballDX = (hitPos - 0.5) * 10; // -5到5之间的值
this.ballDY = -Math.abs(this.ballDY);

UI设计与实现

1. 游戏元素渲染

使用ArkUI的声明式语法,我们实现了各种游戏元素的渲染:

砖块渲染

ForEach(this.bricks, (row: boolean[], rowIndex:number) => {
  ForEach(row, (brickExists: boolean, colIndex: number) => {
    if (brickExists) {
      Column()
        .width(BRICK_WIDTH - 2)
        .height(BRICK_HEIGHT - 2)
        .backgroundColor(this.colors[rowIndex])
        // 其他样式...
    }
  })
})

挡板和球

// 挡板
Column()
  .width(PADDLE_WIDTH)
  .height(PADDLE_HEIGHT)
  .backgroundColor('#4CAF50')
  .position({ x: this.paddleX, y: GAME_HEIGHT - PADDLE_HEIGHT - 10 })

// 球
Column()
  .width(BALL_SIZE)
  .height(BALL_SIZE)
  .backgroundColor('#FFC107')
  .position({ x: this.ballX, y: this.ballY })

2. 游戏状态UI

根据游戏状态显示不同的UI元素:

准备界面

if (this.gameState === GameState.READY) {
  Column() {
    Text('打砖块')
      .fontSize(32)
    Text('点击开始游戏')
      .fontSize(20)
  }
  // 其他样式...
}

游戏结束界面

} else if (this.gameState === GameState.OVER) {
  Column() {
    Text('游戏结束')
      .fontSize(28)
    Text(`得分: ${this.score}`)
      .fontSize(22)
    Text('点击重新开始')
      .fontSize(18)
  }
  // 其他样式...
}

交互处理

游戏通过触摸事件来控制挡板移动:

.handleMove(event: TouchEvent) {
  if (this.gameState === GameState.PLAYING) {
    const touchX = event.touches[0].x;
    this.paddleX = Math.max(
      0,
      Math.min(GAME_WIDTH - PADDLE_WIDTH, touchX - PADDLE_WIDTH / 2)
    );
  }
}

点击事件用于开始游戏或重新开始:

.onClick(() => {
  if (this.gameState === GameState.READY) {
    this.startGame();
  } else if (this.gameState === GameState.OVER) {
    this.gameState = GameState.READY;
  }
})

附:源代码

import { promptAction } from "@kit.ArkUI";
import { Color } from "@ohos.graphics.scene";

// 游戏常量定义
const GAME_WIDTH = 360; // 游戏区域宽度
const GAME_HEIGHT = 600; // 游戏区域高度
const PADDLE_WIDTH = 80; // 挡板宽度
const PADDLE_HEIGHT = 15; // 挡板高度
const BALL_SIZE = 15; // 球大小
const BRICK_WIDTH = 60; // 砖块宽度
const BRICK_HEIGHT = 20; // 砖块高度
const BRICK_ROWS = 5; // 砖块行数
const BRICK_COLS = 6; // 砖块列数
const PADDLE_SPEED = 8; // 挡板移动速度
const BALL_SPEED = 4; // 球初始速度

// 游戏状态
enum GameState {
  READY,  // 准备中
  PLAYING, // 游戏中
  OVER     // 游戏结束
}

// 游戏主逻辑
@Component
struct BrickBreakerGame {
  @State gameState: GameState = GameState.READY
  @State paddleX: number = GAME_WIDTH / 2 - PADDLE_WIDTH / 2
  @State ballX: number = GAME_WIDTH / 2
  @State ballY: number = GAME_HEIGHT - 100
  @State ballDX: number = BALL_SPEED
  @State ballDY: number = -BALL_SPEED
  @State bricks: boolean[][] = []
  @State score: number = 0
  @State lives: number = 3
  private gameLoopId: number = 0
  @State colors:string[] = ['#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3']

  // 初始化游戏
  initGame() {
    // 初始化砖块
    this.bricks = [];
    for (let row = 0; row < BRICK_ROWS; row++) {
      let colArray: boolean[] = [];
      for (let col = 0; col < BRICK_COLS; col++) {
        colArray.push(true);
      }
      this.bricks.push(colArray);
    }

    this.paddleX = GAME_WIDTH / 2 - PADDLE_WIDTH / 2;
    this.ballX = GAME_WIDTH / 2;
    this.ballY = GAME_HEIGHT - 100;
    this.ballDX = BALL_SPEED;
    this.ballDY = -BALL_SPEED;
    this.score = 0;
    this.lives = 3;
  }

  // 开始游戏
  startGame() {
    this.gameState = GameState.PLAYING;
    this.initGame();

    // 启动游戏循环
    this.gameLoopId = setInterval(() => {
      this.gameLoop();
    }, 16);
  }

  // 游戏结束
  gameOver() {
    this.gameState = GameState.OVER;
    clearInterval(this.gameLoopId);

    promptAction.showToast({
      message: `游戏结束! 得分: ${this.score}`,
      duration: 2000
    });
  }

  // 游戏主循环
  gameLoop() {
    // 移动球
    this.ballX += this.ballDX;
    this.ballY += this.ballDY;

    // 检测球与墙壁碰撞
    if (this.ballX < 0 || this.ballX + BALL_SIZE > GAME_WIDTH) {
      this.ballDX = -this.ballDX;
    }
    if (this.ballY < 0) {
      this.ballDY = -this.ballDY;
    }

    // 检测球与挡板碰撞
    if (this.ballY + BALL_SIZE > GAME_HEIGHT - PADDLE_HEIGHT &&
      this.ballY + BALL_SIZE < GAME_HEIGHT &&
      this.ballX + BALL_SIZE > this.paddleX &&
      this.ballX < this.paddleX + PADDLE_WIDTH) {
      // 根据击中挡板的位置改变反弹角度
      const hitPos = (this.ballX - this.paddleX) / PADDLE_WIDTH;
      this.ballDX = (hitPos - 0.5) * 10; // -5到5之间的值
      this.ballDY = -Math.abs(this.ballDY); // 确保向上反弹
    }

    // 检测球与砖块碰撞
    for (let row = 0; row < BRICK_ROWS; row++) {
      for (let col = 0; col < BRICK_COLS; col++) {
        if (this.bricks[row][col]) {
          const brickX = col * BRICK_WIDTH;
          const brickY = row * BRICK_HEIGHT + 50; // 顶部留出空间

          if (this.ballX + BALL_SIZE > brickX &&
            this.ballX < brickX + BRICK_WIDTH &&
            this.ballY + BALL_SIZE > brickY &&
            this.ballY < brickY + BRICK_HEIGHT) {

            // 创建新数组以确保UI刷新
            const newBricks = [...this.bricks];
            newBricks[row] = [...newBricks[row]];
            newBricks[row][col] = false;
            this.bricks = newBricks;
            this.score += 10;

            // 根据碰撞位置决定反弹方向
            if (this.ballX + BALL_SIZE / 2 < brickX ||
              this.ballX + BALL_SIZE / 2 > brickX + BRICK_WIDTH) {
              this.ballDX = -this.ballDX;
            } else {
              this.ballDY = -this.ballDY;
            }
          }
        }
      }
    }

    // 检测球是否落到底部
    if (this.ballY + BALL_SIZE > GAME_HEIGHT) {
      this.lives--;
      if (this.lives <= 0) {
        this.gameOver();
      } else {
        // 重置球位置
        this.ballX = GAME_WIDTH / 2;
        this.ballY = GAME_HEIGHT - 100;
        this.ballDX = BALL_SPEED;
        this.ballDY = -BALL_SPEED;
      }
    }

    // 检查是否所有砖块都被消除
    const allBricksGone = this.bricks.every(row =>
    row.every(brick => !brick)
    );

    if (allBricksGone) {
      promptAction.showToast({
        message: `恭喜通关! 得分: ${this.score}`,
        duration: 2000
      });
      this.initGame(); // 重新开始新一局
    }
  }

  // 处理触摸移动事件
  handleMove(event: TouchEvent) {
    if (this.gameState === GameState.PLAYING) {
      const touchX = event.touches[0].x;
      this.paddleX = Math.max(
        0,
        Math.min(GAME_WIDTH - PADDLE_WIDTH, touchX - PADDLE_WIDTH / 2)
      );
    }
  }

  build() {
    Stack() {
      // 游戏背景
      Column()
        .width(GAME_WIDTH)
        .height(GAME_HEIGHT)
        .backgroundColor('#1a1a1a')
        .border({ width: 2, color: '#4a4a4a' })

      // 绘制砖块
      ForEach(this.bricks, (row: boolean[], rowIndex:number) => {
        ForEach(row, (brickExists: boolean, colIndex: number) => {
          if (brickExists) {
            Column()
              .width(BRICK_WIDTH - 2)
              .height(BRICK_HEIGHT - 2)
              .backgroundColor(this.colors[rowIndex])
              .borderRadius(6)
              .shadow({ color: '#00000040', radius: 4 })
              .position({
                x: colIndex * BRICK_WIDTH + 1,
                y: rowIndex * BRICK_HEIGHT + 50
              })
          }
        }, (brickExists: boolean, colIndex: number) => {
          return colIndex.toString();
        })
      })

      // 绘制挡板
      Column()
        .width(PADDLE_WIDTH)
        .height(PADDLE_HEIGHT)
        .backgroundColor('#4CAF50')
        .borderRadius(12)
        .shadow({ color: '#00000040', radius: 4 })
        .position({ x: this.paddleX, y: GAME_HEIGHT - PADDLE_HEIGHT - 10 })

      // 绘制球
      Column()
        .width(BALL_SIZE)
        .height(BALL_SIZE)
        .backgroundColor('#FFC107')
        .borderRadius(BALL_SIZE / 2)
        .shadow({ color: '#00000040', radius: 4 })
        .position({ x: this.ballX, y: this.ballY })

      // 游戏状态提示
      if (this.gameState === GameState.READY) {
        Column() {
          Text('打砖块')
            .fontSize(32)
            .fontColor('#fff')
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 24 })

          Text('点击开始游戏')
            .fontSize(20)
            .fontColor('#ffffffCC')
            .padding({ top: 8, bottom: 8, left: 24, right: 24 })
            .borderRadius(20)
            .border({ width: 1, color: '#ffffff40' })
        }
        .alignItems(HorizontalAlign.Center)
        .justifyContent(FlexAlign.Center)
        .position({ x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 - 40 })
      } else if (this.gameState === GameState.OVER) {
        Column() {
          Text('游戏结束')
            .fontSize(28)
            .fontColor('#fff')
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 12 })

          Text(`得分: ${this.score}`)
            .fontSize(22)
            .fontColor('#ffd700')
            .margin({ bottom: 20 })

          Text('点击重新开始')
            .fontSize(18)
            .fontColor('#ffffffCC')
            .padding({ top: 6, bottom: 6, left: 20, right: 20 })
            .borderRadius(18)
            .border({ width: 1, color: '#ffffff40' })
        }
        .alignItems(HorizontalAlign.Center)
        .justifyContent(FlexAlign.Center)
        .position({ x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 - 20 })
      }

      // 分数和生命显示
      Row() {
        Column({ space: 4 }) {
          Row() {
            Image($r("app.media.star"))
              .width(16)
              .height(16)
            Text(`得分: ${this.score}`)
              .fontSize(16)
              .fontColor('#fff')
              .margin({left: 4})
          }
          .alignItems(VerticalAlign.Center)

          Row() {
            Image($r("app.media.background"))
              .width(16)
              .height(16)
            Text(`生命: ${this.lives}`)
              .fontSize(16)
              .fontColor('#fff')
              .margin({left: 4})
          }
          .alignItems(VerticalAlign.Center)
        }
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor('#00000066')
        .borderRadius(12)
      }
      .position({ x: 16, y: 16 })
    }
    .width(GAME_WIDTH)
    .height(GAME_HEIGHT)
    .onClick(() => {
      if (this.gameState === GameState.READY) {
        this.startGame();
      } else if (this.gameState === GameState.OVER) {
        this.gameState = GameState.READY;
      }
    })
    .onTouch((event: TouchEvent) => {
      if (event.type === TouchType.Move) {
        this.handleMove(event)
      }
    })
  }
}

// 主页面
@Entry
@Component
struct BrickBreakerPage {
  build() {
    Column() {
      // 游戏标题
      Row() {
        Image($r("app.media.background"))
          .width(32)
          .height(32)
          .margin({ right: 10 })
        Text('打砖块')
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2c2c2c')
      }
      .alignItems(VerticalAlign.Center)
      .margin({ top: 24, bottom: 16 })

      // 游戏区域
      BrickBreakerGame()
        .width(GAME_WIDTH)
        .height(GAME_HEIGHT)
        .margin({ top: 12 })
        .border({ width: 2, color: '#e0e0e0' })
        .borderRadius(16)
        .shadow({ color: '#00000020', radius: 8 })

    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 12, bottom: 12 })
    .backgroundColor('#f0f0f0')
  }
}

总结

通过这个打砖块游戏的开发实践,我们展示了如何使用ArkUI框架实现一个完整的游戏应用。关键点包括:

  1. 游戏状态管理
  2. 游戏主循环实现
  3. 碰撞检测算法
  4. 声明式UI渲染
  5. 用户交互处理

这个项目不仅演示了ArkUI的基本用法,也展示了如何将游戏逻辑与UI框架结合。开发者可以在此基础上进一步扩展,如添加音效、更多关卡、特殊道具等功能,使游戏更加丰富有趣。

ArkUI的声明式开发模式使得游戏UI的实现变得简洁直观,而TypeScript的强类型特性则帮助我们在开发复杂游戏逻辑时减少错误。这种组合为HarmonyOS应用开发提供了强大的工具集。


网站公告

今日签到

点亮在社区的每一天
去签到