鸣涧 发表于 2022-8-25 23:30:06

基于OpenHarmony系统从零开发传炸弹小游戏

基于OpenHarmony系统从零开发传炸弹小游戏
简介
demo基于OpenHarmony系统使用ETS语言进行编写,在邀请用户进行设备认证后,用户根据操作提示完成相应操作,然后通过分布式流转实现随机传递炸弹的效果。


工程目录
完整的项目结构目录如下
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
├─entry\src\main│   │config.json // 应用配置│   ├─ets│   │└─MainAbility│   │      │app.ets //ets应用程序主入口│   │      └─pages│   │            CommonLog.ets // 日志类│   │            game.ets // 游戏首页│   │            RemoteDeviceManager.ets // 设备管理类│   └─resources // 静态资源目录│         ├─base│         │├─element│         │├─graphic│         │├─layout│         │├─media // 存放媒体资源│         │└─profile│         └─rawfile(左右移动查看全部内容)

开发步骤
1、新建OpenHarmony ETS项目:在DevEco Studio中点击File -> New Project ->Empty Ability->Next,Language 选择ETS语言,最后点击Finish即创建成功。

2、编写游戏页面

效果图如上可以分为两部分
2.1 顶部状态提示栏

[*]首先在@entry组件入口build()中使用Stack作为容器,达到图片和文字堆叠的效果;
[*]接着依次写入Image和Column包裹的两个Text组件;

[*]
[*]
[*]
[*]
[*]
[*]
[*]
Stack() {   Image($r("app.media.title")).objectFit(ImageFit.Contain).height(120)   Column() {      Text(this.duration.toString() + 'ms').fontColor(Color.White)      Text(this.touchText).fontColor(Color.White)   }}(左右移动查看全部内容)
2.2 中间游戏炸弹九宫格区域
[*]使用Grid网格容器来编写九宫格区域;
[*]在GridItem中Stack容器依次添加方块背景图片和炸弹图片;
[*]在visibility属性中用bombIndex变量值来决定炸弹显示的位置;
[*]通过onClick点击事件和GestureGroup组合手势加入单击、双击和长按的监听事件;

