当前位置: 首页 > news >正文

# HarmonyOS ArkTS 小游戏开发实战(一):弹弹球物理碰撞游戏

摘要:本文详细介绍如何使用 HarmonyOS ArkTS 开发一款弹弹球(Bouncing Ball)物理碰撞游戏。涵盖 Circle 绘制、边界碰撞检测、分数累积、颜色循环以及触摸加速等核心技术点,帮助开发者快速掌握 ArkTS 声明式 UI 与交互开发技巧。


一、项目概述

弹弹球是一款经典的休闲物理小游戏,核心玩法是小球在屏幕内自由弹跳,用户点击小球使其加速并获得分数。在 HarmonyOS ArkTS 框架下,我们利用CanvasCircle 组件的协同工作,配合onTouch 事件实现流畅的物理模拟与交互反馈。

本项目的技术亮点包括:

  • 使用 ArkTS 声明式语法构建 UI 层
  • Circle 组件实现圆形小球的绘制与动画
  • 矩形边界碰撞检测算法
  • 基于状态变量驱动的颜色循环
  • 触摸事件(onTouch)触发加速逻辑
  • 分数增量累积与实时显示

二、项目架构与目录结构

entry/src/main/ets/ ├── pages/ │ ├── index.ets // 主页面:游戏画布与UI │ ├── BouncingBall.ets // 弹弹球逻辑组件 │ └── GameEngine.ets // 物理引擎(碰撞检测、运动计算) └── common/ └── Constants.ets // 常量定义:颜色、速度、半径等

整个应用采用分层架构

  1. 表现层:index.ets 负责界面布局与状态展示
  2. 逻辑层:BouncingBall.ets 封装小球对象的状态与行为
  3. 引擎层:GameEngine.ets 实现物理碰撞检测与运动计算
  4. 配置层:Constants.ets 统一管理游戏参数

三、核心技术分析

3.1 Circle 组件与自定义绘制

ArkTS 提供了丰富的组件库,其中Circle是绘制圆形的声明式组件。在弹弹球游戏中,我们使用 Circle 组件来渲染小球:

// BouncingBall.ets @Component export struct BouncingBall { @Prop ballX: number = 200 @Prop ballY: number = 300 @Prop ballRadius: number = 25 @Prop ballColor: Color = Color.Red build() { Circle({ width: this.ballRadius * 2, height: this.ballRadius * 2 }) .fill(this.ballColor) .position({ x: this.ballX, y: this.ballY }) .width(this.ballRadius * 2) .height(this.ballRadius * 2) } }

关键点

  • Circle组件的宽高决定其大小,传入直径ballRadius * 2
  • position属性控制小球在父容器中的绝对位置
  • @Prop装饰器使得小球属性可以由父组件驱动更新

3.2 边界碰撞检测算法

碰撞检测是物理游戏的核心。我们的场景是一个矩形画布,小球在其中运动,碰到四边则反弹。

// GameEngine.ets export class GameEngine { // 检测并处理边界碰撞 static checkBoundaryCollision( x: number, y: number, radius: number, vx: number, vy: number, canvasWidth: number, canvasHeight: number ): { x: number, y: number, vx: number, vy: number } { let newVx = vx let newVy = vy let newX = x let newY = y // 左右边界检测 if (x - radius <= 0) { newX = radius newVx = -vx } else if (x + radius >= canvasWidth) { newX = canvasWidth - radius newVx = -vx } // 上下边界检测 if (y - radius <= 0) { newY = radius newVy = -vy } else if (y + radius >= canvasHeight) { newY = canvasHeight - radius newVy = -vy } return { x: newX, y: newY, vx: newVx, vy: newVy } } }

算法要点

边界碰撞条件反弹处理
左边界x - radius <= 0x = radius, vx = -vx
右边界x + radius >= canvasWidthx = canvasWidth - radius, vx = -vx
上边界y - radius <= 0y = radius, vy = -vy
下边界y + radius >= canvasHeighty = canvasHeight - radius, vy = -vy

