介紹
基于TS擴展的聲明式開發(fā)范式編程語言,以及OpenHarmony的分布式能力實現(xiàn)的一個手柄游戲。
完成本篇Codelab需要兩臺開發(fā)板,一臺開發(fā)板作為游戲端,一臺開發(fā)板作為手柄端,實現(xiàn)如下功能:
- 游戲端呈現(xiàn)飛機移動、發(fā)射子彈等效果。
- 游戲端分布式拉起手柄端FA。
- 手柄端與游戲端建立連接,發(fā)送指令給游戲端,比如移動飛機,發(fā)射子彈和釋放技能等。
最終效果圖如下:
搭建OpenHarmony環(huán)境
完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進行:
- [獲取OpenHarmony系統(tǒng)版本]:標準系統(tǒng)解決方案(二進制)。
以3.1版本為例: - 搭建燒錄環(huán)境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發(fā)板的燒錄]
- 鴻蒙開發(fā)指導:[
qr23.cn/AKFP8k
]
- 搭建開發(fā)環(huán)境。
- 開始前請參考[工具準備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
- 開發(fā)環(huán)境配置完成后,請參考[使用工程向?qū)創(chuàng)建工程(模板選擇“Empty Ability”),選擇JS或者eTS語言開發(fā)。
- 工程創(chuàng)建完成后,選擇使用[真機進行調(diào)測]。
2.鴻蒙next文檔籽料+mau123789直接去v拿取
分布式組網(wǎng)
本章節(jié)以系統(tǒng)自帶的音樂播放器為例(具體以實際的應用為準),介紹如何完成兩臺設備的分布式組網(wǎng)。
硬件準備:準備兩臺燒錄相同的版本系統(tǒng)的RK3568開發(fā)板A、B。
開發(fā)板A、B連接同一個WiFi網(wǎng)絡。
打開設置-->WLAN-->點擊右側(cè)WiFi開關(guān)-->點擊目標WiFi并輸入密碼。將設備A,B設置為互相信任的設備。
- 找到系統(tǒng)應用“音樂”。
- 設備A打開音樂,點擊左下角流轉(zhuǎn)按鈕,彈出列表框,在列表中會展示遠端設備的id。
- 選擇遠端設備B的id,另一臺開發(fā)板(設備B)會彈出驗證的選項框。
- 設備B點擊允許,設備B將會彈出隨機PIN碼,將設備B的PIN碼輸入到設備A的PIN碼填入框中。
配網(wǎng)完畢。
代碼結(jié)構(gòu)解讀
- [HandleEtsOpenHarmony]
- [GameEtsOpenHarmony]
本篇Codelab只對核心代碼進行講解,對于完整代碼,我們會在參考章節(jié)中提供下載方式,首先介紹一下整個工程的代碼結(jié)構(gòu):
└── HandleGameApplication
│── GameEtsOpenHarmony
│
└── HandleEtsOpenHarmony
其中HandleEtsOpenHarmony為手柄端工程代碼,GameEtsOpenHarmony為游戲端工程代碼。
HandleEtsOpenHarmony
- MainAbility:存放應用主頁面。
- pages/index.ets:應用主頁面。
- common/images:存放圖片資源的目錄。
- ServiceAbility:存放ServiceAbility相關(guān)文件。
- service.ts:service服務,用于跨設備連接后通訊。
GameEtsOpenHarmony
- MainAbility:存放應用主頁面。
- pages/index.ets:應用主頁面。
- common/images:存放圖片資源。
- model:存放獲取組網(wǎng)內(nèi)的設備列表相關(guān)文件。
- RemoteDeviceModel.ets:獲取組網(wǎng)內(nèi)的設備列表。
- GameElement.ets:游戲端界面元素的實體類,用于封裝子彈、飛機等元素的屬性。
- ServiceAbility:存放ServiceAbility相關(guān)文件。
- service.ts:service服務,用于跨設備連接后通訊。
實現(xiàn)手柄端功能
- 實現(xiàn)布局和樣式。
手柄端有兩個功能:向游戲端發(fā)送指令和實時獲取游戲端得分數(shù)據(jù)。界面上有三個功能組件:藍色圖形組件用于控制游戲端飛機移動方向,黃色圖形組件用于發(fā)射子彈,綠色圖形組件用于釋放技能,效果圖如下:
主要代碼如下:@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) ... } } }
- 實現(xiàn)搖桿功能。
給搖桿(藍色小圓圖形)添加TouchEvent,動態(tài)改變搖桿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。
當手柄端被游戲端拉起時,獲取游戲端傳遞的數(shù)據(jù):游戲端deviceId和分數(shù)score。然后通過deviceId連接游戲端Service,主要代碼如下:aboutToAppear() { // 當被拉起時,通過want傳遞的參數(shù)同步對端界面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發(fā)送數(shù)據(jù)到游戲端。
連接游戲端Service之后,搖桿角度angle和操作類型actionType(1為發(fā)射子彈,2為釋放技能)發(fā)送給游戲端,主要代碼如下: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); }
實現(xiàn)游戲端功能
- 實現(xiàn)布局和樣式。
游戲界面主要由玩家飛機、敵機、子彈和道具(降落傘)等組成,由于敵機和子彈都是多個的,所以使用ForEach來實現(xiàn),主要代碼如下:@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%') } }
- 實現(xiàn)游戲端元素動畫效果。
飛機、子彈和道具等元素的移動是通過動態(tài)改變Image的position屬性來實現(xiàn)的。使用定時器setInterval每隔16ms重新設置界面元素position屬性的值,主要實現(xiàn)代碼如下:startGame() { var that = this setInterval(function () { // 每60*16ms創(chuàng)建一個敵機 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); }
- 判斷元素是否發(fā)生碰撞。
在setInterval中改變元素位置的時候同時檢測元素之間是否發(fā)生碰撞,子彈和敵機發(fā)生碰撞則分數(shù)值改變(摧毀小飛機加50分,摧毀大飛機加100分),玩家飛機和道具發(fā)生碰撞則道具加1,主要實現(xiàn)代碼如下: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); // 發(fā)生碰撞 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--; } } } // 飛機和降落傘是否發(fā)生碰撞 var isGetProps = this.isInside(myPlane, props); if (isGetProps && this.getPropsFlag) { this.getPropsFlag = false this.bombNum++ this.propsPosY = 2000 } }
- 獲取設備列表。
點擊界面右上角的“電腦”圖標,調(diào)用registerDeviceListCallback()發(fā)現(xiàn)設備列表,并彈出設備列表選擇框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) { // 連接成功的回調(diào) 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發(fā)送數(shù)據(jù)到手柄端。
通過RPC將游戲分數(shù)發(fā)送給手柄端,主要代碼如下: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發(fā)布公共事件。
通過Service接收手柄端數(shù)據(jù),然后使用CommonEvent模塊將數(shù)據(jù)發(fā)送給FA,主要代碼如下:class GameServiceAbilityStub extends rpc.RemoteObject { ... onRemoteRequest(code, data, reply, option) { console.log('[game]Service onRemoteRequest'); var publishCallBack; if (code === 1) { // 讀取手柄端發(fā)送的數(shù)據(jù) 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 () {} // 發(fā)布公共事件 commonEvent.publish("publish_action", options, publishCallBack); } return true; } }
- FA訂閱公共事件。
訂閱公共事件,接收從Service發(fā)送的公共事件數(shù)據(jù),actionType 為操作類型(1表示發(fā)送子彈指令,2表示釋放技能指令),angle 為飛機移動的角度。接收到數(shù)據(jù)后執(zhí)行手柄端發(fā)送的指令:移動玩家飛機、發(fā)射子彈和釋放技能摧毀所有敵機,主要代碼如下:subscribeEvent() { ... // 訂閱公共事件回調(diào) function SubscribeCallBack(err, data) { let msgData = data.data; let code = data.code; ... // 處理接收到的數(shù)據(jù)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() } } //創(chuàng)建訂閱者回調(diào) function CreateSubscriberCallBack(err, data) { subscriber = data; //訂閱公共事件 commonEvent.subscribe(subscriber, SubscribeCallBack); } //創(chuàng)建訂閱者 commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack); }
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
57文章
2351瀏覽量
42849 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3722瀏覽量
16313
發(fā)布評論請先 登錄
相關(guān)推薦
評論