1.介紹
本文將介紹分布式游戲鑒權(quán)應(yīng)用。操作過(guò)程為:
- 設(shè)備A點(diǎn)擊“開(kāi)始游戲”按鈕,開(kāi)始搜索周邊設(shè)備。
- 設(shè)備A顯示周邊設(shè)備,點(diǎn)擊設(shè)備B并發(fā)起連接請(qǐng)求,遠(yuǎn)程拉起設(shè)備B的FA。
- 設(shè)備B收到請(qǐng)求后,選擇是否允許“開(kāi)啟游戲”。
- 選擇允許,遠(yuǎn)程拉起設(shè)備A,并傳遞允許的信息,設(shè)備A解析了信息后自動(dòng)開(kāi)始游戲。
- 選擇不允許,遠(yuǎn)程拉起設(shè)備A,并傳遞不允許的信息,設(shè)備A回到最初的狀態(tài),并提示申請(qǐng)鑒權(quán)未通過(guò)。
效果圖展示:
2.相關(guān)概念
[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
3.搭建OpenHarmony環(huán)境
完成本篇Codelab我們首先要完成開(kāi)發(fā)環(huán)境的搭建,本示例以RK3568開(kāi)發(fā)板為例,參照以下步驟進(jìn)行:
- [獲取OpenHarmony系統(tǒng)版本]:標(biāo)準(zhǔn)系統(tǒng)解決方案(二進(jìn)制)。
以3.1版本為例: - 搭建燒錄環(huán)境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開(kāi)發(fā)板的燒錄](méi)
- 搭建開(kāi)發(fā)環(huán)境。
- 開(kāi)始前請(qǐng)參考[工具準(zhǔn)備] ,完成DevEco Studio的安裝和開(kāi)發(fā)環(huán)境配置。
- 開(kāi)發(fā)環(huán)境配置完成后,請(qǐng)參考[使用工程向?qū) 創(chuàng)建工程(模板選擇“Empty Ability”) ,選擇JS或者eTS語(yǔ)言開(kāi)發(fā)。
- 工程創(chuàng)建完成后,選擇使用[真機(jī)進(jìn)行調(diào)測(cè)] 。
4.分布式組網(wǎng)
本章節(jié)以系統(tǒng)自帶的音樂(lè)播放器為例(具體以實(shí)際的應(yīng)用為準(zhǔn)),介紹如何完成兩臺(tái)設(shè)備的分布式組網(wǎng)。
- 硬件準(zhǔn)備:準(zhǔn)備兩臺(tái)燒錄相同的版本系統(tǒng)的RK3568開(kāi)發(fā)板A、B。
- 開(kāi)發(fā)板A、B連接同一個(gè)WiFi網(wǎng)絡(luò)。
打開(kāi)設(shè)置-->WLAN-->點(diǎn)擊右側(cè)WiFi開(kāi)關(guān)-->點(diǎn)擊目標(biāo)WiFi并輸入密碼。
將設(shè)備A,B設(shè)置為互相信任的設(shè)備。
- 找到系統(tǒng)應(yīng)用“音樂(lè)”。
- 設(shè)備A打開(kāi)音樂(lè),點(diǎn)擊左下角流轉(zhuǎn)按鈕,彈出列表框,在列表中會(huì)展示遠(yuǎn)端設(shè)備的id。
- 選擇遠(yuǎn)端設(shè)備B的id,另一臺(tái)開(kāi)發(fā)板(設(shè)備B)會(huì)彈出驗(yàn)證的選項(xiàng)框。
- 設(shè)備B點(diǎn)擊允許,設(shè)備B將會(huì)彈出隨機(jī)PIN碼,將設(shè)備B的PIN碼輸入到設(shè)備A的PIN碼填入框中。
配網(wǎng)完畢。
5.代碼結(jié)構(gòu)解讀
本篇Codelab只對(duì)核心代碼進(jìn)行講解,對(duì)于完整代碼,我們會(huì)在參考中提供下載方式,整個(gè)工程的代碼結(jié)構(gòu)如下:
- common:存放公共資源
- pages:存放頁(yè)面
index.js:主頁(yè)面 - config.json:配置文件
6.初始化頁(yè)面
在本章節(jié)中,您將學(xué)會(huì)如何進(jìn)行頁(yè)面初始化。
- 在data下定義需要使用的字段。
data: { // 目標(biāo)設(shè)備Id,用于記錄申請(qǐng)過(guò)來(lái)的設(shè)備Id targetDeviceId: '', // 是否同意玩游戲 isAgree: false, // 是否顯示開(kāi)始游戲圖標(biāo) showStart: false },
- 根據(jù)Ability啟動(dòng)參數(shù)來(lái)判斷頁(yè)面被拉起的狀態(tài)。
在拉起頁(yè)面時(shí)候,設(shè)置requestType為分布式拉起頁(yè)面的業(yè)務(wù)請(qǐng)求類型(申請(qǐng)鑒權(quán)或者回復(fù)鑒權(quán)結(jié)果),如果沒(méi)有requestType參數(shù),則為手動(dòng)拉起本機(jī)Ability。然后通過(guò)分析requestType參數(shù)的值來(lái)進(jìn)行不同的業(yè)務(wù)邏輯操作。onInit() { // 獲取Ability啟動(dòng)參數(shù) featureAbility.getWant().then((want) = > { if (want.parameters !== undefined && want.parameters !== null && want.parameters !== '') { // 如果是請(qǐng)求授權(quán)被拉起Ability(requestType === 0),則記錄申請(qǐng)權(quán)限的設(shè)備id if (want.parameters.requestType === 0) { this.isGame = false; this.targetDeviceId = want.parameters.localDeviceId; } else if (want.parameters.requestType === 1) { // 如果是授權(quán)后被拉起Ability(requestType === 1),則根據(jù)授權(quán)情況判斷是否進(jìn)行游戲 if (want.parameters.isAgree !== null) { this.isAgree = want.parameters.isAgree; if (this.isAgree === true) { this.isGame = true; this.isStart = true; this.startGame(); } else { this.showStart = true; prompt.showToast({ message: '申請(qǐng)授權(quán)未被允許', duration: 5000 }); } } this.targetDeviceId = want.parameters.localDeviceId; } else { // 如果沒(méi)有請(qǐng)求類型字段(requestType),則表明是手動(dòng)啟動(dòng)的Ability,此時(shí)顯示啟動(dòng)游戲圖標(biāo) this.showStart = true; } } });
7.顯示鑒權(quán)設(shè)備
在本章節(jié)中,您將學(xué)會(huì)如何顯示需要鑒權(quán)的設(shè)備列表。效果圖如下:
在index.js文件中,在data下定義deviceList數(shù)組,用來(lái)表示周邊的設(shè)備。代碼如下:
export default { data: { //可授權(quán)的設(shè)備 deviceList: [] } }
在index.hml文件中:
- 定義一個(gè)"開(kāi)始游戲"的button組件,設(shè)置startFA的點(diǎn)擊事件;
- 顯示周邊設(shè)備的對(duì)話框dialog,使用list 、list-item實(shí)現(xiàn)設(shè)備列表的展示;
- 通過(guò)for屬性遍歷deviceList數(shù)組,$item是每一項(xiàng)的實(shí)例;
- 給每一項(xiàng)設(shè)置selectDevice點(diǎn)擊事件,參數(shù)為設(shè)備的networkId。
代碼如下:
< div class="container" > < button class="text-button" onclick="startFA" >開(kāi)始游戲< /button > < dialog id="continueAbilityDialog" class="dialog-main" oncancel="cancelDialog" > < div class="dialog-div" > < text class="dialog_title_text" >選擇設(shè)備< /text > < list class="dialog_device_list" divider="true" > < list-item for="{{ deviceList }}" class="device_list_item" > < div > < label class="device_item_title" target="{{ $item.id }}" >{{ $item.name }}< /label > < input class="device_item_radio" type="radio" checked="{{ $item.id === 'localhost' }}" id="{{ $item.id }}" name="radioSample" value="{{ $item.id }}" onchange="onRadioChange({{ $item.id }})" >< /input > < /div > < /list-item > < /list > < div class="inner-btn" > < button class="dialog_cancel_button" type="text" value="取消" onclick="onDismissDialogClicked" >< /button > < /div > < /div > < /dialog > < /div >
在index.css文件中,定義布局和樣式。代碼如下:
.container { flex-direction: column; justify-content: center; align-items: center; } .text-button{ background-color: #5959f1; color: #FFFFFF; text-align: center; font-size: 16px; width: 80px; height: 40px; border-radius: 8px; } .select-device-dialog { width: 90%; height: 33%; } .select-device-wrapper { margin: 5%; width: 90%; height: 90%; flex-direction: column; } .select-device-title { width: 100%; height: 20%; text-align: left; font-size: 20px; } .select-device-list { width: 100%; height: 60%; text-align: left; font-size: 15px; } .select-device-item { width: 100%; height: 33%; } .select-device-item-left { width: 100%; height: 100%; text-align: left; font-size: 16px; } .dialog-main { width: 500px; } .dialog-div { flex-direction: column; align-items: center; } .dialog_title_text { width: 434px; height: 80px; font-size: 32px; font-weight: 600; } .dialog_cancel_button { width: 100%; font-size: 32px; }
在index.js文件中:
- 定義createDeviceManager方法,獲得設(shè)備管理器實(shí)例并進(jìn)行獲得同一網(wǎng)段下的所有在線設(shè)備;
// 創(chuàng)建實(shí)例 createDeviceManager() { if (dmClass !== null) { return; } deviceManager.createDeviceManager('com.huawei.cookbook', (err, data) = > { if (err) { return; } subscribeId = Math.floor(Math.random() * 10000 + 1000); dmClass = data; dmClass.on('dmFaCallback', data = > this.log('dmFaCallback on:' + JSON.stringify(data))); dmClass.on('deviceStateChange', mFilterOption, data = > this.log('deviceStateChange on:' + JSON.stringify(data))); dmClass.on('deviceFound', data = > this.log('deviceFound on:' + JSON.stringify(data))); dmClass.on('discoverFail', data = > this.log('discoverFail on:' + JSON.stringify(data))); dmClass.on('serviceDie', data = > this.log('serviceDie on:' + JSON.stringify(data))); this.getLocalDeviceInfoSync(); const deviceInfoList = dmClass.getTrustedDeviceListSync(); const list = []; list[0] = DEVICE_LIST_LOCALHOST; if (deviceInfoList.length > 0) { for (let i = 0; i < deviceInfoList.length; i++) { list[i + 1] = { name: deviceInfoList[i].deviceName, id: deviceInfoList[i].deviceId }; } } this.deviceList = list; }); },
- 定義getLocalDeviceInfoSync方法,獲取本設(shè)備信息;
getLocalDeviceInfoSync() { if (dmClass != null) { deviceInfo = dmClass.getLocalDeviceInfoSync(); } else { prompt.showToast({ message: '請(qǐng)先初始化' }); } },
- 將獲取到的同一網(wǎng)段下的所有在線設(shè)備信息放入deviceList數(shù)組中;
- 通過(guò)this.$element('showDialog')找到hml文件中dialog組件,調(diào)用show()方法顯示對(duì)話框。
- 定義createDeviceManager方法,獲得設(shè)備管理器實(shí)例并進(jìn)行獲得同一網(wǎng)段下的所有在線設(shè)備;
8.鑒權(quán)申請(qǐng)與回應(yīng)
在本章節(jié)中,您將學(xué)會(huì)如何從設(shè)備A拉起設(shè)備B的FA,并將設(shè)備A的標(biāo)識(shí)信息發(fā)送給設(shè)備B。效果圖如下:
申請(qǐng)鑒權(quán)(同意游戲)
申請(qǐng)鑒權(quán)(拒絕游戲)
- 設(shè)備A點(diǎn)擊開(kāi)始游戲,顯示可以進(jìn)行鑒權(quán)申請(qǐng)的設(shè)備列表,并選中設(shè)備申請(qǐng)游戲鑒權(quán)
- 定義startFa方法,用以顯示設(shè)備列表對(duì)話框;
startFA() { this.$element('continueAbilityDialog').show(); },
- 定義onRadioChange方法,用以監(jiān)聽(tīng)選擇的設(shè)備變化;
onRadioChange(inputValue, e) { if (inputValue === e.value) { if (e.value === 'localhost') { this.$element('continueAbilityDialog').close(); return; } if (this.deviceList.length > 0) { for (let i = 0; i < this.deviceList.length; i++) { if (this.deviceList[i].id === e.value) { this.startAbilityContinuation(this.deviceList[i].id, this.deviceList[i].name); } } } } },
- 定義startAbilityContinuation方法,用以申請(qǐng)鑒權(quán);
startAbilityContinuation(deviceId, deviceName) { this.$element('continueAbilityDialog').close(); const wantValue = { bundleName: 'com.huawei.cookbook', abilityName: 'com.huawei.gameauthopenh.MainAbility', deviceId: deviceId, // localDeviceId:申請(qǐng)?jiān)O(shè)備的id,requestType,請(qǐng)求類型:0,申請(qǐng)鑒權(quán) parameters: {'localDeviceId': deviceInfo.deviceId, 'requestType': 0} }; featureAbility.startAbility({ want: wantValue }).then((data) = > { // 銷毀自身Ability featureAbility.terminateSelf(); }); },
- 定義startFa方法,用以顯示設(shè)備列表對(duì)話框;
- 設(shè)備B被設(shè)備A分布式拉起,對(duì)游戲進(jìn)行授權(quán)
- index.hml頁(yè)面添加div用以顯示授權(quán)選項(xiàng);
class="div-permit" if="{{!isGame}}" > < text class="text-title" >來(lái)自遠(yuǎn)程合成設(shè)備小游戲權(quán)限請(qǐng)求,是否允許?< /text > class="div-button" > < text class="text-allow" onclick="responds(true)" >允許< /text > < text class="text-reject" onclick="responds(false)" >不允許< /text >
- 定義responds方法用以反饋鑒權(quán)結(jié)果,并分布式拉起設(shè)備A的Ability;
responds(value) { const wantValue = { bundleName: 'com.huawei.cookbook', abilityName: 'com.huawei.gameauthopenh.MainAbility', deviceId: this.targetDeviceId, parameters: {'localDeviceId': deviceInfo.deviceId, 'requestType': 1, 'isAgree': value} }; featureAbility.startAbility({ want: wantValue }).then((data) = > { console.info('featureAbility.startAbility finished, ' + JSON.stringify(data)); featureAbility.terminateSelf(); }); },
- index.hml頁(yè)面添加div用以顯示授權(quán)選項(xiàng);
- 設(shè)備A被分布式拉起并解析鑒權(quán)結(jié)果,并根據(jù)結(jié)果執(zhí)行不同的操作;
在onInit方法中調(diào)用featureAbility.getWant()來(lái)獲取啟動(dòng)信息并根據(jù)啟動(dòng)信息判斷游戲申請(qǐng)是否被拒絕;onInit() { ... // 獲取Ability啟動(dòng)參數(shù) featureAbility.getWant().then((want) = > { if (want.parameters !== undefined && want.parameters !== null && want.parameters !== '') { // 如果是請(qǐng)求授權(quán)被拉起Ability(requestType === 0),則記錄申請(qǐng)權(quán)限的設(shè)備id if (want.parameters.requestType === 0) { this.isGame = false; this.targetDeviceId = want.parameters.localDeviceId; } else if (want.parameters.requestType === 1) { // 如果是授權(quán)后被拉起Ability(requestType === 1),則根據(jù)授權(quán)情況判斷是否進(jìn)行游戲 if (want.parameters.isAgree !== null) { this.isAgree = want.parameters.isAgree; if (this.isAgree === true) { this.isGame = true; this.isStart = true; this.startGame(); } else { this.showStart = true; prompt.showToast({ message: '申請(qǐng)授權(quán)未被允許', duration: 5000 }); } } this.targetDeviceId = want.parameters.localDeviceId; } else { // 如果沒(méi)有請(qǐng)求類型字段(requestType),則表明是手動(dòng)啟動(dòng)的Ability,此時(shí)顯示啟動(dòng)游戲圖標(biāo) this.showStart = true; } } }); ... },
- index.css文件新增內(nèi)容如下:
.div-permit{ flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } .div-button{ flex-direction: row; justify-content: center; align-items: center; } .text-title{ color: #222222; font-size: 22px; align-items: center; align-content: center; margin: 20px; } .text-allow{ color: #3E7BDE; font-size: 18px; margin-right: 10px; } .text-reject{ color: #212121; font-size: 18px; margin-left: 10px; }
9.游戲:圖形的運(yùn)動(dòng)、碰撞與合成
- [圖形的運(yùn)動(dòng)]
- [圖形的碰撞與合成
如下圖所示,按照從左到右的順序,相同的圖形碰撞合成下一個(gè)圖形,最終合成OpenHarmony圖形。
效果圖預(yù)覽:
圖形的運(yùn)動(dòng)
在index.js的data下定義圖片資源數(shù)組imgArray和顯示在屏幕中圖片的數(shù)組modes。代碼如下:
export default { data: { // 圖片數(shù)組 imgArray: ['common/images/product0.png', 'common/images/product1.png', 'common/images/product2.png', 'common/images/product3.png', 'common/images/product4.png', 'common/images/product5.png', 'common/images/product6.png'], //在屏幕中出現(xiàn)的數(shù)據(jù) modes: [], } }
modes添加數(shù)據(jù)模型格式為:要顯示的圖形路徑src、圖形的寬度width、圖形的高度height、圖形的等級(jí)level(用于區(qū)分不同的圖形),以及圖形的左坐標(biāo)left、頂部坐標(biāo)top和其在x、y方向上的速度。新增一個(gè)數(shù)據(jù)模型到數(shù)組中,代碼如下:
addNewData() { var index = Math.floor(Math.random() * 4); var src = this.imgArray[index]; var width = 50 + index * 10; var height = 50 + index * 10; this.modes.push({ level: index, width: width, height: height, src: src, top: 0, left: 120, speedX: 0, speedY: 10, }) }
在index.hml文件中,遍歷modes數(shù)組,用Image組件顯示圖形,只需要?jiǎng)討B(tài)更改class、style、src等屬性即可。代碼如下:
< div class="div-image" if="{{isStart}}" > < image for="{{ (index, item) in modes }}" class="product{{ item.level }}" style="top : {{ item.top }}; left : {{ item.left }}" src="{{ item.src }} "/ > < /div >
對(duì)應(yīng)的index.css新增內(nèi)容如下:
.div-image{ flex-direction: column; justify-content: flex-start; align-items: flex-start; width: 100%; height: 100%; } .product0{ width: 50px; height: 50px; position: absolute; } .product1{ width: 60px; height: 60px; position: absolute; } .product2{ width: 70px; height: 70px; position: absolute; } .product3{ width: 80px; height: 80px; position: absolute; } .product4{ width: 90px; height: 90px; position: absolute; } .product5{ width: 100px; height: 100px; position: absolute; } .product6{ width: 110px; height: 110px; position: absolute; }
使用setInterval()開(kāi)啟定時(shí)器,反復(fù)執(zhí)行excuteTask()方法,該方法用來(lái)計(jì)算圖形的運(yùn)動(dòng)。圖形的移動(dòng)主要是將圖形的頂部top和左側(cè)left的坐標(biāo)值,每次遞增各自的x、y方向的速度值。部分代碼如下:
export default { startGame(){ addNewData(); intervalId = setInterval(this.excuteTask, 50); }, excuteTask(){ this.modes.forEach(item = > { item.top += item.speedY; item.left += item.speedX; }); } }
圖形的碰撞與合成
這部分僅介紹核心的思路,具體的實(shí)現(xiàn)過(guò)程讀者可自行完成,其達(dá)到的效果圖如下:
- 兩個(gè)圖形若滿足它們的的圓心距離小于它們半徑的總和,則認(rèn)為它們發(fā)生了碰撞。部分代碼如下:
isCollision(run, other) { var runCenterX = run.left + run.width / 2; var runCenterY = run.top + run.width / 2; var otherCenterX = other.left + other.width / 2; var otherCenterY = other.top + other.width / 2; var distance = Math.sqrt(Math.abs(runCenterX - otherCenterX) * Math.abs(runCenterX - otherCenterX) + Math.abs(runCenterY - otherCenterY) * Math.abs(runCenterY - otherCenterY)); if (distance < (run.width + other.width) / 2) { return true; } return false; }
- 通過(guò)判斷兩個(gè)圖形的等級(jí)level值是否相等,若相等就可以進(jìn)行合成。部分代碼如下:
isCompose( productA, productB) { return productA.level == productB.level; }
審核編輯 黃宇
-
分布式
+關(guān)注
關(guān)注
1文章
903瀏覽量
74546 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2363瀏覽量
42888 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3725瀏覽量
16375 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
189瀏覽量
4437
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論