[文章]HarmonyOS教程—分布式游戏手柄

阅读量0
0
1
1. 项目介绍      
HarmonyOS的分布式能力可以方便地扩展虚拟终端,造就新的交互,体验新的场景。本篇codelab通过分布式能力,使手机在智慧屏附近即可迅速被虚拟成一个手柄终端,将智慧屏的交互扩展到手机,充分结合手机的多模输入和智慧屏的大屏优点,组成新的多人娱乐场景。

本应用分两部分,安装在手机上的手柄端程序和安装在大屏上的大屏端程序。

说明
为便于演示,本篇codelab所指大屏均使用支持HarmonyOS的手机代替。
启动大屏端程序进入游戏主页面,此时会弹出手柄设备列表选择框,选择(最多选择两个手柄设备)手柄设备点击【确定】发起连接请求;手柄端收到连接请求亦会弹出设备列表选择框,选择对应大屏设备点击【确定】,此时大屏端与手柄端连接完成。手柄端可向大屏端发送指令。
主界面:
  • 手柄端点击【START】按钮,大屏端进入游戏中界面,此时会根据已连接的手柄创建对应数量的玩家飞机;敌机从屏幕顶端随机水平位置出现并向屏幕下方垂直移动;降落伞从屏幕顶端随机水平位置出现并向屏幕下方移动。
  • 手柄端点击【PAUSE】按钮,游戏暂停,点击【START】按钮继续游戏。
    游戏中界面:
  • 手柄端可通过滑动摇杆按钮控制大屏端玩家飞机的飞行方向。
  • 点击绿色按钮可以发射子弹,当子弹与敌机发生碰撞后,对应玩家飞机得分数值加100,同时摧毁子弹与敌机。
  • 当玩家飞机与降落伞发生碰撞,对应屏幕左下角或右下角***数量+1,同时销毁降落伞。
  • 点击黄色按钮可以释放***(若对应玩家机***数量不为0)清空屏幕敌机,根据屏幕中被清空的敌机数量N,计算对应玩家飞机的得分N * 100。
  • 当玩家飞机与敌机发生碰撞,若玩家飞机数量为2,则玩家飞机和敌机将同时被摧毁,此玩家飞机对应的手柄操作无效;若屏幕内玩家飞机均被摧毁,则游戏结束并跳转到游戏结束界面。
    游戏结束界面:
  • 展示玩家最终得分。
  • 任一手柄端点击【PAUSE】按钮,大屏端跳转到游戏主界面;任一手柄端点击【START】按钮,大屏端跳转到游戏中界面。效果图如下:
    a) 大屏端选择手柄设备,发起连接;手柄被拉起,弹出设备选择框,选择大屏设备进行连接。

b) 游戏中,飞机移动、发射子弹、捡降落伞(获取触发大招的***)。

c) 游戏结束,大屏端显示所有玩家得分,手柄端显示自己得分。

   
2. 搭建HarmonyOS环境      
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
  • 安装DevEco Studio,详情请参考下载和安装软件。
  • 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
    • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
    • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
  • 开发者可以参考以下链接,完成设备调试的相关配置:
    • 使用真机进行调试
    • 使用模拟器进行调试
   

3. 手柄端代码结构解读      
在本篇codelab中我们只对核心代码进行讲解,您可以在第9章下载完整代码。手柄端工程的代码结构如下图:

  • devices:设备列表适配器和设备选择对话框、权限动态申请。
  • handle:手柄实体类,包含游戏暂停、重新开始以及技能是否被点击。
  • proxy:定义了连接远程service(PA)实现类以及代理。
  • service:远程通信,读取大屏端发过来的数据,即玩家得分。
  • slice:MainAbilitySlice主界面,弹窗选择大屏设备后跳转到HandleAbilitySlice界面,HandleAbilitySlice实现了分布式设备连接和手柄界面。
  • utils:CalculAngle处理摇杆滑动事件,包含计算偏移角度;Constans常量类。
  • resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件,resourcesbasemedia下存放图片资源。
  • config.json:应用的配置文件。建议使用SDK4,即config.json中的"target"字段和模块下的build.gradle中的"compileSdkVersion"字段修改为4。
   

4. 大屏端代码结构解读      

本篇codelab中只对核心代码进行讲解,您可以在第9章下载完整代码。大屏端工程的代码结构如下图:

  • devices:展示手柄设备列表对话框,用户选择手柄设备(最多选择两个手柄设备)进行连接。
  • game:游戏中的各个角色类的定义以及游戏界面定义。
  • service:远程通信,用于获取手柄端发送的数据。
  • slice:游戏主界面、游戏中界面、游戏结束界面逻辑判断。
  • util:工具类,Constants.java定义常量,GameUtils.java获取当前设备屏幕尺寸、将图片id转换成PixelMapHolder对象。
  • GameOverAbility.java:游戏结束的Page Ability。
  • MainAbility.java:游戏主界面的Page Ability,由DevEco Studio生成,不需添加逻辑。
  • MyApplication.java:入口类,由DevEco Studio生成,不需添加逻辑。
  • PlaneGameAbility.java:游戏中的Page Ability。
  • resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件,resourcesbasemedia下存放图片资源。
  • config.json:Ability声明以及权限配置。
   

