OpenHarmony开发实例:【分布式游戏鉴权应用】

1.介绍

本文将介绍分布式游戏鉴权应用。操作过程为:

  1. 设备A点击"开始游戏"按钮,开始搜索周边设备。

  2. 设备A显示周边设备,点击设备B并发起连接请求,远程拉起设备B的FA。

  3. 设备B收到请求后,选择是否允许"开启游戏"。

    • 选择允许,远程拉起设备A,并传递允许的信息,设备A解析了信息后自动开始游戏。
    • 选择不允许,远程拉起设备A,并传递不允许的信息,设备A回到最初的状态,并提示申请鉴权未通过。

效果图展示:

2.相关概念

gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md鸿蒙开发文档参考,点击或者复制转到。

3.搭建OpenHarmony环境

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。

    以3.1版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]

鸿蒙HarmonyOS与OpenHarmony籽料

复制代码

mau123789是v直接拿取

  1. 搭建开发环境。

    1. 开始前请参考[工具准备] ,完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导] 创建工程(模板选择"Empty Ability") ,选择JS或者eTS语言开发。
    3. 工程创建完成后,选择使用[真机进行调测] 。

4.分布式组网

本章节以系统自带的音乐播放器为例(具体以实际的应用为准),介绍如何完成两台设备的分布式组网。

  1. 硬件准备:准备两台烧录相同的版本系统的RK3568开发板A、B。

  2. 开发板A、B连接同一个WiFi网络。

    打开设置-->WLAN-->点击右侧WiFi开关-->点击目标WiFi并输入密码。

  1. 将设备A,B设置为互相信任的设备。

    • 找到系统应用"音乐"。
    • 设备A打开音乐,点击左下角流转按钮,弹出列表框,在列表中会展示远端设备的id。

    • 选择远端设备B的id,另一台开发板(设备B)会弹出验证的选项框。

  • 设备B点击允许,设备B将会弹出随机PIN码,将设备B的PIN码输入到设备A的PIN码填入框中。

配网完毕。

5.代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在参考中提供下载方式,整个工程的代码结构如下:

  • common:存放公共资源

  • pages:存放页面

    index.js:主页面

  • config.json:配置文件

6.初始化页面

在本章节中,您将学会如何进行页面初始化。

  1. 在data下定义需要使用的字段。

    cpp 复制代码
    data: {
      // 目标设备Id,用于记录申请过来的设备Id
      targetDeviceId: '',
      // 是否同意玩游戏
      isAgree: false,
      // 是否显示开始游戏图标
      showStart: false
    },
  2. 根据Ability启动参数来判断页面被拉起的状态。

    在拉起页面时候,设置requestType为分布式拉起页面的业务请求类型(申请鉴权或者回复鉴权结果),如果没有requestType参数,则为手动拉起本机Ability。然后通过分析requestType参数的值来进行不同的业务逻辑操作。

    cpp 复制代码
    onInit() {
      // 获取Ability启动参数
      featureAbility.getWant().then((want) => {
        if (want.parameters !== undefined && want.parameters !== null && want.parameters !== '') {
          // 如果是请求授权被拉起Ability(requestType === 0),则记录申请权限的设备id
          if (want.parameters.requestType === 0) {
            this.isGame = false;
            this.targetDeviceId = want.parameters.localDeviceId;
          } else if (want.parameters.requestType === 1) {
            // 如果是授权后被拉起Ability(requestType === 1),则根据授权情况判断是否进行游戏
            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: '申请授权未被允许',
                  duration: 5000
                });
              }
            }
            this.targetDeviceId = want.parameters.localDeviceId;
          } else {
            // 如果没有请求类型字段(requestType),则表明是手动启动的Ability,此时显示启动游戏图标
            this.showStart = true;
          }
        }
      });

7.显示鉴权设备