[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
Stack() {   Image($r("app.media.background")).objectFit(ImageFit.Contain)   Grid() {   ForEach(this.grid, (item) => {       GridItem() {         Stack() {         Image($r("app.media.squares")).objectFit(ImageFit.Contain)         Image($r("app.media.bomb"))             .width('50%')             .objectFit(ImageFit.Contain)             .visibility(this.bombIndex == item ? Visibility.Visible : Visibility.Hidden)             // 炸弹点击事件             .onClick((event) => {               // 单击               this.judgeGame(RuleType.click)             })             .gesture(             GestureGroup(GestureMode.Exclusive,             LongPressGesture({ repeat: false })               .onAction((event: GestureEvent) => {               // 长按               this.judgeGame(RuleType.longPress)               }),             TapGesture({ count: 2 })               .onAction(() => {               // 双击               this.judgeGame(RuleType.doubleClick)               })             )         }       }.forceRebuild(false)   }, item => item)   }   .columnsTemplate('1fr 1fr 1fr')   .rowsTemplate('1fr 1fr 1fr')   .columnsGap(10)   .rowsGap(10)   .width('90%')   .height('75%') }.width('80%').height('70%')(左右移动查看全部内容)
3、添加弹窗3.1 创建规则游戏弹窗
[*]通过**@CustomDialog**装饰器来创建自定义弹窗,使用方式可参考 自定义弹窗;
[*]规则弹窗效果如下,弹窗组成由两个Text和两个Image竖向排列组成,所以我们可以在build()下使用Column容器来包裹,组件代码如下;



[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
@CustomDialog   struct RuleDialog {      controller: CustomDialogController      confirm: () => void      invite: () => void      @consume deviceList: RemoteDevice[]
      build() {         Column() {            Text('游戏规则').fontSize(30).margin(20)            Text('炸弹会随机出现在9个方块内,需要在规定时间内完成指定操作(点击、双击或长按),即可将炸弹传递给下一个人,小心炸弹可是会越来越快的喔!')               .fontSize(24).margin({ bottom: 10 })            Image($r("app.media.btn_start")).objectFit(ImageFit.Contain).height(80).margin(10)               .onClick(() => {                  console.info(TAG + 'Click start game')                  if (checkTrustedDevice(this.remoteDeviceModel)) {                     this.controller.close()                     this.confirm()                  }               })            Image($r("app.media.btn_Invite")).objectFit(ImageFit.Contain).height(80).margin(10)               .onClick(() => {                  this.invite()               })         }.width('90%')         .margin(20)         .backgroundColor(Color.White)      }   }(左右移动查看全部内容)

[*]在@entry创建CustomDialogController对象并传入弹窗所需参数,后面可通过该对象open()和close()方法进行打开和关闭弹窗;

[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
@Provide deviceList: RemoteDevice[] = []private ruleDialog: CustomDialogController = new CustomDialogController({   builder: RuleDialog({      invite: () => this.InvitePlayer(),      confirm: () => this.startGame(),      deviceList: this.deviceList   }),   autoCancel: false})(左右移动查看全部内容)
3.2 创建游戏失败弹窗,并添加动画效果


[*]编写弹窗布局:将游戏失败文本、炸弹图片和再来一局按钮图片放置于Column容器中;
[*]用变量来控制动画起始和结束的位置:用Flex容器包裹炸弹图片,并用@State装饰变量toggle,通过变量来动态修改Flex的direction属性;

[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
@State toggle: boolean = trueprivate controller: CustomDialogController@Consume deviceList: RemoteDevice[]private confirm: () => voidprivate interval = null
build() {   Column() {      Text('游戏失败').fontSize(30).margin(20)      Flex({         direction: this.toggle ? FlexDirection.Column : FlexDirection.ColumnReverse,         alignItems: ItemAlign.Center      })      {         Image($r("app.media.bomb")).objectFit(ImageFit.Contain).height(80)      }.height(200)
      Image($r("app.media.btn_restart")).objectFit(ImageFit.Contain).height(120).margin(10)         .onClick(() => {               this.controller.close()               this.confirm()         })   }   .width('80%')   .margin(50)   .backgroundColor(Color.White)}(左右移动查看全部内容)

[*]设置动画效果:使用 animateTo 显式动画接口炸弹位置切换时添加动画,并且设置定时器定时执行动画;

[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
aboutToAppear() {   this.setBombAnimate()}
setBombAnimate() {   let fun = () => {      this.toggle = !this.toggle;   }   this.interval = setInterval(() => {      animateTo({ duration: 1500, curve: Curve.Sharp }, fun)   }, 1600)}(左右移动查看全部内容)
4、添加分布式流转分布式流转需要在同一网络下通过 DeviceManager组件 进行设备间发现和认证,获取到可信设备的deviceId调用 featureAbility.startAbility ,即可把应用程序流转到另一设备。
原本分布式流转应用流程如下:

[*]创建DeviceManager实例;
[*]调用实例的startDeviceDiscovery(),开始设备发现未信任设备;
[*]设置设备状态监听on('deviceStateChange',callback),监听设备上下线状态;
[*]设置设备状态监听on('deviceFound',callback),监听设备发现;
[*]传入未信任设备参数,调用实例authenticateDevice方法,对设备进行PIN码认证;
[*]若是已信任设备,可通过实例的getTrustedDeviceListSync()方法来获取设备信息;
[*]将设备信息中的deviceId传入featureAbility.startAbility方法,实现流转;
[*]流转接收方可通过featureAbility.getWant()获取到发送方携带的数据;
[*]注销设备发现监听off('deviceFound');
[*]注销设备状态监听off('deviceStateChange');

项目中将上面设备管理封装至RemoteDeviceManager,通过RemoteDeviceManager的四个方法来动态维护deviceList设备信息列表。

项目实现分布式流转只需如下流程:4.1 创建RemoteDeviceManager实例
[*]导入RemoteDeviceManager

[*]
import {RemoteDeviceManager} from './RemoteDeviceManager'(左右移动查看全部内容)

[*]声明@Provide装饰的设备列表变量deviceList,和创建RemoteDeviceManager实例。

[*]
[*]
@Provide deviceList: RemoteDevice[] = []private remoteDm: RemoteDeviceManager = new RemoteDeviceManager(this.deviceList)(左右移动查看全部内容)
4.2 刷新设备列表在生命周期aboutToAppear中,调用刷新设备列表和开始发现设备。
aboutToAppear定义:函数在创建自定义组件的新实例后,在执行其build函数之前执行。
[*]
[*]
[*]
[*]
aboutToAppear() {this.remoteDm.refreshRemoteDeviceList() // 刷新设备列表this.remoteDm.startDeviceDiscovery() // 开始发现设备}(左右移动查看全部内容)
4.3 设备认证
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
invitePlayer(remoteDevice:RemoteDevice) {if (remoteDevice.status == RemoteDeviceStatus.ONLINE) {    prompt.showToast({ message: "Already invited!" })    return}this.remoteDm.authDevice(remoteDevice).then(() => {    prompt.showToast({ message: "Invite success! deviceName=" + remoteDevice.deviceName })}).catch(() => {    prompt.showToast({ message: "Invite fail!" })})}(左右移动查看全部内容)
4.4 跨设备流转从deviceList中获取设备列表在线的设备Id,通过featureAbility.startAbility进行流转。
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
async startAbilityRandom() {let deviceId = this.getRandomDeviceId() // 随机获取设备idCommonLog.info('featureAbility.startAbility deviceId=' + deviceId);let bundleName = await getBundleName()let wantValue = {    bundleName: bundleName,    abilityName: 'com.sample.bombgame.MainAbility',    deviceId: deviceId,    parameters: {      ongoing: true,      transferNumber: this.transferNumber + 1    }};featureAbility.startAbility({    want: wantValue}).then((data) => {    CommonLog.info(' featureAbility.startAbility finished, ' + JSON.stringify(data));    featureAbility.terminateSelf((error) => {      CommonLog.info('terminateSelf finished, error=' + error);    });});}(左右移动查看全部内容)
4.5 注销监听在声明周期aboutToDisappear进行注销监听。
aboutToDisappear定义:函数在自定义组件析构消耗之前执行。
[*]
[*]
[*]
aboutToDisappear() {this.remoteDm.stopDeviceDiscovery() // 注销监听}(左右移动查看全部内容)
5、编写游戏逻辑5.1 开始游戏
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
startGame() {CommonLog.info('startGame');this.randomTouchRule() // 随机游戏点击规则this.setRandomBomb() // 随机生成炸弹位置this.stopCountDown() // 停止倒计时if (this.transferNumber < 10) {    this.duration = 3000 - this.transferNumber * 100} else {    this.duration = 2000}const interval: number = 500// 开始倒计时this.timer = setInterval(() => {    if (this.duration <= interval) {      this.duration = 0      clearInterval(this.timer)      this.timer = null      this.gameFail()    } else {      this.duration -= interval    }}, interval)}(左右移动查看全部内容)
5.2 判断输赢编写判断逻辑,用于不同的点击事件中调用。
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
[*]
/** * 判断游戏输赢 * @param operation 点击类型 */judgeGame(operation:RuleType) {   this.stopCountDown()   if (operation != this.ruleText) {      this.gameFail()   } else {      prompt.showToast({ message: "finish" })      this.bombIndex = -1      this.startAbilityRandom()   }}(左右移动查看全部内容)
5.3 游戏失败游戏失败,弹出游戏失败弹框。
[*]
[*]
[*]
[*]
[*]
[*]
[*]
gameFail() {prompt.showToast({    message: 'Game Fail'})CommonLog.info('gameFail');this.gameFailDialog.open()}(左右移动查看全部内容)

项目下载和导入
项目仓库地址:https://gitee.com/openharmony-sig/knowledge_demo_temp/tree/master/FA/Entertainment/BombGame

1)git下载
[*]
git clone https://gitee.com/openharmony-sig/knowledge_demo_temp.git(左右移动查看全部内容)
2)项目导入打开DevEco Studio,点击File->Open->下载路径/FA/Entertainment/BombGame

约束与限制
1、设备编译约束润和HiSpark Taurus AI Camera(Hi3516d)开发板套件:
[*]Hi3516DV300开发板标准设备HelloWorld,参考环境准备、编译和烧录章节;

润和大禹系列HH-SCDAYU200开发套件:
[*]开发板上新 | RK3568开发板上丝滑体验OpenHarmony标准系统

2、应用编译约束
[*]参考 应用开发快速入门
[*]集成开发环境:DevEco Studio 3.0.0.601版本,下载地址;
[*]OpenHarmony SDK 3.0.0.0;


页: [1]
查看完整版本: 基于OpenHarmony系统从零开发传炸弹小游戏