5. 相关权限      
手柄端和大屏端程序开发均需申请以下多设备协同相关的四个权限,应用权限的申请可以参考权限章节。
ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。
ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。
ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。
ohos.permission.GET_BUNDLE_INFO:用于允许查询其他应用信息的权限。

说明
其中多设备协同数据同步权限"ohos.permission.DISTRIBUTED_DATASYNC",需要按照动态申请流程向用户申请授权。
   

6. 分布式设备启动与连接      
大屏端发起连接,进入应用,大屏端弹出手柄设备列表选择框。代码示例如下:

  1. // 设备选择弹出框
  2. private SelectDeviceDialog showDialog() {
  3.     return new SelectDeviceDialog(this, new SelectDeviceDialog.SelectResultListener() {
  4.         public void callBack(List<DeviceInfo> deviceInfos) {
  5.             for (DeviceInfo deviceInfo : deviceInfos) {
  6.                 Handle handleInfo = new Handle(MainAbilitySlice.this); // 连接信息
  7.                 Intent intent = new Intent();
  8.                 Operation operation = new Intent.OperationBuilder()
  9.                         .withDeviceId(deviceInfo.getDeviceId())
  10.                         .withBundleName(getBundleName())
  11.                         .withAbilityName(MainAbility.class.getName())
  12.                         .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
  13.                         .build();
  14.                 intent.setOperation(operation);
  15.                 startAbility(intent);
  16.                 // 保存手柄连接信息
  17.                 boolean isConn = connectRemotePa(deviceInfo.getDeviceId(), 1, handleInfo);
  18.                 handleInfo.setDeviceId(deviceInfo.getDeviceId());
  19.                 handleInfo.setConn(isConn);
  20.                 handles.add(handleInfo);
  21.             }
  22.         }
  23.     });
  24. }
复制代码
选择手柄设备(可多选),点击【确定】按钮,连接选择的手柄,代码示例如下:

  1. private boolean connectRemotePa(String deviceId, int requestType, Handle handleInfo) {
  2.     Intent connectPaIntent = new Intent();
  3.     Operation operation = new Intent.OperationBuilder()
  4.             .withDeviceId(deviceId)
  5.             .withBundleName(getBundleName())
  6.             .withAbilityName(GameServiceAbility.class.getName())
  7.             .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
  8.             .build();
  9.     connectPaIntent.setOperation(operation);

  10.     IAbilityConnection conn = new IAbilityConnection() {
  11.         [url=home.php?mod=space&uid=2735960]@Override[/url]
  12.         public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
  13.           GameRemoteProxy proxy = new GameRemoteProxy(remote);
  14.           handleInfo.setProxy(proxy);
  15.         }

  16.         @Override
  17.         public void onAbilityDisconnectDone(ElementName elementName, int index) {
  18.           if (handleInfo.getProxy() != null) {
  19.             handleInfo.setProxy(null);
  20.           }

  21.         }
  22.     };

  23.     boolean ret = connectAbility(connectPaIntent, conn);
  24.     return ret;
  25. }
复制代码
大屏端发起连接后,手柄端被远程拉起,此时手柄端弹出设备列表选择框,选择大屏设备,点击【确定】按钮,连接选择的大屏。代码示例如下:

  1. private boolean connectRemotePa(String deviceId, int requestType) {
  2.     Intent connectPaIntent = new Intent();
  3.     Operation operation = new Intent.OperationBuilder().withDeviceId(deviceId)
  4.             .withBundleName(getBundleName())
  5.             .withAbilityName(GameServiceAbility.class.getName())
  6.             .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
  7.             .build();
  8.     connectPaIntent.setOperation(operation);

  9.     IAbilityConnection conn = new IAbilityConnection() {
  10.         @Override
  11.         public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
  12.             LogUtil.info(TAG, "onAbilityConnectDone......");
  13.             proxy = new GameRemoteProxy(remote, localDeviceId, calculAngle, handle);
  14.             LogUtil.error(TAG, "connectRemoteAbility done");
  15.             send(requestType);
  16.         }

  17.         @Override
  18.         public void onAbilityDisconnectDone(ElementName elementName, int index) {
  19.             if (proxy != null) {
  20.                 proxy = null;
  21.             }
  22.             LogUtil.info(TAG, "onAbilityDisconnectDone......");
  23.         }
  24.     };
  25.     boolean ret = connectAbility(connectPaIntent, conn);
  26.     return ret;
  27. }

  28. private void send(int requestType) {
  29.     if (proxy != null) {
  30.         try {
  31.             proxy.senDataToRemote(requestType);
  32.         } catch (RemoteException e) {
  33.             LogUtil.error(TAG, "onAbilityConnectDone RemoteException");
  34.         }
  35.     }
  36. }
复制代码

