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

打砖块是一款经典的游戏,它简单易懂却又充满挑战性。本文将介绍如何使用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 = 3private 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应用开发提供了强大的工具集。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86849.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter MobX 响应式原理与实战详解

&#x1f4da; Flutter 状态管理系列文章目录 Flutter 状态管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux) setState() 使用详解&#xff1a;原理及注意事项 InheritedWidget 组件使用及原理 Flutter 中 Provider 的使用、注…

浅谈国产数据库多租户方案:提升云计算与SaaS的资源管理效率

近年来&#xff0c;“数据库多租户”这一概念在技术圈内频频出现&#xff0c;成为云计算和SaaS&#xff08;软件即服务&#xff09;架构中的重要组成部分。多租户架构不仅为企业提供了高效的资源隔离与共享解决方案&#xff0c;还能大幅降低成本&#xff0c;提高系统的可扩展性…

Wpf的Binding

前言 wpf的Binding就像一个桥梁&#xff0c;它的作用就是连接逻辑层与界面层&#xff0c;既能够把逻辑层的数据搬到界面层展示&#xff0c;又能将界面层的数据更改后传递到逻辑层&#xff0c;Binding的数据来源就是Binding的源&#xff0c;数据展示的地方就是Binding的目标。 …

嵌入式单片机中SPI串行外设接口控制与详解

串行外设接口(Serial Peripheral Interface)的简称也叫做SPI,是一种高速的、全双工同步通信的一种接口,串行外设接口一般是需要4根线来进行通信(NSS、MISO、MOSI、SCK),但是如果打算实现单向通信(最少3根线),就可以利用这种机制实现一对多或者一对一的通信。 第一:…

【世纪龙科技】新能源汽车动力电池总成装调与检修教学软件

在新能源汽车产业“技术迭代快、安全要求高、实操风险大”的背景下&#xff0c;职业院校如何以“项目式教学改革”为突破口&#xff0c;破解传统实训“高成本、高风险、低效率”的困局&#xff1f;江苏世纪龙科技以桌面VR沉浸式技术为支点&#xff0c;推出《新能源动力电池总成…

GO泛型编程面试题及参考答案

目录 什么是 Go 中的泛型?Go 从哪个版本开始支持泛型? 在 Go 中如何定义一个带类型参数的函数? 如何为结构体添加类型参数? 使用 any 关键字和自定义类型约束有什么区别? 泛型中~T 的语义及其实际应用是什么? 如何在函数中使用多个类型参数?举例说明。 Go 泛型支…

ReactRouter-404路由配置以及两种路由模式

404路由 场景&#xff1a;当浏览器输入url的路径在整个路由配置中都找不到对应的path&#xff0c;为了用户体验&#xff0c;可以使用404兜底组件进行渲染 实现步骤 准备一个404组件在路由表数组的末尾&#xff0c;以*号作为路由path配置路由 新建404组件 const NotFound (…

《Kubernetes》Service 详解+Ingress

主要介绍kubernetes的流量负载组件&#xff1a;Service和Ingress。 1. Service 1.1 Service介绍 在kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;我们可以通过pod的ip来访问应用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;这也就意味着不方便直接采用…

常见网络知识,宽带、路由器

常见网络知识&#xff0c;宽带、路由器 1、关于光猫、桥接和路由接法 现在的光猫都带有路由功能&#xff0c;即光猫、路由一体。不需要路由器也能让设备连上&#xff0c;但是一般来说路由功能穿墙有限&#xff0c;放在弱电箱/多媒体箱里的光猫发射出来的wifi信号其实是很难在…

Android应用缓存清理利器:WorkManager深度实践指南

本文将带你全面掌握使用WorkManager实现缓存清理的技术方案&#xff0c;从基础原理到性能优化&#xff0c;提供完整代码实现和工程实践指南 一、缓存清理的必要性与挑战 在Android应用开发中&#xff0c;缓存管理是优化应用性能的关键环节。随着应用使用时间增长&#xff0c;缓…

如何理解构件“可独立部署”的特性

构件的“可独立部署”特性是其区别于普通代码模块的核心特征之一&#xff0c;我们可以通过生活案例和技术原理解释来理解这一特性&#xff1a; 一、生活类比&#xff1a;从“家电维修”看独立部署 假设你家的空调坏了&#xff0c;维修时只需拆开空调外机更换压缩机&#xff0…

uni-app subPackages 分包加载:优化应用性能的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

CentOS 8 安装第二个jdk隔离环境

1.适用于原本已经装了jdk8&#xff0c;现在需要安装jdk17&#xff1a; 多版本 JDK 共存不希望修改系统默认 JavaDocker 或脚本化部署 2. 下载 Adoptium&#xff08;原 AdoptOpenJDK&#xff09; 的 OpenJDK 17&#xff1a; cd /指定目录 sudo wget https://github.com/adopti…

Day.43

getitem方法&#xff1a; class MyList: def __init__(self): self.data [10, 20, 30, 40, 50] def __getitem__(self, idx): return self.data[idx] my_list_obj MyList() print(my_list_obj[2]) len方法&#xff1a; class MyList: def __init__(self): self.data [10…

三七互娱GO面经及参考答案

MySQL 有哪些存储引擎?MyISAM 如何存储数字类型数据? MySQL 拥有多种存储引擎,每种都有其独特的特性和适用场景。常见的存储引擎包括 InnoDB、MyISAM、Memory、CSV、Archive、Federated 等。 InnoDB 是 MySQL 5.5 版本之后的默认存储引擎,它支持事务、外键、行级锁和崩溃恢…

git常见问题汇总-重复提交/删除已提交文件等问题

git常见问题汇总&#xff1a; 1&#xff0c;已经commit的文件需要修改 /删除&#xff0c;应该怎么处理&#xff1f; 2&#xff0c;自己建的分支“branch1”显示“rebasing branch1”&#xff0c;这是什么情况&#xff1f; 3&#xff0c;由于内容修改/优化&#xff0c;在同一个…

Python实例题:简单的 Web 服务器

目录 Python实例题 题目 要求&#xff1a; 解题思路&#xff1a; 代码实现&#xff1a; Python实例题 题目 简单的 Web 服务器 要求&#xff1a; 使用 Python 的 socket 模块实现一个简单的 HTTP 服务器。支持以下功能&#xff1a; 处理 GET 和 POST 请求静态文件服务…

3.Stable Diffusion WebUI本地部署和实践

本文看了(68 封私信) 逼真的图片生产 | Stable Diffusion WebUI本地部署看这一篇就够了 - 知乎和(68 封私信) Stable Diffusion WebUI 实践: 基本技法及微调 - 知乎&#xff0c;本人根据它们部署了一遍&#xff0c;中间遇到一些报错&#xff0c;但根据报错提示解决了问题&#…

阿里最新开源:Mnn3dAvatar 3D数字人框架, 无需联网,本地部署可离线运行,支持多模态实时交互

Mnn3dAvatar 3D数字人框架是基于阿里巴巴开源的轻量级深度学习推理框架MNN&#xff08;Mobile Neural Network&#xff09;开发的全新3D数字人框架。Aibot亲测这是一个可以在本地运行、完全离线、支持多模态实时交互的智能数字人App。可以在本地私有部署。感兴趣的同学可以拿来…

03【C++ 入门基础】函数重载

文章目录 引言函数重载函数重载的使用函数重载的原理extern “C” 静态多态 总结 引言 通过00【C 入门基础】前言得知&#xff0c;C是为了解决C语言在面对大型项目的局限而诞生&#xff1a; C语言面对的现实工程问题&#xff08;复杂性、可维护性、可扩展性、安全性&#xff0…