碰撞检测的关键在于将圆形简化为质点,判断质点位置与边界距离是否小于半径。当检测到碰撞时,不仅反转速度方向,还需修正位置防止小球卡在边界外。

3.3 触摸加速功能

用户点击小球时,小球获得瞬时加速度。我们通过onTouch事件监听触摸位置,判断是否与小球区域重合:

// index.ets — 主页面触摸事件 @Entry @Component struct GamePage { @State ballX: number = 200 @State ballY: number = 400 @State ballVx: number = 3 @State ballVy: number = -4 @State score: number = 0 @State ballColor: Color = Color.Red private readonly CANVAS_WIDTH: number = 360 private readonly CANVAS_HEIGHT: number = 780 private readonly RADIUS: number = 30 private readonly ACCELERATION: number = 2.5 build() { Stack() { // 分数显示 Text(`得分: ${this.score}`) .fontSize(24) .fontWeight(FontWeight.Bold) .position({ x: 20, y: 40 }) // 弹弹球 Circle({ width: this.RADIUS * 2, height: this.RADIUS * 2 }) .fill(this.ballColor) .position({ x: this.ballX, y: this.ballY }) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { // 判断触摸点是否在小球区域内 const touchX = event.touches[0].x const touchY = event.touches[0].y const dx = touchX - (this.ballX + this.RADIUS) const dy = touchY - (this.ballY + this.RADIUS) const distance = Math.sqrt(dx * dx + dy * dy) if (distance <= this.RADIUS) { // 触摸小球:加速并计分 this.ballVx *= this.ACCELERATION this.ballVy *= this.ACCELERATION this.score++ this.changeColor() } } }) } .width('100%') .height('100%') .backgroundColor('#F5F5F5') } }

onTouch 事件处理流程

  1. 用户触摸屏幕,触发onTouch回调
  2. 判断事件类型为TouchType.Down(手指按下)
  3. 获取触摸点坐标event.touches[0].x / y
  4. 计算触摸点与圆心的距离
  5. 若距离 ≤ 半径,判定为点击到小球
  6. 执行加速(速度乘以系数)、加分、换色

3.4 颜色循环机制

为了让游戏更具视觉吸引力,每次点击小球时切换颜色。我们定义一个颜色数组并循环索引:

private readonly colors: Color[] = [ Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Pink, Color.Purple, Color.Yellow, Color.Grey ] private colorIndex: number = 0 changeColor(): void { this.colorIndex = (this.colorIndex + 1) % this.colors.length this.ballColor = this.colors[this.colorIndex] }

这种循环取模的方式简洁高效,无需额外的状态判断。也可以扩展为 HSL 颜色渐变,实现更平滑的过渡效果。

3.5 帧动画驱动

使用setInterval定时更新小球位置,实现连续动画:

aboutToAppear(): void { this.timerId = setInterval(() => { this.updateBallPosition() }, 16) // 约 60 FPS } updateBallPosition(): void { let newX = this.ballX + this.ballVx let newY = this.ballY + this.ballVy // 碰撞检测 const result = GameEngine.checkBoundaryCollision( newX, newY, this.RADIUS, this.ballVx, this.ballVy, this.CANVAS_WIDTH, this.CANVAS_HEIGHT ) this.ballX = result.x this.ballY = result.y this.ballVx = result.vx this.ballVy = result.vy }

帧率说明16ms间隔约等于 60 FPS,这是大多数游戏的标准刷新率。如果感觉性能开销大,可以调整为 30ms(约 33 FPS)。


四、完整代码整合

将以上各部分整合为一个完整可运行的主页面:

// index.ets — 完整游戏页面 import { GameEngine } from './GameEngine' @Entry @Component struct BouncingBallGame { @State ballX: number = 180 @State ballY: number = 400 @State ballVx: number = 4 @State ballVy: number = -5 @State score: number = 0 @State ballColor: Color = Color.Red private readonly CANVAS_W: number = 360 private readonly CANVAS_H: number = 780 private readonly R: number = 28 private readonly COLORS: Color[] = [ Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Pink, Color.Purple, Color.Yellow, Color.Grey ] private colorIdx: number = 0 private timerId: number = -1 aboutToAppear(): void { this.timerId = setInterval(() => { let nx = this.ballX + this.ballVx let ny = this.ballY + this.ballVy const ret = GameEngine.checkBoundaryCollision( nx, ny, this.R, this.ballVx, this.ballVy, this.CANVAS_W, this.CANVAS_H ) this.ballX = ret.x this.ballY = ret.y this.ballVx = ret.vx this.ballVy = ret.vy }, 16) } aboutToDisappear(): void { clearInterval(this.timerId) } changeColor(): void { this.colorIdx = (this.colorIdx + 1) % this.COLORS.length this.ballColor = this.COLORS[this.colorIdx] } build() { Stack() { // 背景 Rect() .width('100%') .height('100%') .fill('#1a1a2e') // 分数 Text(`🏆 ${this.score}`) .fontSize(28) .fontColor(Color.White) .fontWeight(FontWeight.Bold) .position({ x: 20, y: 50 }) // 小球 Circle({ width: this.R * 2, height: this.R * 2 }) .fill(this.ballColor) .position({ x: this.ballX, y: this.ballY }) .onTouch((ev: TouchEvent) => { if (ev.type === TouchType.Down) { const tx = ev.touches[0].x const ty = ev.touches[0].y const dx = tx - (this.ballX + this.R) const dy = ty - (this.ballY + this.R) if (Math.sqrt(dx * dx + dy * dy) <= this.R) { this.ballVx *= 2.0 this.ballVy *= 2.0 this.score++ this.changeColor() } } }) // 操作提示 Text('点击小球加速得分!') .fontSize(16) .fontColor('#888888') .position({ x: 90, y: 700 }) } .width('100%') .height('100%') } }

五、HarmonyOS 特性深度解析

5.1 声明式 UI 与状态驱动

ArkTS 采用声明式编程范式,开发者只需描述 UI 应处于的状态,框架自动处理渲染更新。在弹弹球中:

  • @State标记的变量(ballX, ballY, score, ballColor)发生变化时,框架自动重绘相关组件
  • 无需手动操作 DOM 或调用 invalidate 方法
  • 数据流单向:状态 → UI,避免双向绑定带来的调试困难

5.2 Stack 布局的妙用

Stack是一个层叠布局容器,子组件按声明顺序从下到上堆叠。在游戏中非常适合:

  1. 底层放背景矩形
  2. 中间层放分数文本
  3. 顶层放小球 Circle

这种布局方式使得 z-order 管理变得直观,无需像传统游戏引擎那样手动处理绘制顺序。

5.3 生命周期管理

  • aboutToAppear():页面即将显示时调用,适合初始化计时器
  • aboutToDisappear():页面即将销毁时调用,必须清理计时器防止内存泄漏

六、UI/UX 设计建议

6.1 视觉风格

  1. 深色背景 + 亮色小球:深色背景(#1a1a2e)减少视觉疲劳,亮色小球形成对比
  2. 分数动画:得分时显示 “+1” 浮动文字,增强反馈
  3. 拖尾效果:在小球后方绘制半透明轨迹,增加运动感

6.2 交互反馈

  1. 触感反馈:点击小球时配合短震动(如调用vibrator.vibrate(50)
  2. 速度显示:在画布角落显示当前速度值,让用户感知加速效果
  3. 碰撞音效:小球碰到边界时播放简单的「砰」声

6.3 优化体验

  1. 初始速度随机化:每次启动游戏时,小球的速度方向随机,增加可玩性
  2. 难度递增:随着分数增加,小球的基础速度逐渐提高
  3. 暂停机制:点击暂停按钮冻结物理模拟

七、最佳实践总结

7.1 性能优化

  • 避免频繁创建对象:在updateBallPosition中复用计算结果对象
  • 合理使用状态变量:只将对 UI 有影响的属性标记为@State,内部计算变量保持为普通属性
  • 定时器清理:务必在aboutToDisappear中清理setInterval

7.2 代码组织

  • 将物理引擎、小球逻辑、页面布局拆分到不同文件中
  • 使用@Prop@State明确组件间数据关系
  • 常量集中管理,避免硬编码

7.3 兼容性注意

  • Circle 组件的fill属性支持Color枚举和字符串色值
  • onTouch事件在模拟器中与真机行为一致,但多点触摸支持需额外处理
  • 不同设备的屏幕尺寸差异建议使用breakpoint断点系统适配

八、扩展与演进

弹弹球游戏虽然简单,但可以扩展为更完整的物理引擎:

  1. 重力系统:增加竖直方向恒定加速度
  2. 多球碰撞:实现球与球之间的弹性碰撞
  3. 障碍物:在画布中随机生成矩形障碍物
  4. 粒子特效:点击小球时产生粒子爆炸效果

九、结语

通过弹弹球游戏的开发,我们深入实践了 HarmonyOS ArkTS 框架的核心能力:声明式组件、状态驱动、触摸事件与动画循环。麻雀虽小五脏俱全,这个小项目涵盖了游戏开发中最基础的物理模拟与交互模式,是学习 ArkTS 游戏开发的理想起点。

希望本文能帮助开发者快速上手 ArkTS 游戏开发,在实际项目中灵活运用 Circle、Stack、onTouch 等关键技术。下一期我们将探索表单验证的实现技巧,敬请期待!


http://www.jsqmd.com/news/1291176/

相关文章:

  • STM32智能小车实战:从硬件选型到PID算法,手把手教你打造循迹机器人
  • 智能车图像处理:八邻域边界追踪与中心线提取实战指南
  • STM32主从定时器实现任意相位差PWM输出配置详解
  • 揭秘司法AI法条推荐准确率提升47%的关键:从语义理解到动态权重建模全流程拆解
  • Web安全核心:从数据流视角剖析SQL注入、反序列化与文件上传漏洞
  • 收到学术不端指控邮件后,千万别急着做这三件事——附英澳真实听证会案例
  • 2026年近期惠州五金外壳找哪家?5大优选厂商深度盘点 - 装修教育财税推荐2026
  • 2026AI智能纪要助力培训效果评估 准识别快整理更清晰更省事
  • 流批安卓自动点击神器,轻松搞定重复操作
  • Avatar骨骼映射:用大白话讲清楚这件事
  • 最新量化开发分阶段,工具和AI代码都要放对位置
  • 飞致云发布1Panel AI一体机产品家族
  • 从NPC台词到史诗终章:手把手教你用Diffusion+GraphRAG构建动态剧情树(含可商用许可清单)
  • Python第四次作业:从基础语法到实战项目全解析
  • 如何通过智能自动化工具提升英雄联盟游戏体验:League Akari完整指南
  • 实战从零构建Loop Engineering
  • Kimi K3大语言模型:许可证解析、本地部署与API集成指南
  • Java switch语句演进:从传统语法到模式匹配的实战指南
  • FPGA数字逻辑设计实战:基于Verilog的8路彩灯控制器完整开发指南
  • 从静态检索到自主规划:后端工程师的 Agentic RAG 实践手册
  • 5.20华为OD机试真题 新系统 - 多模型版本的最优调度 (JavaPyCC++JsGo)
  • Java通用树形结构工具类设计与实践
  • 深入浅出 Graph Engineering,看这篇就够了
  • 云原生 GitOps 终极武器:Argo CD 从原理到实战全解析
  • 在线png转jpg:系统只认jpg报格式错时跟着做 - 办公小帮手
  • UVa 664递归下降解析器实现与表达式求值技巧
  • LangChain 1.3实战:从零构建AI智能体与RAG系统完整指南
  • 从创意到代码:如何通过编程实践释放开发者创造力
  • 中职计算机应用教资面试:从零散笔记到系统化备考的实战指南
  • Airoha AB157x开发实战:从环境搭建到OLED驱动与系统集成