7. 手柄端功能      
向大屏端发送指令
手柄端只是向大屏端发送指令,由大屏端实现业务逻辑。有如下指令:
  • 点击绿色按钮,玩家飞机发送一个发射子弹的指令。
  • 点击黄色按钮,释放大招,使屏幕上所有敌机爆炸销毁(需玩家飞机获得降落伞中***包,指令才可生效)。
  • 点击【START】按钮,发送开始游戏的指令。
  • 点击【PAUSE】按钮,发送暂停游戏的指令。
  • 滑动摇杆,发送用于控制飞机飞行方向的指令。
    以点击绿色按钮为例,代码示例如下:

  1. private Component.ClickedListener listenerA = new Component.ClickedListener() {
  2.     @Override
  3.     public void onClick(Component component) {
  4.         vibrator(Constants.VIBRATION_30); // 震动
  5.         handle.setIsAbtnClick(1);
  6.         isFlagA = true;
  7.         if (isConn && proxy != null) {
  8.             try {
  9.                 proxy.senDataToRemote(1);
  10.             } catch (RemoteException e) {         
  11.                LogUtil.error(TAG, "Send Data to Remote Failed...");
  12.             }
  13.         }
  14.         handle.setIsAbtnClick(0);
  15.     }
  16. };
复制代码
计算偏移角度
摇杆部分,大圆是固定不动的,小圆默认停在大圆中间。手指移动时,小圆跟随手指的位置移动但不能超出大圆范围。以大圆圆心为坐标系原点,手指的坐标点与X轴形成的角度为玩家飞机的偏移角度,以此来控制飞机行进方向。关键代码示例如下:
  • 计算手指所在象限
    1. private boolean getFlagX() {
    2.     return 0 < moveX - startPosX ? true : false;
    3. }
    4. private boolean getFlagY() {
    5.     return 0 < moveY - startPosY ? true : false;
    6. }
    7. // 返回手指所在象限(坐标原点为大圆圆心)
    8. private int quadrant() {
    9.     if (getFlagX() && !getFlagY()) {
    10.         return Constants.QUADRANT_1;
    11.     } else if (!getFlagX() && !getFlagY()) {
    12.         return Constants.QUADRANT_2;
    13.     } else if (!getFlagX() && getFlagY()) {
    14.         return Constants.QUADRANT_3;
    15.     } else if (getFlagX() && getFlagY()) {
    16.         return Constants.QUADRANT_4;
    17.     } else {
    18.         return 0;
    19.     }
    20. }
    复制代码

  • 计算偏移角度
    1. private int calculateAngle() {
    2.     int degree = (int) Math.toDegrees(Math.atan(getDisAbsY() / getDisAbsX()));
    3.     int quadrant = quadrant();
    4.     switch (quadrant) {
    5.         case Constants.QUADRANT_1:
    6.             // 向右上移动
    7.             angle = degree;
    8.             break;
    9.         case Constants.QUADRANT_2:
    10.             // 向左上移动
    11.             angle = Constants.DEGREE_180 - degree;
    12.             break;
    13.         case Constants.QUADRANT_3:
    14.             // 向左下移动
    15.             angle = -Constants.DEGREE_180 + degree;
    16.             break;
    17.         case Constants.QUADRANT_4:
    18.             // 向右下移动
    19.             angle = -degree;
    20.             break;
    21.         default:
    22.             angle = 0;
    23.             break;
    24.     }
    25.     return angle;
    26. }
    复制代码

设置小圆坐标
当手指在大圆范围内时,小圆圆心坐标跟随手指坐标;当手指滑出大圆范围后,小圆圆心坐标在大圆圆周上,不能超出大圆范围。代码示例如下:

  1. private float[] getSmallCurrentPos(float currX, float currY) {
  2.     float[] smallCurrentPos = new float[Constants.QUADRANT_2];
  3.     if (getDisZ() < bigR) {
  4.         smallCurrentPos[0] = currX;
  5.         smallCurrentPos[1] = currY;
  6.         return smallCurrentPos;
  7.     } else {
  8.         // 手指滑出大圆外后,由于小圆不能超出大圆,此时小圆圆心坐标在大圆圆周上,以下是计算小圆的控件坐标
  9.         double disX = (getDisAbsX() * bigR) / getDisZ();
  10.         double disY = (getDisAbsY() * bigR) / getDisZ();
  11.         int quadrant = quadrant(); // 手指所在象限
  12.         switch (quadrant) {
  13.             case Constants.QUADRANT_1:
  14.                 smallCurrentPos[0] = (float) (disX + startPosX - smallR);
  15.                 smallCurrentPos[1] = (float) (startPosY - disY - smallR);
  16.                 break;
  17.             case Constants.QUADRANT_2:
  18.                 smallCurrentPos[0] = (float) (startPosX - disX - smallR);
  19.                 smallCurrentPos[1] = (float) (startPosY - disY - smallR);
  20.                 break;
  21.             case Constants.QUADRANT_3:
  22.                 smallCurrentPos[0] = (float) (startPosX - disX - smallR);
  23.                 smallCurrentPos[1] = (float) (disY + startPosY - smallR);
  24.                 break;
  25.             case Constants.QUADRANT_4:
  26.                 smallCurrentPos[0] = (float) (disX + startPosX - smallR);
  27.                 smallCurrentPos[1] = (float) (disY + startPosY - smallR);
  28.                 break;
  29.             default:
  30.                 break;
  31.         }
  32.     }
  33.     return smallCurrentPos;
  34. }
复制代码
显示得分
手柄端接收大屏端发送的数据,更新UI组件,代码示例如下:

  1. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
  2.     int score = data.readInt();
  3.     txtScore.setText(String.valueOf(score));
  4.     return true;
  5. }
复制代码

8. 大屏端功能
通过获取手柄端发送的数据操作大屏端:开始游戏、暂停游戏、移动飞机以及发射子弹等。大屏端可随机生成敌机、降落伞以及检测碰撞事件。
精灵类定义
  • 子类公共属性定义,代码示例如下:
    1. private PixelMapHolder pixelMapHolder; // 图片
    2. private int planeX; // x坐标
    3. private int planeY; // y坐标
    4. private int width; // 宽
    5. private int height; // 高
    6. private int speed; // 速度
    7. private boolean isDestroyed; // 是否被销毁
    复制代码

  • 当前对象与其他对象集合的碰撞检测,如未发生碰撞返回null,否则返回碰撞对象,代码示例如下:
    1. public Spirit collideWithOther(List<? extends Spirit> spirits) {
    2.     Iterator<? extends Spirit> iterator = spirits.iterator();
    3.     while (iterator.hasNext()) {
    4.         Spirit spirit = iterator.next();
    5.         RectFloat r1 = new RectFloat(spirit.getPlaneX(), spirit.getPlaneY(),
    6.                 spirit.getPlaneX() + spirit.getWidth(),
    7.                 spirit.getPlaneY() + spirit.getHeight());
    8.         RectFloat r2 = new RectFloat(planeX, planeY, planeX + width,
    9.                 planeY + height);
    10.         if (r1.getIntersectRect(r2)) { // 碰撞
    11.             return spirit;
    12.         }
    13.     }
    14.     return optionalEmpty.orElse(null);
    15. }
    复制代码

  • 画图方法,包含画图前、画图中、画图后三个方法,代码示例如下:
    1. public void draw(Canvas canvas, Paint paint) {
    2.     beforeOndraw();
    3.     onDraw(canvas, paint);
    4.     afterDraw(canvas, paint);
    5. }
    复制代码

  • 画图前执行的方法,计算精灵类垂直方向坐标,代码示例如下:
    1. public void beforeOndraw() {
    2.     move();
    3. }
    4. public void move() {
    5.     planeY += speed;
    6. }
    复制代码

  • 画图中方法,将精灵对象画到屏幕,代码示例如下:
    1. public void onDraw(Canvas canvas, Paint paint) {
    2.     canvas.drawPixelMapHolder(pixelMapHolder, planeX, planeY, paint);
    3. }
    复制代码

  • 画图之后执行的方法,销毁移出屏幕的精灵对象,代码示例如下:
    1. public void afterDraw(Canvas canvas, Paint paint) {
    2.     if (GameUtils.getScreenHeight() < planeY) {
    3.         destroy();
    4.     }
    5. }
    复制代码

  • 执行销毁方法,将图片pixelMapHolder对象设置为null,将销毁状态设置为true,代码示例如下:
    1. public void destroy() {
    2.     pixelMapHolder = null;
    3.     isDestroyed = true;
    4. }
    复制代码

精灵类子类定义
  • Bomb.java降落伞类,构造方法中设置水平方向随机位置、垂直方向位置和速度,代码示例如下:
    1. public Bomb(PixelMapHolder pixelMapHolder) {
    2.     super(pixelMapHolder);
    3.     this.setPlaneX(getRandom().nextInt(GameUtils.getScreenWidth() - BOMB_Y_POSITION));
    4.     this.setPlaneY(-BOMB_Y_POSITION);
    5.     this.setSpeed((getRandom().nextInt(SPEED_RATE) + SPEED_RATE) * SPEED_RATE);
    6. }
    复制代码

  • Bullet.java子弹类,构造函数中根据玩家飞机位置设置子弹位置以及设置速度;重写afterDraw()方法,用于销毁超出屏幕范围的子弹,代码示例如下:
    1. public Bullet(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
    2.     super(pixelMapHolder);
    3.     this.setPlaneX(planeX + BULLET_X_OFFSET);
    4.     this.setPlaneY(planeY - BULLET_Y_OFFSET);
    5.     this.setSpeed(BULLET_SPEED);
    6. }
    7. public void afterDraw(Canvas canvas, Paint paint) {
    8.     if (getPlaneY() < 0) {
    9.         destroy();
    10.     }
    11. }
    复制代码

  • EnemyPlane.java敌机类,构造函数中设置敌机尺寸大小、水平方向随机位置、垂直方向位置和随机速度,代码示例如下:
    1. public EnemyPlane(PixelMapHolder pixelMapHolder, int planeIndex) {
    2.     super(pixelMapHolder);
    3.     int size = (planeIndex == SPEED_RATE) ? Constants.SMALL_PLANE_SIZE : Constants.BIG_PLANE_SIZE;
    4.     this.setPlaneX(getRandom().nextInt(GameUtils.getScreenWidth() - size));
    5.     this.setPlaneY(-size);
    6.     this.setSpeed((getRandom().nextInt(SPEED_RATE) + SPEED_RATE) * SPEED_RATE);
    7. }
    复制代码

  • Explosion.java爆炸效果类,销毁精灵子类时展示爆炸效果,构造方法中设置爆炸位置(为被销毁对象的位置)、速度为0;重写afterDraw()方法,每8帧销毁爆炸效果,代码示例如下:
    1. public Explosion(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
    2.     super(pixelMapHolder);
    3.     this.setPlaneX(planeX);
    4.     this.setPlaneY(planeY);
    5.     this.setSpeed(0);
    6. }

    7. @Override
    8. public void afterDraw(Canvas canvas, Paint paint) {
    9.     if (0 == GameView.getFrame() % CLEAN_DESTROY_FRAME) {
    10.         destroy();
    11.     }
    12. }
    复制代码

  • MyPlane.java玩家飞机类,构造方法中设置水平、垂直方向位置、速度、分数为0、***数为0;重写draw()方法,在屏幕上画玩家飞机,代码示例如下:
    1. public MyPlane(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
    2.     super(pixelMapHolder);
    3.     this.setPlaneX(planeX);
    4.     this.setPlaneY(planeY);
    5.     this.setSpeed(SPEED);
    6.     this.setScore(0);
    7.     this.setBombNum(0);
    8. }

    9. @Override
    10. public void draw(Canvas canvas, Paint paint) {
    11.     canvas.drawPixelMapHolder(getPixelMapHolder(), getPlaneX(), getPlaneY(), paint);
    12. }
    复制代码

