介紹
基于TS擴展的聲明式開發范式編程語言,以及OpenHarmony的分布式能力實現的一個手柄游戲。
完成本篇Codelab需要兩臺開發板,一臺開發板作為游戲端,一臺開發板作為手柄端,實現如下功能:
- 游戲端呈現飛機移動、發射子彈等效果。
- 游戲端分布式拉起手柄端FA。
- 手柄端與游戲端建立連接,發送指令給游戲端,比如移動飛機,發射子彈和釋放技能等。
最終效果圖如下:
搭建OpenHarmony環境
完成本篇Codelab我們首先要完成開發環境的搭建,本示例以RK3568開發板為例,參照以下步驟進行:
- [獲取OpenHarmony系統版本]:標準系統解決方案(二進制)。
以3.1版本為例: - 搭建燒錄環境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發板的燒錄]
- 鴻蒙開發指導:[
qr23.cn/AKFP8k
]
- 搭建開發環境。
- 開始前請參考[工具準備],完成DevEco Studio的安裝和開發環境配置。
- 開發環境配置完成后,請參考[使用工程向導]創建工程(模板選擇“Empty Ability”),選擇JS或者eTS語言開發。
- 工程創建完成后,選擇使用[真機進行調測]。
2.鴻蒙next文檔籽料+mau123789直接去v拿取
分布式組網
本章節以系統自帶的音樂播放器為例(具體以實際的應用為準),介紹如何完成兩臺設備的分布式組網。
硬件準備:準備兩臺燒錄相同的版本系統的RK3568開發板A、B。
開發板A、B連接同一個WiFi網絡。
打開設置-->WLAN-->點擊右側WiFi開關-->點擊目標WiFi并輸入密碼。將設備A,B設置為互相信任的設備。
- 找到系統應用“音樂”。
- 設備A打開音樂,點擊左下角流轉按鈕,彈出列表框,在列表中會展示遠端設備的id。
- 選擇遠端設備B的id,另一臺開發板(設備B)會彈出驗證的選項框。
- 設備B點擊允許,設備B將會彈出隨機PIN碼,將設備B的PIN碼輸入到設備A的PIN碼填入框中。
配網完畢。
代碼結構解讀
- [HandleEtsOpenHarmony]
- [GameEtsOpenHarmony]
本篇Codelab只對核心代碼進行講解,對于完整代碼,我們會在參考章節中提供下載方式,首先介紹一下整個工程的代碼結構:
└── HandleGameApplication
│── GameEtsOpenHarmony
│
└── HandleEtsOpenHarmony
其中HandleEtsOpenHarmony為手柄端工程代碼,GameEtsOpenHarmony為游戲端工程代碼。
HandleEtsOpenHarmony
- MainAbility:存放應用主頁面。
- pages/index.ets:應用主頁面。
- common/images:存放圖片資源的目錄。
- ServiceAbility:存放ServiceAbility相關文件。
- service.ts:service服務,用于跨設備連接后通訊。
GameEtsOpenHarmony
- MainAbility:存放應用主頁面。
- pages/index.ets:應用主頁面。
- common/images:存放圖片資源。
- model:存放獲取組網內的設備列表相關文件。
- RemoteDeviceModel.ets:獲取組網內的設備列表。
- GameElement.ets:游戲端界面元素的實體類,用于封裝子彈、飛機等元素的屬性。
- ServiceAbility:存放ServiceAbility相關文件。
- service.ts:service服務,用于跨設備連接后通訊。
實現手柄端功能
- 實現布局和樣式。
手柄端有兩個功能:向游戲端發送指令和實時獲取游戲端得分數據。界面上有三個功能組件:藍色圖形組件用于控制游戲端飛機移動方向,黃色圖形組件用于發射子彈,綠色圖形組件用于釋放技能,效果圖如下:
主要代碼如下:@Entry @Component struct Index { ... build() { Stack() { ... Text('score:' + this.score) ... Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.SpaceBetween }) { Stack() { Image('/common/images/bigcircle.png') .width(300) .height(300) Image('/common/images/smallcircle.png') .width(140) .height(140) .position({ x: this.smallPosX, y: this.smallPosY }) // 30+75-35 } ... Row() { Image('/common/images/a.png') .width(160) .height(160) .margin({ right: 20, bottom: 80 }) Image('/common/images/b.png') .width(200) .height(200) }.alignItems(VerticalAlign.Bottom) ... } } }
- 實現搖桿功能。
給搖桿(藍色小圓圖形)添加TouchEvent,動態改變搖桿position屬性使搖桿跟隨手指移動,主要代碼如下:onTouchEvent(event: TouchEvent) { switch (event.type) { case TouchType.Down: this.startX = event.touches[0].screenX; this.startY = event.touches[0].screenY; break; case TouchType.Move: this.curX = event.touches[0].screenX; this.curY = event.touches[0].screenY; this.getSmallCurrentPos(this.curX - this.smallR - 60, this.curY - this.smallR - 60) angle = Math.round(this.calculateAngle()); break; default: break; } }
- 計算搖桿偏移角度。
主要代碼如下:calculateAngle() { var angle = 0 var degree = Math.atan(this.getDisAbsY() / this.getDisAbsX()) * 180 / Math.PI var quadrant = this.quadrant(); switch (quadrant) { case this.QUADRANT_1: // 向右上移動 angle = degree; break; case this.QUADRANT_2: // 向左上移動 angle = 180 - degree; break; case this.QUADRANT_3: // 向左下移動 angle = -180 + degree; break; case this.QUADRANT_4: // 向右下移動 angle = -degree; break; default: angle = 0; break; } return angle; }
- 連接游戲端Service。
當手柄端被游戲端拉起時,獲取游戲端傳遞的數據:游戲端deviceId和分數score。然后通過deviceId連接游戲端Service,主要代碼如下:aboutToAppear() { // 當被拉起時,通過want傳遞的參數同步對端界面UI await featureAbility.getWant((error, want) = > { // 遠端被拉起后,連接游戲端的service if (want.parameters.deviceId) { let remoteDeviceId = want.parameters.deviceId connectRemoteService(remoteDeviceId) } }); } async function connectRemoteService(deviceId) { ... await featureAbility.connectAbility( { 'deviceId': deviceId, 'bundleName': "com.huawei.cookbook", 'abilityName': "com.huawei.cookbook.ServiceAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); }
- 通過RPC發送數據到游戲端。
連接游戲端Service之后,搖桿角度angle和操作類型actionType(1為發射子彈,2為釋放技能)發送給游戲端,主要代碼如下:async function sendMessageToRemoteService() { ... let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeInt(actionType); data.writeInt(angle); await mRemote.sendRequest(1, data, reply, option); }
實現游戲端功能
- 實現布局和樣式。
游戲界面主要由玩家飛機、敵機、子彈和道具(降落傘)等組成,由于敵機和子彈都是多個的,所以使用ForEach來實現,主要代碼如下:@Entry @Component struct Index { build() { Stack() { ... ForEach(this.bullets, item = > { Image(item.imgSrc) .width(item.imgWidth) .height(item.imgHeight) .position({ x: item.positionX, y: item.positionY }) }, item = > item.timestamp.toString()) ForEach(this.enemyPlanes, item = > { Image(item.imgSrc) .width(item.imgWidth) .height(item.imgHeight) .position({ x: item.positionX, y: item.positionY }) }, item = > item.timestamp.toString()) Image('/common/images/planeOne.png') .width(this.planeSize) .height(this.planeSize) .position({ x: this.planePosX, y: this.planePosY }) .onTouch((event: TouchEvent) = > { this.onTouchEvent(event) }) Image('/common/images/props.png') .width(this.propsSize) .height(this.propsSize) .position({ x: this.propsPosX, y: this.propsPosY }) ... } .height('100%') .width('100%') } }
- 實現游戲端元素動畫效果。
飛機、子彈和道具等元素的移動是通過動態改變Image的position屬性來實現的。使用定時器setInterval每隔16ms重新設置界面元素position屬性的值,主要實現代碼如下:startGame() { var that = this setInterval(function () { // 每60*16ms創建一個敵機 if (that.num % 60 == 0) { that.createEnemyPlane() } // 移動子彈 var bulletsTemp: GameElement[] = [] for (var i = 0; i < that.bullets.length; i++) { var bullet = that.bullets[i] bullet.positionY -= 8 // 當子彈移除屏幕外的時候,釋放掉 if (bullet.positionY > 0) { bulletsTemp.push(bullet) } } that.bullets = bulletsTemp // 移動飛機 var enemyPlanesTemp: GameElement[] = [] for (var j = 0; j < that.enemyPlanes.length; j++) { var enemyPlane = that.enemyPlanes[j] enemyPlane.positionY += 6 // 當飛機移除屏幕外的時候,釋放掉 if (enemyPlane.positionY < that.screenHeight) { enemyPlanesTemp.push(enemyPlane) } } that.enemyPlanes = enemyPlanesTemp // 每隔 500*16ms顯示降落傘 if (that.num % 500 == 0) { that.getPropsFlag = true that.propsPosY = -that.propsSize that.propsPosX = Math.round((Math.random() * (that.screenWidth - that.propsSize))) } // 刷新道具位置 if (that.propsPosY < that.screenHeight) { that.propsPosY += 6 } that.checkCollision() }, 16); }
- 判斷元素是否發生碰撞。
在setInterval中改變元素位置的時候同時檢測元素之間是否發生碰撞,子彈和敵機發生碰撞則分數值改變(摧毀小飛機加50分,摧毀大飛機加100分),玩家飛機和道具發生碰撞則道具加1,主要實現代碼如下:checkCollision() { ... for (var i = 0; i < this.enemyPlanes.length; i++) { var enemy = this.enemyPlanes[i]; for (var j = 0; j < this.bullets.length; j++) { var bullet = this.bullets[j]; var inside = this.isInside(bullet, enemy); // 發生碰撞 if (inside) { enemy.imgSrc = '/common/images/boom.png' if (enemy.flag == 1) { this.score += 50 sendMessageToRemoteService(that.score) } else if (enemy.flag == 2) { this.score += 100 sendMessageToRemoteService(that.score) } // 清除子彈 this.enemyPlanes.splice(i, 1); i--; enemy.flag = 3 // 清除被子彈打中敵機 that.bullets.splice(j, 1); j--; } } } // 飛機和降落傘是否發生碰撞 var isGetProps = this.isInside(myPlane, props); if (isGetProps && this.getPropsFlag) { this.getPropsFlag = false this.bombNum++ this.propsPosY = 2000 } }
- 獲取設備列表。
點擊界面右上角的“電腦”圖標,調用registerDeviceListCallback()發現設備列表,并彈出設備列表選擇框DeviceListDialog ,選擇設備后拉起遠端FA。DeviceListDialog 主要代碼如下:@CustomDialog export struct DeviceListDialog { controller: CustomDialogController build() { Column() { Text("選擇設備") .fontWeight(FontWeight.Bold) .fontSize(20) .margin({ top: 20, bottom: 10 }) List() { ForEach(deviceList, item = > { ListItem() { Stack() { Text(item) .fontSize(12) .margin({ top: 10 }) } .onClick(() = > { startRemoteAbility(item) this.controller.close(); }) .padding({ left: 30, right: 30 }) } }, item = > item.toString()) } .height("30%") .align(Alignment.TopStart) ... } } }
- 拉起手柄端FA。
點擊設備列表獲取遠程設備id后,拉起手柄端FA,代碼如下:function startRemoteAbility(deviceId) { var params = { deviceId: localDeviceId } var wantValue = { bundleName: 'com.huawei.cookbook', abilityName: 'com.huawei.cookbook.MainAbility', deviceId: deviceId, parameters: params }; featureAbility.startAbility({ want: wantValue }).then((data) = > { console.info('[game] featureAbility.startAbility finished, localDeviceId=' + localDeviceId + '----deviceId:' + deviceId); // 拉起遠端后,連接遠端service connectRemoteService(deviceId) }); }
- 連接手柄端Service。
拉起手柄端FA后,連接手柄端Service,代碼如下:async function connectRemoteService(deviceId) { // 連接成功的回調 async function onConnectCallback(element, remote) { mRemote = remote; } ... if (remoteDeviceModel.deviceList.length === 0) { return; } await featureAbility.connectAbility( { 'deviceId': deviceId, 'bundleName': "com.huawei.cookbook", 'abilityName': "com.huawei.cookbook.ServiceAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); }
- 通過RPC發送數據到手柄端。
通過RPC將游戲分數發送給手柄端,主要代碼如下:async function sendMessageToRemoteService(score) { console.log('[game]connectRemoteService sendMessageToRemoteService:') if (mRemote == null) { return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeInt(score); await mRemote.sendRequest(1, data, reply, option); }
- Service發布公共事件。
通過Service接收手柄端數據,然后使用CommonEvent模塊將數據發送給FA,主要代碼如下:class GameServiceAbilityStub extends rpc.RemoteObject { ... onRemoteRequest(code, data, reply, option) { console.log('[game]Service onRemoteRequest'); var publishCallBack; if (code === 1) { // 讀取手柄端發送的數據 let actionType = data.readInt(); let angle = data.readInt(); reply.writeInt(100); var params = { actionType: actionType, angle: angle, } var options = { code: 1, data: 'init data', isOrdered: true, bundleName: 'com.huawei.cookbook', parameters: params } publishCallBack = function () {} // 發布公共事件 commonEvent.publish("publish_action", options, publishCallBack); } return true; } }
- FA訂閱公共事件。
訂閱公共事件,接收從Service發送的公共事件數據,actionType 為操作類型(1表示發送子彈指令,2表示釋放技能指令),angle 為飛機移動的角度。接收到數據后執行手柄端發送的指令:移動玩家飛機、發射子彈和釋放技能摧毀所有敵機,主要代碼如下:subscribeEvent() { ... // 訂閱公共事件回調 function SubscribeCallBack(err, data) { let msgData = data.data; let code = data.code; ... // 處理接收到的數據data that.actionType = data.parameters.actionType; that.angle = data.parameters.angle; if (that.actionType == 1) { that.createBullet() } if (that.actionType == 2) { if (that.bombNum > 0) { that.bombNum-- that.destroyAllEnemy() } } if (that.angle != 0) { that.movePlaneByHandle() } } //創建訂閱者回調 function CreateSubscriberCallBack(err, data) { subscriber = data; //訂閱公共事件 commonEvent.subscribe(subscriber, SubscribeCallBack); } //創建訂閱者 commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack); }
審核編輯 黃宇
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
鴻蒙
+關注
關注
56文章
2267瀏覽量
42486 -
OpenHarmony
+關注
關注
25文章
3548瀏覽量
15737
發布評論請先 登錄
相關推薦
OpenHarmony南向能力征集令
1、適配過程中缺少哪些接口能力或者南向能力,需要OpenHarmony去補齊的?例如內核、編譯、器件適配、單板適配等;
2、對標linux,需要OpenHarmony提供哪些能力?比如V4L2
發表于 04-09 15:32
鴻蒙OpenHarmony南向/北向快速開發教程-迅為RK3568開發板
P2_OpenHarmony功能框架
P3_OpenHarmony技術特性
P4_OpenHarmony支持設備類型
p5_南向開發和北向
發表于 07-23 10:44
HarmonyOS教程—分布式游戲手柄
大招的***)。c) 游戲結束,大屏端顯示所有玩家得分,手柄端顯示自己得分。 2. 搭建HarmonyOS環境我們首先需要完成HarmonyOS開發環境搭建,可參照如下步驟進行。安裝DevEco
發表于 09-08 14:13
游戲手柄按鍵的設計資料分享
一、背景近期開發了一個空鼠遙控器的外設產品,採用Nordic51822 MCU芯片,基于BLE4.0標準,與OTT盒子連接,同一時候具有遙控器、空鼠、游戲手柄的功能。當中在按鍵的設計這塊我們走了一些
發表于 11-11 09:12
OpenHarmony開發板運行俄羅斯方塊游戲
本案例展示在OpenHarmony開發板上運行俄羅斯方塊游戲, 通過12864液晶屏進行顯示. 項目底層通過OpenHarmony的HDF框架來驅動, 并基于linkboy圖形引擎編程
發表于 12-03 17:27
如何通過STM32來驅動FC游戲機手柄
相信80后小時候都玩過FC游戲機(又稱:紅白機/小霸王游戲機),那是一代經典,給童年帶來了無限樂趣。本章,介紹如何通過STM32來驅動FC游戲機手柄,將FC游戲機的
發表于 01-05 07:57
基于 OpenHarmony 拳擊健康游戲應用
樣例簡介拳擊健康游戲應用是基于OpenHarmony 3.2 Beta標準系統上開發的eTS應用,本應用運行于RK3568,游戲開始會隨著音樂播放會拳擊方庫進行隨機速度下落,樣例利用N
發表于 08-31 11:20
Unity中國、Cocos為OpenHarmony游戲生態插上騰飛的翅膀
標志著OpenHarmony已經可以開發并流暢運行大型的3A游戲。
Cocos****率先推出支持OpenHarmony的游戲引擎
Coco
發表于 10-23 16:15
基于2.4G RF開發的無線游戲手柄解決方案
平時喜歡玩游戲的朋友,肯定知道鍵鼠在某些類型的游戲適配和操作方面,不如手柄。作為一個游戲愛好者,還得配上一個游戲
OpenHarmony技術大會 | 硬件(南向)生態分論壇嘉賓金句
點擊藍字 ╳ 關注我們 開源項目 OpenHarmony 是每個人的 OpenHarmony 原文標題:OpenHarmony技術大會 | 硬件(南向)生態分論壇嘉賓金句 文章出處:【
評論