在本章节中,您将学会如何显示需要鉴权的设备列表。效果图如下:

  1. 在index.js文件中,在data下定义deviceList数组,用来表示周边的设备。代码如下:

    cpp 复制代码
    export default {
      data: {
        //可授权的设备
        deviceList: []
      }
    }
  2. 在index.hml文件中:

    • 定义一个"开始游戏"的button组件,设置startFA的点击事件;
    • 显示周边设备的对话框dialog,使用list 、list-item实现设备列表的展示;
    • 通过for属性遍历deviceList数组,$item是每一项的实例;
    • 给每一项设置selectDevice点击事件,参数为设备的networkId。

    代码如下:

    cpp 复制代码
    <div class="container">
        <button class="text-button" onclick="startFA">开始游戏</button>
        <dialog id="continueAbilityDialog" class="dialog-main" oncancel="cancelDialog">
            <div class="dialog-div">
                <text class="dialog_title_text">选择设备</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>
  3. 在index.css文件中,定义布局和样式。代码如下:

    cpp 复制代码
    .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;
    }
  4. 在index.js文件中:

    • 定义createDeviceManager方法,获得设备管理器实例并进行获得同一网段下的所有在线设备;

      cpp 复制代码
      // 创建实例
        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方法,获取本设备信息;

      cpp 复制代码
      getLocalDeviceInfoSync() {
          if (dmClass != null) {
            deviceInfo = dmClass.getLocalDeviceInfoSync();
          } else {
            prompt.showToast({
              message: '请先初始化'
            });
          }
        },
    • 将获取到的同一网段下的所有在线设备信息放入deviceList数组中;

    • 通过this.$element('showDialog')找到hml文件中dialog组件,调用show()方法显示对话框。

8.鉴权申请与回应

在本章节中,您将学会如何从设备A拉起设备B的FA,并将设备A的标识信息发送给设备B。效果图如下:

申请鉴权(同意游戏)

申请鉴权(拒绝游戏)

  1. 设备A点击开始游戏,显示可以进行鉴权申请的设备列表,并选中设备申请游戏鉴权

    1. 定义startFa方法,用以显示设备列表对话框;

      cpp 复制代码
      startFA() {
          this.$element('continueAbilityDialog').show();
        },
    2. 定义onRadioChange方法,用以监听选择的设备变化;

      cpp 复制代码
      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);
                }
              }
            }
          }
        },
    3. 定义startAbilityContinuation方法,用以申请鉴权;

      cpp 复制代码
      startAbilityContinuation(deviceId, deviceName) {
          this.$element('continueAbilityDialog').close();
          const wantValue = {
            bundleName: 'com.huawei.cookbook',
            abilityName: 'com.huawei.gameauthopenh.MainAbility',
            deviceId: deviceId,
            // localDeviceId:申请设备的id,requestType,请求类型:0,申请鉴权
            parameters: {'localDeviceId': deviceInfo.deviceId, 'requestType': 0}
          };
      
          featureAbility.startAbility({
            want: wantValue
          }).then((data) => {
            // 销毁自身Ability
            featureAbility.terminateSelf();
          });
        },
  2. 设备B被设备A分布式拉起,对游戏进行授权

    1. index.hml页面添加div用以显示授权选项;

      cpp 复制代码
      <div class="div-permit" if="{{!isGame}}" >
          <text class="text-title">来自远程合成设备小游戏权限请求,是否允许?</text>
          <div class="div-button"  >
              <text class="text-allow" onclick="responds(true)">允许</text>
              <text class="text-reject" onclick="responds(false)">不允许</text>
          </div>
      </div>
    2. 定义responds方法用以反馈鉴权结果,并分布式拉起设备A的Ability;

      cpp 复制代码
      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();
          });
        },
  3. 设备A被分布式拉起并解析鉴权结果,并根据结果执行不同的操作;

    在onInit方法中调用featureAbility.getWant()来获取启动信息并根据启动信息判断游戏申请是否被拒绝;

    复制代码
    cpp 复制代码
    onInit() {
      ...
      // 获取Ability启动参数
      featureAbility.getWant().then((want) => {
        if (want.parameters !== undefined && want.parameters !== null && want.parameters !== '') {
          // 如果是请求授权被拉起Ability(requestType === 0),则记录申请权限的设备id
          if (want.parameters.requestType === 0) {
            this.isGame = false;
            this.targetDeviceId = want.parameters.localDeviceId;
          } else if (want.parameters.requestType === 1) {
            // 如果是授权后被拉起Ability(requestType === 1),则根据授权情况判断是否进行游戏
            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: '申请授权未被允许',
                  duration: 5000
                });
              }
            }
            this.targetDeviceId = want.parameters.localDeviceId;
          } else {
            // 如果没有请求类型字段(requestType),则表明是手动启动的Ability,此时显示启动游戏图标
            this.showStart = true;
          }
        }
      });
      ...
    },
  4. index.css文件新增内容如下:

    cpp 复制代码
    .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.游戏:图形的运动、碰撞与合成

  • [图形的运动]
  • [图形的碰撞与合成

如下图所示,按照从左到右的顺序,相同的图形碰撞合成下一个图形,最终合成OpenHarmony图形。

效果图预览:

图形的运动

  1. 在index.js的data下定义图片资源数组imgArray和显示在屏幕中图片的数组modes。代码如下:

    cpp 复制代码
    export default {
      data: {
        // 图片数组
        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'],
        //在屏幕中出现的数据
        modes: [],
      }
    }

    modes添加数据模型格式为:要显示的图形路径src、图形的宽度width、图形的高度height、图形的等级level(用于区分不同的图形),以及图形的左坐标left、顶部坐标top和其在x、y方向上的速度。新增一个数据模型到数组中,代码如下:

    cpp 复制代码
    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,
      })
    }
  2. 在index.hml文件中,遍历modes数组,用Image组件显示图形,只需要动态更改class、style、src等属性即可。代码如下:

    cpp 复制代码
    <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>

    对应的index.css新增内容如下:

    cpp 复制代码
    .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;
    }
  3. 使用setInterval()开启定时器,反复执行excuteTask()方法,该方法用来计算图形的运动。图形的移动主要是将图形的顶部top和左侧left的坐标值,每次递增各自的x、y方向的速度值。部分代码如下:

    cpp 复制代码
    export default {
      startGame(){
        addNewData();
        intervalId = setInterval(this.excuteTask, 50);
      },
      excuteTask(){     
        this.modes.forEach(item => {
          item.top += item.speedY;
          item.left += item.speedX;
        });
      }
    }

