概述
本篇Codelab是基于TS擴展的聲明式開發范式編程語言編寫的一個分布式益智拼圖游戲,可以兩臺設備同時開啟一局拼圖游戲,每次點擊九宮格內的圖片,都會同步更新兩臺設備的圖片位置。效果圖如下:
搭建OpenHarmony環境
完成本篇Codelab我們首先要完成開發環境的搭建,本示例以Hi3516DV300開發板為例,參照以下步驟進行:
- [獲取OpenHarmony系統版本]):標準系統解決方案(二進制)。
以3.0版本為例: - 搭建燒錄環境。
- [完成DevEco Device Tool的安裝]
- [完成Hi3516開發板的燒錄]
- 鴻蒙開發指導:[
qr23.cn/AKFP8k
]
- 搭建開發環境。
- 開始前請參考[工具準備],完成DevEco Studio的安裝和開發環境配置。
- 開發環境配置完成后,請參考[使用工程向導]創建工程(模板選擇“Empty Ability”),選擇JS或者eTS語言開發。
- 工程創建完成后,選擇使用[真機進行調測]。
2.鴻蒙HarmonyOS與OpenHarmony文檔籽料mau123789是v直接拿
分布式組網
本章節以系統自帶的音樂播放器為例(具體以實際的應用為準),介紹如何完成兩臺設備的分布式組網。
硬件準備:準備兩臺燒錄相同的版本系統的Hi3516DV300開發板A、B。
開發板A、B連接同一個WiFi網絡。
打開設置-->WLAN-->點擊右側WiFi開關-->點擊目標WiFi并輸入密碼。將設備A,B設置為互相信任的設備。
- 找到系統應用“音樂”。
- 設備A打開音樂,點擊左下角流轉按鈕,彈出列表框,在列表中會展示遠端設備的id。
- 選擇遠端設備B的id,另一臺開發板(設備B)會彈出驗證的選項框。
- 設備B點擊允許,設備B將會彈出隨機PIN碼,將設備B的PIN碼輸入到設備A的PIN碼填入框中。
配網完畢。
代碼結構解讀
本篇Codelab只對核心代碼進行講解,首先來介紹下整個工程的代碼結構:
- MainAbility:存放應用主頁面。
- pages/index.ets:應用主頁面。
- model:存放獲取組網內的設備列表相關文件。
- RemoteDeviceModel.ets:獲取組網內的設備列表。
- ServiceAbility:存放ServiceAbility相關文件。
- service.ts:service服務,創建一個ServiceAbility,用于跨設備連接后通訊。
- resources :存放工程使用到的資源文件。
- resources/rawfile:存放工程中使用的圖片資源文件。
- config.json:配置文件。
實現頁面布局和樣式
在本章節中,您將學會如何制作一個簡單的拼圖游戲。
- 實現主頁面布局和樣式。
在MainAbility/pages/index.ets 主界面文件中添加入口組件PictureGame。PictureGame組件頁面布局代碼如下:
// 入口組件 @Entry @Component struct PictureGame { @Provide imageIndexForPosition: number[] = [] @Provide pictureList: string[]= [] build() { Column() { Text("益智拼圖游戲").fontSize(40).margin({ top: 30, bottom: 30 }) PictureGrid() Row() { Button("重新開始").fontSize(20).margin({ right: 10 }).onClick(() = > { ... }) Button("親子協同").fontSize(20).margin({ left: 10 }).onClick(()= >{ ... }) }.margin(30) Image($rawfile('picture.jpg')).width(300).height(300) } .width('100%') .height('100%') } }
給PictureGame組件添加九宮格拼圖子組件PictureGrid。PictureGrid九宮格拼圖組件主要是由[Grid組件]、[GridItem組件]和[Image組件]構成,頁面布局代碼如下:
// 九宮格拼圖組件 @Component struct PictureGrid { private gridRowTemplate: string = '' @Consume imageIndexForPosition : number[] @Consume pictureList: string[] private heightValue: number aboutToAppear() { var rows = Math.round(this.pictureList.length / 3); this.gridRowTemplate = '1fr '.repeat(rows); this.heightValue = rows * 101 ; } build() { Column() { Grid() { ForEach(this.pictureList.map((item,index)= >{return {i:index,data:item};}),(item,index) = > { GridItem() { Image($rawfile(this.pictureList[item.i])) .width(100) .height(100) .onClick(() = > { ... }) } }, (item: string) = > item.toString()) } .rowsTemplate(this.gridRowTemplate) .columnsTemplate('1fr 1fr 1fr') .columnsGap(1) .rowsGap(1) .height(this.heightValue) .width(303) } } }
在入口組件的生命周期函數aboutToAppear()中調用onRandom()方法,初始化imageIndexForPosition數組。如果Ability是被其他設備拉起的,在aboutToAppear()中調用featureAbility.getWant(),可通過want中的參數重新初始化imageIndexForPosition數組和pictureList數組,入口組件的生命周期函數aboutToAppear()代碼如下:
async aboutToAppear() { let self =this; this.onRandom(); // 當被拉起時,通過want傳遞的參數同步對端界面UI await featureAbility.getWant((error, want) = > { var status = want.parameters; if(want.parameters.pictureList){ self.pictureList = JSON.parse(status.pictureList) self.imageIndexForPosition = status.imageIndexForPosition; // 遠端被拉起后,連接對端的service if(want.parameters.remoteDeviceId) { let remoteDeviceId = want.parameters.remoteDeviceId onConnectRemoteService(remoteDeviceId) } } }); }
- 給"重新開始"按鈕添加點擊事件。
點擊"重新開始"按鈕,調用onRandom()方法,打亂圖片現階段排列順序,在onRandom()調用setupRandomPosition()方法,初始化imageIndexForPosition數組,onRandom()和setupRandomPosition()代碼如下:onRandom() { this.setupRandomPosition(); this.pictureList = [] this.imageIndexForPosition.forEach(value = > { if (value == 9) { this.pictureList.push("--") } else { this.pictureList.push(`picture_0` + value + `.png`) } }); } // 初始化imageIndexForPosition數組 setupRandomPosition() { let list1 = [5, 4, 3, 9, 1, 8, 6, 7, 2]; let list2 = [3, 1, 6, 7, 9, 8, 4, 2, 5]; let list3 = [4, 8, 3, 5, 2, 7, 9, 1, 6]; let list4 = [4, 3, 5, 2, 8, 7, 6, 1, 9]; let lists = [list1, list2, list3, list4]; this.imageIndexForPosition = lists[Math.floor(Math.random() * 4)]; }
- 給九宮格內的每張圖片添加點擊事件。
點擊九宮格內的圖片,調用onchange()方法,每一次點擊后,需要調用onFinish()方法校驗當前imageIndexForPosition 中的元素是否是從小到大排列。其中onChange()和onFinish()方法代碼如下onChange(index) { let self = this; // 相鄰位置數組 let menu = { "1": [2, 4], "2": [1, 3, 5], "3": [2, 6], "4": [1, 5, 7], "5": [2, 4, 6, 8], "6": [3, 5, 9], "7": [4, 8], "8": [5, 7, 9], "9": [6, 8] } // 被點擊的圖片位置 let click_num = index + 1; // 空白圖片位置 let no_see_num = self.imageIndexForPosition.indexOf(9) + 1; // 獲取點擊后能夠移動的圖片位置 let arr = menu[no_see_num]; // 判斷arr是否包含點擊的圖片 if(arr.length==2){ if (!(arr[0]==click_num||arr[1]==click_num)) { } else { let temp = self.imageIndexForPosition[no_see_num - 1]; self.imageIndexForPosition[no_see_num - 1] = self.imageIndexForPosition[click_num - 1]; self.imageIndexForPosition[click_num - 1] = temp; self.pictureList = []; self.imageIndexForPosition.forEach(value = > { if (value == 9) { self.pictureList.push("--") } else { self.pictureList.push(`picture_0` + value + `.png`) } }); } }else if(arr.length==3){ if (!(arr[0]==click_num||arr[1]==click_num||arr[2]==click_num)) { } else { let temp = self.imageIndexForPosition[no_see_num - 1]; self.imageIndexForPosition[no_see_num - 1] = self.imageIndexForPosition[click_num - 1]; self.imageIndexForPosition[click_num - 1] = temp; self.pictureList = []; self.imageIndexForPosition.forEach(value = > { if (value == 9) { self.pictureList.push("--") } else { self.pictureList.push(`picture_0` + value + `.png`) } }); } }else if(arr.length==4){ if (!(arr[0]==click_num||arr[1]==click_num||arr[2]==click_num||arr[3]==click_num)) { } else { let temp = self.imageIndexForPosition[no_see_num - 1]; self.imageIndexForPosition[no_see_num - 1] = self.imageIndexForPosition[click_num - 1]; self.imageIndexForPosition[click_num - 1] = temp; self.pictureList = []; self.imageIndexForPosition.forEach(value = > { if (value == 9) { self.pictureList.push("--") } else { self.pictureList.push(`picture_0` + value + `.png`) } }); } } // 發送消息到遠端Service服務 sendMessageToRemoteService(JSON.stringify(self.imageIndexForPosition)); // 判斷是否完成拼接 self.onFinish(); } onFinish() { let finalList = [1, 2, 3, 4, 5, 6, 7, 8, 9]; if (this.equarList(this.imageIndexForPosition, finalList)) { this.pictureList = []; this.imageIndexForPosition.forEach(value = > { this.pictureList.push("picture_0" + value + ".png") }); prompt.showToast({ message: "success" }); // 完成拼接后斷開Service連接 onDisconnectService(); } }
拉起遠端FA,并連接遠端Service服務
在本章節中,您將學會如何拉起在同一組網內的設備上的FA,創建并連接遠端Service服務。
調用featureAbility.startAbility()方法,拉起遠端FA,并同步界面UI。
點擊"親子協同"按鈕,調用RegisterDeviceListCallback()發現設備列表,并彈出設備列表選擇框CustomDialogExample,選擇設備后拉起遠端FA。CustomDialogExample()代碼如下:// 設備列表彈出框 @CustomDialog struct CustomDialogExample { @State editFlag: boolean = false @Consume imageIndexForPosition : number[] @Consume pictureList: string[] controller: CustomDialogController cancel: () = > void confirm: () = > void build() { Column() { List({ space: 10, initialIndex: 0 }) { ForEach(DeviceIdList, (item) = > { ListItem() { Row() { Text(item) .width('87%').height(50).fontSize(10) .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) .onClick(() = > { onStartRemoteAbility(item,this.imageIndexForPosition,this.pictureList); this.controller.close(); }) Radio({value:item}) .onChange((isChecked) = > { onStartRemoteAbility(item,this.imageIndexForPosition,this.pictureList); this.controller.close(); }).checked(false) } }.editable(this.editFlag) }, item = > item) } }.width('100%').height(200).backgroundColor(0xDCDCDC).padding({ top: 5 }) } }
點擊Text組件或者Radio組件都會調用onStartRemoteAbility()方法拉起遠端FA,onStartRemoteAbility()代碼如下:
function onStartRemoteAbility(deviceId,imageIndexForPosition,pictureList: string[]) { AuthDevice(deviceId); let numDevices = remoteDeviceModel.deviceList.length; if (numDevices === 0) { prompt.showToast({ message: "onStartRemoteAbility no device found" }); return; } var params = { imageIndexForPosition: JSON.stringify(imageIndexForPosition), pictureList : JSON.stringify(pictureList), remoteDeviceId : localDeviceId } var wantValue = { bundleName: 'com.huawei.cookbook', abilityName: 'com.example.openharmonypicturegame.MainAbility', deviceId: deviceId, parameters: params }; featureAbility.startAbility({ want: wantValue }).then((data) = > { // 拉起遠端后,連接遠端service onConnectRemoteService(deviceId) }); }
創建ServiceAbility,步驟如下圖所示:
調用featureAbility.connectAbility方法,連接遠端Service服務,連接成功后返會remote對象。
在featureAbility.startAbility()成功的回調中調用onConnectRemoteService()方法,onConnectRemoteService()方法代碼如下:// 連接遠端Service async function onConnectRemoteService(deviceId) { // 連接成功的回調 async function onConnectCallback(element, remote) { mRemote = remote; } // Service異常死亡的回調 function onDisconnectCallback(element) { } // 連接失敗的回調 function onFailedCallback(code) { prompt.showToast({ message: "onConnectRemoteService onFailed: " + code }); } let numDevices = remoteDeviceModel.deviceList.length; if (numDevices === 0) { prompt.showToast({ message: "onConnectRemoteService no device found" }); return; } connectedAbility = await featureAbility.connectAbility( { deviceId: deviceId, bundleName: "com.huawei.cookbook", abilityName: "com.example.openharmonypicturegame.ServiceAbility", }, { onConnect: onConnectCallback, onDisconnect: onDisconnectCallback, onFailed: onFailedCallback, }, ); }
在配置文件config.json需要設置ServiceAbility的屬性visible為true,代碼如下:
"abilities": [ ... { "visible": true, "srcPath": "ServiceAbility", "name": ".ServiceAbility", "icon": "$media:icon", "srcLanguage": "ets", "description": "$string:description_serviceability", "type": "service" } ],
同時,Service側也需要在onConnect()時返回IRemoteObject,從而定義與Service進行通信的接口。onConnect()需要返回一個IRemoteObject對象,OpenHarmony提供了IRemoteObject的默認實現,通過繼承rpc.RemoteObject來創建自定義的實現類。Service側把自身的實例返回給調用側的代碼如下:
import rpc from "@ohos.rpc"; import commonEvent from '@ohos.commonEvent'; class FirstServiceAbilityStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } else { return null; } } onRemoteRequest(code, data, reply, option) { if (code === 1) { let arr = data.readIntArray(); reply.writeInt(100); // 發布公共事件相關流程 ... } else { } return true; } } export default { // 創建Service的時候調用,用于Service的初始化 onStart() { }, // 在Service銷毀時調用。Service應通過實現此方法來清理任何資源,如關閉線程、注冊的偵聽器等。 onStop() { }, // 在Ability和Service連接時調用,該方法返回IRemoteObject對象,開發者可以在該回調函數中生成對應Service的IPC通信通道 onConnect(want) { try { let value = JSON.stringify(want); } catch(error) { } return new FirstServiceAbilityStub("[pictureGame] first ts service stub"); }, // 在Ability與綁定的Service斷開連接時調用 onDisconnect(want) { let value = JSON.stringify(want); }, // 在Service創建完成之后調用,該方法在客戶端每次啟動該Service時都會調用 onCommand(want, startId) { let value = JSON.stringify(want); } };
RPC跨設備通訊
在本章節中,您將學會在成功連接遠端Service服務的前提下,如何利用RPC進行跨設備通訊。
- 成功連接遠端Service服務的前提下,點擊"重新開始"按鈕或者九宮格內的圖片,都會完成一次跨設備通訊,假如在設備A端點擊"重新開始"按鈕,消息的傳遞是由設備A端的FA傳遞到設備B的Service服務,發送消息的方法sendMessageToRemoteService()代碼如下:
// 連接成功后發送消息 async function sendMessageToRemoteService(imageIndexForPosition) { if (mRemote == null) { prompt.showToast({ message: "mRemote is null" }); return; } let option = new rpc.MessageOption(); let data = new rpc.MessageParcel(); let reply = new rpc.MessageParcel(); data.writeIntArray(JSON.parse(imageIndexForPosition)); await mRemote.sendRequest(1, data, reply, option); let msg = reply.readInt(); }
- 在B端的Service接收消息,當A端成功連接B端Service服務后,在A端會返回一個remote對象,當A端remote對象調用sendRequest()方法后,在B端的Service中的onRemoteRequest()方法中會接收到發送的消息,其中繼承rpc.RemoteObject的類和onRemoteRequest()方法代碼如下:
class FirstServiceAbilityStub extends rpc.RemoteObject{ constructor(des) { if (typeof des === 'string') { super(des); } else { return null; } } onRemoteRequest(code, data, reply, option) { if (code === 1) { // 從data中接收數據 let arr = data.readIntArray(); // 回復接收成功標識 reply.writeInt(100); // 發布公共事件相關流程 ... } else { } return true; } }
Service服務發布公共事件
當Service服務接收到消息后,在onRemoteRequest()發布公共事件,代碼如下:
onRemoteRequest(code, data, reply, option) {
if (code === 1) {
// 從data中接收數據
let arr = data.readIntArray();
// 回復接收成功標識
reply.writeInt(100);
// 公共事件相關信息
var params ={
imageIndexForPosition: arr
}
var options = {
// 公共事件的初始代碼
code: 1,
// 公共事件的初始數據
data: 'init data',、
// 有序公共事件
isOrdered: true,
bundleName: 'com.huawei.cookbook',
parameters: params
}
// 發布公共事件回調
function PublishCallBack() {
}
// 發布公共事件
commonEvent.publish("publish_moveImage", options, PublishCallBack);
} else {
}
return true;
}
在接收到消息后,把接收到的圖片位置數組放入params中,然后發布名稱為"publish_moveImage"的有序公共事件。
FA訂閱公共事件
在本章節中,您將學會如何通過CommonEvent訂閱公共事件。在九宮格組件PictureGrid的生命周期函數aboutToAppear()中,調用訂閱公共事件方法subscribeEvent(),用來訂閱"publish_moveImage"公共事件,subscribeEvent()代碼如下:
subscribeEvent(){
let self = this;
// 用于保存創建成功的訂閱者對象,后續使用其完成訂閱及退訂的動作
var subscriber;
// 訂閱者信息
var subscribeInfo = {
events: ["publish_moveImage"],
priority: 100
};
// 設置有序公共事件的結果代碼回調
function SetCodeCallBack(err) {
}
// 設置有序公共事件的結果數據回調
function SetDataCallBack(err) {
}
// 完成本次有序公共事件處理回調
function FinishCommonEventCallBack(err) {
}
// 訂閱公共事件回調
function SubscribeCallBack(err, data) {
let msgData = data.data;
let code = data.code;
// 設置有序公共事件的結果代碼
subscriber.setCode(code, SetCodeCallBack);
// 設置有序公共事件的結果數據
subscriber.setData(msgData, SetDataCallBack);
// 完成本次有序公共事件處理
subscriber.finishCommonEvent(FinishCommonEventCallBack)
// 處理接收到的數據data
self.imageIndexForPosition = data.parameters.imageIndexForPosition;
self.pictureList = [];
self.imageIndexForPosition.forEach(value = > {
if (value == 9) {
self.pictureList.push("--")
} else {
self.pictureList.push(`picture_0` + value + `.png`)
}
});
self.onFinish();
}
// 創建訂閱者回調
function CreateSubscriberCallBack(err, data) {
subscriber = data;
// 訂閱公共事件
commonEvent.subscribe(subscriber, SubscribeCallBack);
}
// 創建訂閱者
commonEvent.createSubscriber(subscribeInfo, CreateSubscriberCallBack);
}
在FA中訂閱到Service服務發布的"publish_moveImage"事件后,在SubscribeCallBack()回調中重新賦值imageIndexForPosition數組與pictureList數組,從而同步更新界面UI。
審核編輯 黃宇
-
鴻蒙
+關注
關注
57文章
2397瀏覽量
43098 -
HarmonyOS
+關注
關注
79文章
1983瀏覽量
30633 -
OpenHarmony
+關注
關注
25文章
3753瀏覽量
16668
發布評論請先 登錄
相關推薦
拼圖游戲之新鳥求教老鳥
一個基于Labview的簡易拼圖游戲,各位看官可以看看!順便問下為什么我不能進行驗證郵箱任務?
HarmonyOS教程—分布式親子早教系統
基于STM32設計的拼圖小游戲的設計資料分享
LabVIEW拼圖游戲的仿真與設計課程詳細說明
![LabVIEW<b class='flag-5'>拼圖游戲</b>的仿真與設計課程詳細說明](https://file.elecfans.com/web1/M00/BB/39/pIYBAF6hVcKAG2A4AAGejUN9V7k540.png)
評論