获取手柄端数据
  • GameserviceAbility.java类中实时获取手柄端数据,调用Handle类处理数据,代码示例如下:
    1. @Override
    2. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
    3.     String deviceId = data.readString();
    4.     taskDispatcher.syncDispatch(new Runnable() {
    5.         @Override
    6.         public void run() {
    7.             syncDispatchRequest(deviceId, data);
    8.         }
    9.     });
    10.     return true;
    11. }

    12. private void syncDispatchRequest(String deviceId, MessageParcel data) {
    13.     int angle = data.readInt();
    14.     int buttonA = data.readInt();
    15.     int buttonB = data.readInt();
    16.     int pause = data.readInt();
    17.     int start = data.readInt();
    18.     for (Handle handle: MainAbilitySlice.getHandles()) {
    19.         if (deviceId.equals(handle.getDeviceId())) {
    20.             handle.operation(angle, buttonA, buttonB, pause, start);
    21.             break;
    22.         }
    23.     }
    24. }
    复制代码

  • Handle.java类,根据获取的手柄端数据和GameView.java类中的status变量的值对游戏进行操作,代码示例如下:
    1. public void operation(int angle, int buttonA, int buttonB, int pause, int start) {
    2. switch (GameView.getStatus()) {
    3.     case Constants.GAME_UNREADY: // 0游戏未开始
    4.         if (start != 0) {
    5.             startGame(); // 开始游戏
    6.             GameView.setStatus(Constants.GAME_START);
    7.         }
    8.         break;
    9.     case Constants.GAME_START: // 1游戏进行中
    10.         if (angle != 0) {
    11.             movePlane(angle); // 移动飞机
    12.         }
    13.         if (buttonA != 0) {
    14.             createBullet(); // 发射子弹
    15.         }
    16.         if (buttonB != 0) {
    17.             useBomb(); // 使用***(清空屏幕敌机)
    18.         }
    19.         if (pause != 0) {
    20.             pause(); // 暂停
    21.         }
    22.         break;
    23.     case Constants.GAME_PAUSE: // 2游戏暂停
    24.         if (start != 0) { // 取消暂停
    25.             reStart();
    26.             GameView.setStatus(Constants.GAME_START);
    27.         }
    28.         break;
    29.     case Constants.GAME_STOP: // 3游戏结束
    30.         if (pause != 0) { // 返回到主页
    31.             quitGame();
    32.             GameView.setStatus(Constants.GAME_UNREADY);
    33.         }
    34.         if (start != 0) { // 重新开始游戏
    35.             startGame();
    36.             GameView.setStatus(Constants.GAME_START);
    37.         }
    38.         break;
    39.     default:
    40.         break;
    41.     }
    42. }
    复制代码

生成玩家飞机
  • 当GameView.getStatus()的值为0,且获取手柄的start参数不为0时,进入游戏界面,代码示例如下:
    1. private void startGame() {
    2.     Intent intent = new Intent();
    3.     ElementName element = new ElementName("", abilitySlice.getBundleName(),
    4.     abilitySlice.getBundleName() + ".PlaneGameAbility");
    5.     intent.setElement(element);
    6.     abilitySlice.startAbility(intent);
    7. }
    复制代码

  • 进入游戏界面后,根据获取的手柄设备id集合创建玩家飞机,代码示例如下:
    1. public void start(List<String> deviceIdList) {
    2.     this.deviceIds = deviceIdList;
    3.     this.myPlanes = new ArrayList<>(deviceIds.size());
    4.     setPixelMapHolder();
    5.     // 创建飞机
    6.     createMyPlane();
    7. }

    8. private void createMyPlane() {
    9.     int index = 0;
    10.     MyPlane myPlane = null;
    11.     int position = this.screenWidth / MAXMYPLANENUM - MY_PLANE_INITIAL_POSITION;
    12.     for (String deviceId : deviceIds) {
    13.         if (index == 0) {
    14.             myPlane = new MyPlane(myPlaneOnePixelMapHolder, position + index * MY_PLANE_INITIAL_POSITION,
    15.                 this.screenHeight - MY_PLANE_V_LIMIT);
    16.         } else {
    17.             myPlane = new MyPlane(myPlaneTwoPixelMapHolder, position + index * MY_PLANE_INITIAL_POSITION,
    18.                 this.screenHeight - MY_PLANE_V_LIMIT);
    19.         }
    20.     myPlane.setDeviceId(deviceId);
    21.     myPlane.setIndex(index);
    22.     InnerEvent event = InnerEvent.get(1, 0, myPlane);
    23.     myEventHandler.sendEvent(event);
    24.     myPlanes.add(myPlane);
    25.     index++;
    26.     }
    27. }
    复制代码

  • 画玩家飞机,代码示例如下:
    1. private void drawMyPlane() {
    2.     Iterator<MyPlane> iterator = myPlanes.iterator();
    3.     while (iterator.hasNext()) {
    4.         MyPlane myPlane = iterator.next();
    5.         if (!myPlane.isDestroyed()) {
    6.             myPlane.draw(nowCanvas, paint);
    7.         }
    8.     }
    9. }
    复制代码

移动玩家飞机
  • 根据获取到的手柄端数据,移动玩家飞机,代码示例如下:
    1. public void movePlaneByHandles(int angle, String deviceId) {
    2.     MyPlane myPlane = null;
    3.     for (MyPlane nowMyPlane : myPlanes) {
    4.         if (nowMyPlane.getDeviceId().equals(deviceId)) {
    5.             myPlane = nowMyPlane;
    6.         }
    7.     }
    8.     if (myPlane == null) {
    9.         return;
    10.     }
    11.     myPlane.setPlaneX(myPlane.getPlaneX()
    12.         + ((int) (myPlane.getSpeed() * Math.cos(angle * (Math.PI / ANGULAR_180)))));
    13.     myPlane.setPlaneY(myPlane.getPlaneY()
    14.         - ((int) (myPlane.getSpeed() * Math.sin(angle * (Math.PI / ANGULAR_180)))));
    15.     if (myPlane.getPlaneX() < 0) {
    16.         myPlane.setPlaneX(0);
    17.     }
    18.     if (myPlane.getPlaneX() > this.screenWidth - MY_PLANE_H_LIMIT) {
    19.         myPlane.setPlaneX(this.screenWidth - MY_PLANE_H_LIMIT);
    20.     }
    21.     if (myPlane.getPlaneY() < 0) {
    22.         myPlane.setPlaneY(0);
    23.     }
    24.     if (myPlane.getPlaneY() > this.screenHeight - MY_PLANE_V_LIMIT) {
    25.         myPlane.setPlaneY(this.screenHeight - MY_PLANE_V_LIMIT);
    26.     }
    复制代码

发射子弹
  • 根据获取到的手柄端数据,创建子弹,子弹初始位置根据玩家飞机位置确定,代码示例如下:
    1. private void createBullet(MyPlane myPlane) {
    2.     if (myPlane == null) {
    3.         return;
    4.     }
    5.     Bullet bullet = new Bullet(bulletPixelMapHolder, myPlane.getPlaneX(), myPlane.getPlaneY());
    6.     bullet.setDeviceId(myPlane.getDeviceId());
    7.     bullets.add(bullet);
    8. }
    复制代码

  • 在屏幕中画子弹,销毁超出屏幕范围的子弹,并从子弹集合中移除,代码示例如下:
    1. private void drawBullet() {
    2.     Iterator<Bullet> iterator = bullets.iterator();
    3.     while (iterator.hasNext()) {
    4.         Bullet bullet = iterator.next();
    5.         if (!bullet.isDestroyed()) {
    6.             bullet.draw(nowCanvas, paint);
    7.         } else {
    8.             iterator.remove();
    9.         }
    10.     }
    11. }
    复制代码

使用***
  • 通过获取手柄端的设备ID获得对应的玩家飞机,若该玩家飞机***数大于0,则销毁屏幕中所有敌机(敌机数为N),该玩家飞机加N * 100分,并返回分数到手柄端,代码示例如下:
    1. public void bombEnemyPlaneByDeviceId(String deviceId) {
    2.     MyPlane myPlane = getMyPlaneByDeviceId(deviceId);
    3.     if (myPlane == null) {
    4.         return;
    5.     }
    6.     if (myPlane.getBombNum() == 0) {
    7.         return;
    8.     }
    9.     bombEnemyPlane(myPlane);
    10. }

    11. // 使用***摧毁敌机
    12. private void bombEnemyPlane(MyPlane myPlane) {
    13.     int score = 0; // ***摧毁屏幕敌机获得分数
    14.     myPlane.setBombNum(myPlane.getBombNum() - 1);
    15.     Iterator<EnemyPlane> iteratorEnemyPlane = enemyPlanes.iterator();
    16.     while (iteratorEnemyPlane.hasNext()) {
    17.         EnemyPlane enemyPlane = iteratorEnemyPlane.next();
    18.         if (!enemyPlane.isDestroyed()) {
    19.             createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
    20.             score += SCORE;
    21.             enemyPlane.destroy();
    22.         }
    23.     }
    24.     myPlane.setScore(myPlane.getScore() + score);
    25.     InnerEvent event = InnerEvent.get(1, 0, myPlane);
    26.     myEventHandler.sendEvent(event);
    27. }
    复制代码

随机生成敌机
  • 随机生成敌机从屏幕上方任意水平位置出现,代码示例如下:
    1. private void createEnemyPlane() {
    2.     // 随机生成敌机
    3.     int planeIndex = random.nextInt(SPEED_RATE) + SPEED_RATE;
    4.     PixelMapHolder enemyPlanePixelMapHolder = planeIndex == SMALL_ENEMY_PLANE_INDEX
    5.         ? smallEnemyPlanePixelMapHolder : bigEnemyPlanePixelMapHolder;
    6.     EnemyPlane enemyPlane = new EnemyPlane(enemyPlanePixelMapHolder, planeIndex);
    7.     enemyPlanes.add(enemyPlane);
    8. }
    复制代码

  • 画敌机(每50帧生成一个敌机),销毁超出屏幕范围的敌机,并从敌机集合中移除,代码示例如下:
    1. private void drawEnemyPlane() {
    2.     // 随机生成敌机
    3.     if (0 == frame % CREATE_ENEMY_PLANE_FRAME) {
    4.         createEnemyPlane();
    5.     }
    6.     Iterator<EnemyPlane> iterator = enemyPlanes.iterator();
    7.     while (iterator.hasNext()) {
    8.         EnemyPlane enemyPlane = iterator.next();
    9.         if (!enemyPlane.isDestroyed()) {
    10.             enemyPlane.draw(nowCanvas, paint);
    11.         } else {
    12.             iterator.remove();
    13.         }
    14.     }
    15. }
    复制代码

随机生成降落伞
  • 随机生成降落伞从屏幕上方任意水平位置出现,代码示例如下:
    1. private void createBomb() {
    2. // 随机生成***
    3. Bomb bomb = new Bomb(bulletAwardPixelMapHolder);
    4. bombs.add(bomb);
    5. }
    复制代码

  • 画降落伞(每2000帧生成一个降落伞),销毁超出屏幕范围的降落伞,并从降落伞集合中移除,代码示例如下:
    1. private void drawBomb() {
    2.     // 随机生成***
    3.     if (0 == frame % CREATE_BOMB_FRAME) {
    4.         frame = 0;
    5.         createBomb();
    6.     }
    7.     Iterator<Bomb> iterator = bombs.iterator();
    8.     while (iterator.hasNext()) {
    9.         Bomb bomb = iterator.next();
    10.         if (!bomb.isDestroyed()) {
    11.             bomb.draw(nowCanvas, paint);
    12.         } else {
    13.             iterator.remove();
    14.         }
    15.     }
    16. }
    复制代码

碰撞检测
  • 子弹碰撞敌机后,销毁该子弹和敌机,子弹对应的玩家飞机加100分,并返回分数到手柄端,代码示例如下:
    1. private void destroyEnemyPlane() {
    2.     MyPlane myPlane = null;
    3.     Iterator<Bullet> iterator = bullets.iterator();
    4.     while (iterator.hasNext()) {
    5.         Bullet bullet = iterator.next();
    6.         for (MyPlane nowMyPlane : myPlanes) {
    7.             if (nowMyPlane.getDeviceId().equals(bullet.getDeviceId())) {
    8.                 myPlane = nowMyPlane;
    9.                 break;
    10.             }
    11.         }
    12.         if (!bullet.isDestroyed() && (myPlane != null)) {
    13.             EnemyPlane enemyPlane = null;
    14.             Spirit spirit = bullet.collideWithOther(enemyPlanes);
    15.             if (spirit instanceof EnemyPlane) {
    16.             enemyPlane = (EnemyPlane) spirit;
    17.             }
    18.             if (enemyPlane != null) {
    19.                 createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
    20.                 iterator.remove();
    21.                 enemyPlanes.remove(enemyPlane);
    22.                 myPlane.setScore(myPlane.getScore() + SCORE);
    23.                 InnerEvent event = InnerEvent.get(1, 0, myPlane);
    24.                 myEventHandler.sendEvent(event);
    25.             }
    26.         }
    27.     }
    28. }
    复制代码

  • 玩家飞机碰撞敌机后,玩家飞机和敌机被销毁,若玩家飞机数量为0,则结束游戏,代码示例如下:
    1. private void destroyMyPlane() {
    2.     Iterator<MyPlane> iterator = myPlanes.iterator();
    3.     while (iterator.hasNext()) {
    4.         MyPlane myPlane = iterator.next();
    5.         if (!myPlane.isDestroyed()) {
    6.             EnemyPlane enemyPlane = null;
    7.             Spirit spirit = myPlane.collideWithOther(enemyPlanes);
    8.             if (spirit instanceof EnemyPlane) {
    9.                 enemyPlane = (EnemyPlane) spirit;
    10.             }
    11.             judgeGameOver(enemyPlane, myPlane, iterator);
    12.         }
    13.     }
    14. }

    15. private void judgeGameOver(EnemyPlane enemyPlane, MyPlane myPlane, Iterator<MyPlane> iterator) {
    16.     if (enemyPlane != null) {
    17.         createExposion(myPlane.getPlaneX(), myPlane.getPlaneY());
    18.         createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
    19.         enemyPlanes.remove(enemyPlane);
    20.         myPlane.setBombNum(0);
    21.         myPlane.destroy();
    22.         if (myPlanes.size() == MAXMYPLANENUM) {
    23.             iterator.remove();
    24.         } else {
    25.             status = Constants.GAME_STOP; // 游戏结束标识
    26.         }
    27.     }
    28. }
    复制代码

  • 降落伞碰撞玩家飞机后,销毁该降落伞,玩家飞机对应左/右下角***数量加1,代码示例如下:
    1. private void gainBomb() {
    2.     Iterator<Bomb> iterator = bombs.iterator();
    3.     while (iterator.hasNext()) {
    4.         Bomb bomb = iterator.next();
    5.         if (!bomb.isDestroyed()) {
    6.             MyPlane myPlane = null;
    7.             Spirit spirit = bomb.collideWithOther(myPlanes);
    8.             if (spirit instanceof MyPlane) {
    9.                 myPlane = (MyPlane) spirit;
    10.             }
    11.             if (myPlane != null) {
    12.                 myPlane.setBombNum(myPlane.getBombNum() + 1);
    13.                 bomb.destroy();
    14.                 iterator.remove();
    15.             }
    16.         }
    17.     }
    18. }
    复制代码

返回分数
  • 创建线程并绑定事件,返回分数到手柄端,代码示例如下:
    1. // 新增创建新线程
    2. myEventHandler = new MyEventHandler(EventRunner.create(true));
    3. // 设置投递事件
    4. InnerEvent event = InnerEvent.get(1, 0, myPlane);
    5. myEventHandler.sendEvent(event);

    6. // 创建EventHandler类
    7. private class MyEventHandler extends EventHandler {
    8.     MyEventHandler(EventRunner runner) throws IllegalArgumentException {
    9.         super(runner);
    10.     }

    11.     @Override
    12.     protected void processEvent(InnerEvent event) {
    13.         super.processEvent(event);
    14.         int eventId = event.eventId;
    15.         if (eventId == 1) {
    16.             if (event.object instanceof MyPlane) {
    17.                 MyPlane myPlane = (MyPlane) event.object;
    18.                 if (myPlane.getIndex() == 0) {
    19.                     playerOneScore = myPlane.getScore();
    20.                 } else {
    21.                     playerTwoScore = myPlane.getScore();
    22.                 }
    23.                 MainAbilitySlice.returnScore(myPlane.getScore(), myPlane.getDeviceId());
    24.             }
    25.         }
    26.     }
    27. }
    复制代码

  • 根据设备ID将分数返回到对应的手柄端,代码示例如下:
    1. public static void returnScore(int score, String deviceId) {
    2.     for (Handle handleInfo: handles) {
    3.         try {
    4.             if (handleInfo.getDeviceId().equals(deviceId) && handleInfo.isConn() && handleInfo.getProxy() != null) {
    5.                 handleInfo.getProxy().senDataToRemote(1, score);
    6.                 break;
    7.             }
    8.         } catch (RemoteException e) {
    9.             HiLog.error(TAG, handleInfo.getDeviceId() + "::GameServiceAbility::returnScore faild");
    10.         }
    11.     }
    12. }
    复制代码

游戏结束
  • 屏幕中所有玩家飞机被摧毁后,游戏结束(GameView.status值为3),调用Handles类并传递玩家飞机最终得分,代码示例如下:
    1. private void gameOver() {
    2.     MainAbilitySlice.getHandles().get(0).gameOver(playerOneScore, playerTwoScore);
    3. }
    复制代码

  • Handles类跳转到游戏结束界面,并传递玩家飞机最终得分,代码示例如下:
    1. public void gameOver(int playerOneScore, int playerTwoScore) {
    2.     Intent intent = new Intent();
    3.     intent.setParam("playerOneScore", playerOneScore);
    4.     intent.setParam("playerTwoScore", playerTwoScore);
    5.     ElementName element = new ElementName("", abilitySlice.getBundleName(),
    6.         abilitySlice.getBundleName() + ".GameOverAbility");
    7.     intent.setElement(element);
    8.     abilitySlice.startAbility(intent);
    9.     GameView.setStatus(Constants.GAME_STOP);
    10. }
    复制代码

  • 游戏结束界面展示玩家飞机得分,代码示例如下:
    1. private static final String KEYONE = "playerOneScore";
    2. private static final String KEYTWO = "playerTwoScore";

    3. @Override
    4. public void onStart(Intent intent) {
    5.     super.onStart(intent);
    6.     super.setUIContent(ResourceTable.Layout_ability_game_over);

    7.     // 获得玩家分数
    8.     Object scoreOne = intent.getParams().getParam(KEYONE);
    9.     Object scoreTwo = intent.getParams().getParam(KEYTWO);

    10.     // 设置分数
    11.     Component componentOne = findComponentById(ResourceTable.Id_playerOne);
    12.     Component componentTwo = findComponentById(ResourceTable.Id_playerTwo);
    13.     if (componentOne instanceof Text) {
    14.         Text textOne = (Text) componentOne;
    15.         textOne.setText("玩家一分数:" + scoreOne);
    16.     }
    17.     if (componentTwo instanceof Text) {
    18.         Text textTwo = (Text) componentTwo;
    19.         textTwo.setText("玩家二分数:" + scoreTwo);
    20.     }
    21. }
    复制代码


9. 恭喜你
通过本篇codelab,你可以学到:
  • 常用布局和常用组件
  • 分布式设备启动与连接
  • 线程间通信
  • 利用canvas组件绘制图形


10. 完整示例

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友