图形的碰撞与合成

这部分仅介绍核心的思路,具体的实现过程读者可自行完成,其达到的效果图如下:

  1. 两个图形若满足它们的的圆心距离小于它们半径的总和,则认为它们发生了碰撞。部分代码如下:

    cpp 复制代码
    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;
    }
  2. 通过判断两个图形的等级level值是否相等,若相等就可以进行合成。部分代码如下:

    cpp 复制代码
    isCompose( productA,  productB) {
      return productA.level == productB.level;
    }

最后呢,很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造 的**《鸿蒙NEXT星河版OpenHarmony开发文档》**里面内容包含了(**ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony****多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)**技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

高清完整版请点击→《鸿蒙NEXT星河版开发学习文档》****

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细资料鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

《鸿蒙 (OpenHarmony)开发学习视频》

《鸿蒙生态应用开发V2.0白皮书》

《鸿蒙 (OpenHarmony)开发基础到实战手册》

获取这份鸿蒙星河版学习资料,请点击→ 《鸿蒙NEXT星河版开发学习文档》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

  1. ArkTS语言

  2. 安装DevEco Studio

  3. 运用你的第一个ArkTS应用

  4. ArkUI声明式UI开发

  5. .......

《鸿蒙开发进阶》

  1. Stage模型入门

  2. 网络管理

  3. 数据管理

  4. 电话服务

  5. 分布式应用开发

  6. 通知与窗口管理

  7. 多媒体技术

  8. 安全技能

  9. 任务管理

  10. WebGL

  11. 国际化开发

  12. 应用测试

  13. DFX面向未来设计

  14. 鸿蒙系统移植和裁剪定制

  15. ......

《鸿蒙开发实战》

  1. ArkTS实践

  2. UIAbility应用

  3. 网络案例

  4. ......

获取这份鸿蒙星河版学习资料,请点击→《鸿蒙NEXT星河版开发学习文档》

总结

鸿蒙---作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发 ,未来将会支持 50 万款的应用那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

相关推荐
沈剑心7 小时前
如何在鸿蒙系统上实现「沉浸式」页面?
前端·harmonyos
Georgewu8 小时前
【HarmonyOS】鸿蒙应用加载读取csv文件
前端·harmonyos
Georgewu8 小时前
【HarmonyOS】 鸿蒙图片或视频保存相册
前端·harmonyos
川石教育14 小时前
鸿蒙开发-ArkTS 中使用 filter 组件
harmonyos·鸿蒙·鸿蒙应用开发·鸿蒙开发·鸿蒙开发培训·arkts语言
李洋-蛟龙腾飞公司14 小时前
HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据权限与基础数据
分布式·华为·harmonyos
Damon小智14 小时前
HarmonyOS NEXT 技术实践-实现音乐服务卡片
华为·harmonyos·鸿蒙·harmonyos next·服务卡片
play_big_knife14 小时前
鸿蒙项目云捐助第十七讲云捐助我的页面上半部分的实现
华为·harmonyos·鸿蒙·云开发·鸿蒙开发·鸿蒙next·华为云开发
陈哥聊测试15 小时前
软件格局在变,谁能扛起国产替代的大旗?
安全·程序员·产品
枫叶丹420 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos
SoraLuna1 天前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos