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. 分布式设备启动与连接
大屏端发起连接,进入应用,大屏端弹出手柄设备列表选择框。代码示例如下:
- // 设备选择弹出框
- private SelectDeviceDialog showDialog() {
- return new SelectDeviceDialog(this, new SelectDeviceDialog.SelectResultListener() {
- public void callBack(List<DeviceInfo> deviceInfos) {
- for (DeviceInfo deviceInfo : deviceInfos) {
- Handle handleInfo = new Handle(MainAbilitySlice.this); // 连接信息
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId(deviceInfo.getDeviceId())
- .withBundleName(getBundleName())
- .withAbilityName(MainAbility.class.getName())
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- intent.setOperation(operation);
- startAbility(intent);
- // 保存手柄连接信息
- boolean isConn = connectRemotePa(deviceInfo.getDeviceId(), 1, handleInfo);
- handleInfo.setDeviceId(deviceInfo.getDeviceId());
- handleInfo.setConn(isConn);
- handles.add(handleInfo);
- }
- }
- });
- }
复制代码 选择手柄设备(可多选),点击【确定】按钮,连接选择的手柄,代码示例如下:
- private boolean connectRemotePa(String deviceId, int requestType, Handle handleInfo) {
- Intent connectPaIntent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId(deviceId)
- .withBundleName(getBundleName())
- .withAbilityName(GameServiceAbility.class.getName())
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- connectPaIntent.setOperation(operation);
-
- IAbilityConnection conn = new IAbilityConnection() {
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
- GameRemoteProxy proxy = new GameRemoteProxy(remote);
- handleInfo.setProxy(proxy);
- }
-
- @Override
- public void onAbilityDisconnectDone(ElementName elementName, int index) {
- if (handleInfo.getProxy() != null) {
- handleInfo.setProxy(null);
- }
-
- }
- };
-
- boolean ret = connectAbility(connectPaIntent, conn);
- return ret;
- }
复制代码 大屏端发起连接后,手柄端被远程拉起,此时手柄端弹出设备列表选择框,选择大屏设备,点击【确定】按钮,连接选择的大屏。代码示例如下:
- private boolean connectRemotePa(String deviceId, int requestType) {
- Intent connectPaIntent = new Intent();
- Operation operation = new Intent.OperationBuilder().withDeviceId(deviceId)
- .withBundleName(getBundleName())
- .withAbilityName(GameServiceAbility.class.getName())
- .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
- .build();
- connectPaIntent.setOperation(operation);
-
- IAbilityConnection conn = new IAbilityConnection() {
- @Override
- public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
- LogUtil.info(TAG, "onAbilityConnectDone......");
- proxy = new GameRemoteProxy(remote, localDeviceId, calculAngle, handle);
- LogUtil.error(TAG, "connectRemoteAbility done");
- send(requestType);
- }
-
- @Override
- public void onAbilityDisconnectDone(ElementName elementName, int index) {
- if (proxy != null) {
- proxy = null;
- }
- LogUtil.info(TAG, "onAbilityDisconnectDone......");
- }
- };
- boolean ret = connectAbility(connectPaIntent, conn);
- return ret;
- }
-
- private void send(int requestType) {
- if (proxy != null) {
- try {
- proxy.senDataToRemote(requestType);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "onAbilityConnectDone RemoteException");
- }
- }
- }
复制代码
7. 手柄端功能 向大屏端发送指令
手柄端只是向大屏端发送指令,由大屏端实现业务逻辑。有如下指令:
- 点击绿色按钮,玩家飞机发送一个发射子弹的指令。
- 点击黄色按钮,释放大招,使屏幕上所有敌机爆炸销毁(需玩家飞机获得降落伞中***包,指令才可生效)。
- 点击【START】按钮,发送开始游戏的指令。
- 点击【PAUSE】按钮,发送暂停游戏的指令。
- 滑动摇杆,发送用于控制飞机飞行方向的指令。
以点击绿色按钮为例,代码示例如下:
- private Component.ClickedListener listenerA = new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- vibrator(Constants.VIBRATION_30); // 震动
- handle.setIsAbtnClick(1);
- isFlagA = true;
- if (isConn && proxy != null) {
- try {
- proxy.senDataToRemote(1);
- } catch (RemoteException e) {
- LogUtil.error(TAG, "Send Data to Remote Failed...");
- }
- }
- handle.setIsAbtnClick(0);
- }
- };
复制代码计算偏移角度
摇杆部分,大圆是固定不动的,小圆默认停在大圆中间。手指移动时,小圆跟随手指的位置移动但不能超出大圆范围。以大圆圆心为坐标系原点,手指的坐标点与X轴形成的角度为玩家飞机的偏移角度,以此来控制飞机行进方向。关键代码示例如下:
- 计算手指所在象限
- private boolean getFlagX() {
- return 0 < moveX - startPosX ? true : false;
- }
- private boolean getFlagY() {
- return 0 < moveY - startPosY ? true : false;
- }
- // 返回手指所在象限(坐标原点为大圆圆心)
- private int quadrant() {
- if (getFlagX() && !getFlagY()) {
- return Constants.QUADRANT_1;
- } else if (!getFlagX() && !getFlagY()) {
- return Constants.QUADRANT_2;
- } else if (!getFlagX() && getFlagY()) {
- return Constants.QUADRANT_3;
- } else if (getFlagX() && getFlagY()) {
- return Constants.QUADRANT_4;
- } else {
- return 0;
- }
- }
复制代码
- 计算偏移角度
- private int calculateAngle() {
- int degree = (int) Math.toDegrees(Math.atan(getDisAbsY() / getDisAbsX()));
- int quadrant = quadrant();
- switch (quadrant) {
- case Constants.QUADRANT_1:
- // 向右上移动
- angle = degree;
- break;
- case Constants.QUADRANT_2:
- // 向左上移动
- angle = Constants.DEGREE_180 - degree;
- break;
- case Constants.QUADRANT_3:
- // 向左下移动
- angle = -Constants.DEGREE_180 + degree;
- break;
- case Constants.QUADRANT_4:
- // 向右下移动
- angle = -degree;
- break;
- default:
- angle = 0;
- break;
- }
- return angle;
- }
复制代码
设置小圆坐标
当手指在大圆范围内时,小圆圆心坐标跟随手指坐标;当手指滑出大圆范围后,小圆圆心坐标在大圆圆周上,不能超出大圆范围。代码示例如下:
- private float[] getSmallCurrentPos(float currX, float currY) {
- float[] smallCurrentPos = new float[Constants.QUADRANT_2];
- if (getDisZ() < bigR) {
- smallCurrentPos[0] = currX;
- smallCurrentPos[1] = currY;
- return smallCurrentPos;
- } else {
- // 手指滑出大圆外后,由于小圆不能超出大圆,此时小圆圆心坐标在大圆圆周上,以下是计算小圆的控件坐标
- double disX = (getDisAbsX() * bigR) / getDisZ();
- double disY = (getDisAbsY() * bigR) / getDisZ();
- int quadrant = quadrant(); // 手指所在象限
- switch (quadrant) {
- case Constants.QUADRANT_1:
- smallCurrentPos[0] = (float) (disX + startPosX - smallR);
- smallCurrentPos[1] = (float) (startPosY - disY - smallR);
- break;
- case Constants.QUADRANT_2:
- smallCurrentPos[0] = (float) (startPosX - disX - smallR);
- smallCurrentPos[1] = (float) (startPosY - disY - smallR);
- break;
- case Constants.QUADRANT_3:
- smallCurrentPos[0] = (float) (startPosX - disX - smallR);
- smallCurrentPos[1] = (float) (disY + startPosY - smallR);
- break;
- case Constants.QUADRANT_4:
- smallCurrentPos[0] = (float) (disX + startPosX - smallR);
- smallCurrentPos[1] = (float) (disY + startPosY - smallR);
- break;
- default:
- break;
- }
- }
- return smallCurrentPos;
- }
复制代码显示得分
手柄端接收大屏端发送的数据,更新UI组件,代码示例如下:
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- int score = data.readInt();
- txtScore.setText(String.valueOf(score));
- return true;
- }
复制代码
8. 大屏端功能
通过获取手柄端发送的数据操作大屏端:开始游戏、暂停游戏、移动飞机以及发射子弹等。大屏端可随机生成敌机、降落伞以及检测碰撞事件。
精灵类定义
- 子类公共属性定义,代码示例如下:
- private PixelMapHolder pixelMapHolder; // 图片
- private int planeX; // x坐标
- private int planeY; // y坐标
- private int width; // 宽
- private int height; // 高
- private int speed; // 速度
- private boolean isDestroyed; // 是否被销毁
复制代码
- 当前对象与其他对象集合的碰撞检测,如未发生碰撞返回null,否则返回碰撞对象,代码示例如下:
- public Spirit collideWithOther(List<? extends Spirit> spirits) {
- Iterator<? extends Spirit> iterator = spirits.iterator();
- while (iterator.hasNext()) {
- Spirit spirit = iterator.next();
- RectFloat r1 = new RectFloat(spirit.getPlaneX(), spirit.getPlaneY(),
- spirit.getPlaneX() + spirit.getWidth(),
- spirit.getPlaneY() + spirit.getHeight());
- RectFloat r2 = new RectFloat(planeX, planeY, planeX + width,
- planeY + height);
- if (r1.getIntersectRect(r2)) { // 碰撞
- return spirit;
- }
- }
- return optionalEmpty.orElse(null);
- }
复制代码
- 画图方法,包含画图前、画图中、画图后三个方法,代码示例如下:
- public void draw(Canvas canvas, Paint paint) {
- beforeOndraw();
- onDraw(canvas, paint);
- afterDraw(canvas, paint);
- }
复制代码
- 画图前执行的方法,计算精灵类垂直方向坐标,代码示例如下:
- public void beforeOndraw() {
- move();
- }
- public void move() {
- planeY += speed;
- }
复制代码
- 画图中方法,将精灵对象画到屏幕,代码示例如下:
- public void onDraw(Canvas canvas, Paint paint) {
- canvas.drawPixelMapHolder(pixelMapHolder, planeX, planeY, paint);
- }
复制代码
- 画图之后执行的方法,销毁移出屏幕的精灵对象,代码示例如下:
- public void afterDraw(Canvas canvas, Paint paint) {
- if (GameUtils.getScreenHeight() < planeY) {
- destroy();
- }
- }
复制代码
- 执行销毁方法,将图片pixelMapHolder对象设置为null,将销毁状态设置为true,代码示例如下:
- public void destroy() {
- pixelMapHolder = null;
- isDestroyed = true;
- }
复制代码
精灵类子类定义
- Bomb.java降落伞类,构造方法中设置水平方向随机位置、垂直方向位置和速度,代码示例如下:
- public Bomb(PixelMapHolder pixelMapHolder) {
- super(pixelMapHolder);
- this.setPlaneX(getRandom().nextInt(GameUtils.getScreenWidth() - BOMB_Y_POSITION));
- this.setPlaneY(-BOMB_Y_POSITION);
- this.setSpeed((getRandom().nextInt(SPEED_RATE) + SPEED_RATE) * SPEED_RATE);
- }
复制代码
- Bullet.java子弹类,构造函数中根据玩家飞机位置设置子弹位置以及设置速度;重写afterDraw()方法,用于销毁超出屏幕范围的子弹,代码示例如下:
- public Bullet(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
- super(pixelMapHolder);
- this.setPlaneX(planeX + BULLET_X_OFFSET);
- this.setPlaneY(planeY - BULLET_Y_OFFSET);
- this.setSpeed(BULLET_SPEED);
- }
- public void afterDraw(Canvas canvas, Paint paint) {
- if (getPlaneY() < 0) {
- destroy();
- }
- }
复制代码
- EnemyPlane.java敌机类,构造函数中设置敌机尺寸大小、水平方向随机位置、垂直方向位置和随机速度,代码示例如下:
- public EnemyPlane(PixelMapHolder pixelMapHolder, int planeIndex) {
- super(pixelMapHolder);
- int size = (planeIndex == SPEED_RATE) ? Constants.SMALL_PLANE_SIZE : Constants.BIG_PLANE_SIZE;
- this.setPlaneX(getRandom().nextInt(GameUtils.getScreenWidth() - size));
- this.setPlaneY(-size);
- this.setSpeed((getRandom().nextInt(SPEED_RATE) + SPEED_RATE) * SPEED_RATE);
- }
复制代码
- Explosion.java爆炸效果类,销毁精灵子类时展示爆炸效果,构造方法中设置爆炸位置(为被销毁对象的位置)、速度为0;重写afterDraw()方法,每8帧销毁爆炸效果,代码示例如下:
- public Explosion(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
- super(pixelMapHolder);
- this.setPlaneX(planeX);
- this.setPlaneY(planeY);
- this.setSpeed(0);
- }
-
- @Override
- public void afterDraw(Canvas canvas, Paint paint) {
- if (0 == GameView.getFrame() % CLEAN_DESTROY_FRAME) {
- destroy();
- }
- }
复制代码
- MyPlane.java玩家飞机类,构造方法中设置水平、垂直方向位置、速度、分数为0、***数为0;重写draw()方法,在屏幕上画玩家飞机,代码示例如下:
- public MyPlane(PixelMapHolder pixelMapHolder, int planeX, int planeY) {
- super(pixelMapHolder);
- this.setPlaneX(planeX);
- this.setPlaneY(planeY);
- this.setSpeed(SPEED);
- this.setScore(0);
- this.setBombNum(0);
- }
-
- @Override
- public void draw(Canvas canvas, Paint paint) {
- canvas.drawPixelMapHolder(getPixelMapHolder(), getPlaneX(), getPlaneY(), paint);
- }
复制代码
获取手柄端数据
- GameserviceAbility.java类中实时获取手柄端数据,调用Handle类处理数据,代码示例如下:
- @Override
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- String deviceId = data.readString();
- taskDispatcher.syncDispatch(new Runnable() {
- @Override
- public void run() {
- syncDispatchRequest(deviceId, data);
- }
- });
- return true;
- }
-
- private void syncDispatchRequest(String deviceId, MessageParcel data) {
- int angle = data.readInt();
- int buttonA = data.readInt();
- int buttonB = data.readInt();
- int pause = data.readInt();
- int start = data.readInt();
- for (Handle handle: MainAbilitySlice.getHandles()) {
- if (deviceId.equals(handle.getDeviceId())) {
- handle.operation(angle, buttonA, buttonB, pause, start);
- break;
- }
- }
- }
复制代码
- Handle.java类,根据获取的手柄端数据和GameView.java类中的status变量的值对游戏进行操作,代码示例如下:
- public void operation(int angle, int buttonA, int buttonB, int pause, int start) {
- switch (GameView.getStatus()) {
- case Constants.GAME_UNREADY: // 0游戏未开始
- if (start != 0) {
- startGame(); // 开始游戏
- GameView.setStatus(Constants.GAME_START);
- }
- break;
- case Constants.GAME_START: // 1游戏进行中
- if (angle != 0) {
- movePlane(angle); // 移动飞机
- }
- if (buttonA != 0) {
- createBullet(); // 发射子弹
- }
- if (buttonB != 0) {
- useBomb(); // 使用***(清空屏幕敌机)
- }
- if (pause != 0) {
- pause(); // 暂停
- }
- break;
- case Constants.GAME_PAUSE: // 2游戏暂停
- if (start != 0) { // 取消暂停
- reStart();
- GameView.setStatus(Constants.GAME_START);
- }
- break;
- case Constants.GAME_STOP: // 3游戏结束
- if (pause != 0) { // 返回到主页
- quitGame();
- GameView.setStatus(Constants.GAME_UNREADY);
- }
- if (start != 0) { // 重新开始游戏
- startGame();
- GameView.setStatus(Constants.GAME_START);
- }
- break;
- default:
- break;
- }
- }
复制代码
生成玩家飞机
- 当GameView.getStatus()的值为0,且获取手柄的start参数不为0时,进入游戏界面,代码示例如下:
- private void startGame() {
- Intent intent = new Intent();
- ElementName element = new ElementName("", abilitySlice.getBundleName(),
- abilitySlice.getBundleName() + ".PlaneGameAbility");
- intent.setElement(element);
- abilitySlice.startAbility(intent);
- }
复制代码
- 进入游戏界面后,根据获取的手柄设备id集合创建玩家飞机,代码示例如下:
- public void start(List<String> deviceIdList) {
- this.deviceIds = deviceIdList;
- this.myPlanes = new ArrayList<>(deviceIds.size());
- setPixelMapHolder();
- // 创建飞机
- createMyPlane();
- }
-
- private void createMyPlane() {
- int index = 0;
- MyPlane myPlane = null;
- int position = this.screenWidth / MAXMYPLANENUM - MY_PLANE_INITIAL_POSITION;
- for (String deviceId : deviceIds) {
- if (index == 0) {
- myPlane = new MyPlane(myPlaneOnePixelMapHolder, position + index * MY_PLANE_INITIAL_POSITION,
- this.screenHeight - MY_PLANE_V_LIMIT);
- } else {
- myPlane = new MyPlane(myPlaneTwoPixelMapHolder, position + index * MY_PLANE_INITIAL_POSITION,
- this.screenHeight - MY_PLANE_V_LIMIT);
- }
- myPlane.setDeviceId(deviceId);
- myPlane.setIndex(index);
- InnerEvent event = InnerEvent.get(1, 0, myPlane);
- myEventHandler.sendEvent(event);
- myPlanes.add(myPlane);
- index++;
- }
- }
复制代码
- 画玩家飞机,代码示例如下:
- private void drawMyPlane() {
- Iterator<MyPlane> iterator = myPlanes.iterator();
- while (iterator.hasNext()) {
- MyPlane myPlane = iterator.next();
- if (!myPlane.isDestroyed()) {
- myPlane.draw(nowCanvas, paint);
- }
- }
- }
复制代码
移动玩家飞机
- 根据获取到的手柄端数据,移动玩家飞机,代码示例如下:
- public void movePlaneByHandles(int angle, String deviceId) {
- MyPlane myPlane = null;
- for (MyPlane nowMyPlane : myPlanes) {
- if (nowMyPlane.getDeviceId().equals(deviceId)) {
- myPlane = nowMyPlane;
- }
- }
- if (myPlane == null) {
- return;
- }
- myPlane.setPlaneX(myPlane.getPlaneX()
- + ((int) (myPlane.getSpeed() * Math.cos(angle * (Math.PI / ANGULAR_180)))));
- myPlane.setPlaneY(myPlane.getPlaneY()
- - ((int) (myPlane.getSpeed() * Math.sin(angle * (Math.PI / ANGULAR_180)))));
- if (myPlane.getPlaneX() < 0) {
- myPlane.setPlaneX(0);
- }
- if (myPlane.getPlaneX() > this.screenWidth - MY_PLANE_H_LIMIT) {
- myPlane.setPlaneX(this.screenWidth - MY_PLANE_H_LIMIT);
- }
- if (myPlane.getPlaneY() < 0) {
- myPlane.setPlaneY(0);
- }
- if (myPlane.getPlaneY() > this.screenHeight - MY_PLANE_V_LIMIT) {
- myPlane.setPlaneY(this.screenHeight - MY_PLANE_V_LIMIT);
- }
复制代码
发射子弹
- 根据获取到的手柄端数据,创建子弹,子弹初始位置根据玩家飞机位置确定,代码示例如下:
- private void createBullet(MyPlane myPlane) {
- if (myPlane == null) {
- return;
- }
- Bullet bullet = new Bullet(bulletPixelMapHolder, myPlane.getPlaneX(), myPlane.getPlaneY());
- bullet.setDeviceId(myPlane.getDeviceId());
- bullets.add(bullet);
- }
复制代码
- 在屏幕中画子弹,销毁超出屏幕范围的子弹,并从子弹集合中移除,代码示例如下:
- private void drawBullet() {
- Iterator<Bullet> iterator = bullets.iterator();
- while (iterator.hasNext()) {
- Bullet bullet = iterator.next();
- if (!bullet.isDestroyed()) {
- bullet.draw(nowCanvas, paint);
- } else {
- iterator.remove();
- }
- }
- }
复制代码
使用***
- 通过获取手柄端的设备ID获得对应的玩家飞机,若该玩家飞机***数大于0,则销毁屏幕中所有敌机(敌机数为N),该玩家飞机加N * 100分,并返回分数到手柄端,代码示例如下:
- public void bombEnemyPlaneByDeviceId(String deviceId) {
- MyPlane myPlane = getMyPlaneByDeviceId(deviceId);
- if (myPlane == null) {
- return;
- }
- if (myPlane.getBombNum() == 0) {
- return;
- }
- bombEnemyPlane(myPlane);
- }
-
- // 使用***摧毁敌机
- private void bombEnemyPlane(MyPlane myPlane) {
- int score = 0; // ***摧毁屏幕敌机获得分数
- myPlane.setBombNum(myPlane.getBombNum() - 1);
- Iterator<EnemyPlane> iteratorEnemyPlane = enemyPlanes.iterator();
- while (iteratorEnemyPlane.hasNext()) {
- EnemyPlane enemyPlane = iteratorEnemyPlane.next();
- if (!enemyPlane.isDestroyed()) {
- createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
- score += SCORE;
- enemyPlane.destroy();
- }
- }
- myPlane.setScore(myPlane.getScore() + score);
- InnerEvent event = InnerEvent.get(1, 0, myPlane);
- myEventHandler.sendEvent(event);
- }
复制代码
随机生成敌机
- 随机生成敌机从屏幕上方任意水平位置出现,代码示例如下:
- private void createEnemyPlane() {
- // 随机生成敌机
- int planeIndex = random.nextInt(SPEED_RATE) + SPEED_RATE;
- PixelMapHolder enemyPlanePixelMapHolder = planeIndex == SMALL_ENEMY_PLANE_INDEX
- ? smallEnemyPlanePixelMapHolder : bigEnemyPlanePixelMapHolder;
- EnemyPlane enemyPlane = new EnemyPlane(enemyPlanePixelMapHolder, planeIndex);
- enemyPlanes.add(enemyPlane);
- }
复制代码
- 画敌机(每50帧生成一个敌机),销毁超出屏幕范围的敌机,并从敌机集合中移除,代码示例如下:
- private void drawEnemyPlane() {
- // 随机生成敌机
- if (0 == frame % CREATE_ENEMY_PLANE_FRAME) {
- createEnemyPlane();
- }
- Iterator<EnemyPlane> iterator = enemyPlanes.iterator();
- while (iterator.hasNext()) {
- EnemyPlane enemyPlane = iterator.next();
- if (!enemyPlane.isDestroyed()) {
- enemyPlane.draw(nowCanvas, paint);
- } else {
- iterator.remove();
- }
- }
- }
复制代码
随机生成降落伞
- 随机生成降落伞从屏幕上方任意水平位置出现,代码示例如下:
- private void createBomb() {
- // 随机生成***
- Bomb bomb = new Bomb(bulletAwardPixelMapHolder);
- bombs.add(bomb);
- }
复制代码
- 画降落伞(每2000帧生成一个降落伞),销毁超出屏幕范围的降落伞,并从降落伞集合中移除,代码示例如下:
- private void drawBomb() {
- // 随机生成***
- if (0 == frame % CREATE_BOMB_FRAME) {
- frame = 0;
- createBomb();
- }
- Iterator<Bomb> iterator = bombs.iterator();
- while (iterator.hasNext()) {
- Bomb bomb = iterator.next();
- if (!bomb.isDestroyed()) {
- bomb.draw(nowCanvas, paint);
- } else {
- iterator.remove();
- }
- }
- }
复制代码
碰撞检测
- 子弹碰撞敌机后,销毁该子弹和敌机,子弹对应的玩家飞机加100分,并返回分数到手柄端,代码示例如下:
- private void destroyEnemyPlane() {
- MyPlane myPlane = null;
- Iterator<Bullet> iterator = bullets.iterator();
- while (iterator.hasNext()) {
- Bullet bullet = iterator.next();
- for (MyPlane nowMyPlane : myPlanes) {
- if (nowMyPlane.getDeviceId().equals(bullet.getDeviceId())) {
- myPlane = nowMyPlane;
- break;
- }
- }
- if (!bullet.isDestroyed() && (myPlane != null)) {
- EnemyPlane enemyPlane = null;
- Spirit spirit = bullet.collideWithOther(enemyPlanes);
- if (spirit instanceof EnemyPlane) {
- enemyPlane = (EnemyPlane) spirit;
- }
- if (enemyPlane != null) {
- createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
- iterator.remove();
- enemyPlanes.remove(enemyPlane);
- myPlane.setScore(myPlane.getScore() + SCORE);
- InnerEvent event = InnerEvent.get(1, 0, myPlane);
- myEventHandler.sendEvent(event);
- }
- }
- }
- }
复制代码
- 玩家飞机碰撞敌机后,玩家飞机和敌机被销毁,若玩家飞机数量为0,则结束游戏,代码示例如下:
- private void destroyMyPlane() {
- Iterator<MyPlane> iterator = myPlanes.iterator();
- while (iterator.hasNext()) {
- MyPlane myPlane = iterator.next();
- if (!myPlane.isDestroyed()) {
- EnemyPlane enemyPlane = null;
- Spirit spirit = myPlane.collideWithOther(enemyPlanes);
- if (spirit instanceof EnemyPlane) {
- enemyPlane = (EnemyPlane) spirit;
- }
- judgeGameOver(enemyPlane, myPlane, iterator);
- }
- }
- }
-
- private void judgeGameOver(EnemyPlane enemyPlane, MyPlane myPlane, Iterator<MyPlane> iterator) {
- if (enemyPlane != null) {
- createExposion(myPlane.getPlaneX(), myPlane.getPlaneY());
- createExposion(enemyPlane.getPlaneX(), enemyPlane.getPlaneY());
- enemyPlanes.remove(enemyPlane);
- myPlane.setBombNum(0);
- myPlane.destroy();
- if (myPlanes.size() == MAXMYPLANENUM) {
- iterator.remove();
- } else {
- status = Constants.GAME_STOP; // 游戏结束标识
- }
- }
- }
复制代码
- 降落伞碰撞玩家飞机后,销毁该降落伞,玩家飞机对应左/右下角***数量加1,代码示例如下:
- private void gainBomb() {
- Iterator<Bomb> iterator = bombs.iterator();
- while (iterator.hasNext()) {
- Bomb bomb = iterator.next();
- if (!bomb.isDestroyed()) {
- MyPlane myPlane = null;
- Spirit spirit = bomb.collideWithOther(myPlanes);
- if (spirit instanceof MyPlane) {
- myPlane = (MyPlane) spirit;
- }
- if (myPlane != null) {
- myPlane.setBombNum(myPlane.getBombNum() + 1);
- bomb.destroy();
- iterator.remove();
- }
- }
- }
- }
复制代码
返回分数
- 创建线程并绑定事件,返回分数到手柄端,代码示例如下:
- // 新增创建新线程
- myEventHandler = new MyEventHandler(EventRunner.create(true));
- // 设置投递事件
- InnerEvent event = InnerEvent.get(1, 0, myPlane);
- myEventHandler.sendEvent(event);
-
- // 创建EventHandler类
- private class MyEventHandler extends EventHandler {
- MyEventHandler(EventRunner runner) throws IllegalArgumentException {
- super(runner);
- }
-
- @Override
- protected void processEvent(InnerEvent event) {
- super.processEvent(event);
- int eventId = event.eventId;
- if (eventId == 1) {
- if (event.object instanceof MyPlane) {
- MyPlane myPlane = (MyPlane) event.object;
- if (myPlane.getIndex() == 0) {
- playerOneScore = myPlane.getScore();
- } else {
- playerTwoScore = myPlane.getScore();
- }
- MainAbilitySlice.returnScore(myPlane.getScore(), myPlane.getDeviceId());
- }
- }
- }
- }
复制代码
- 根据设备ID将分数返回到对应的手柄端,代码示例如下:
- public static void returnScore(int score, String deviceId) {
- for (Handle handleInfo: handles) {
- try {
- if (handleInfo.getDeviceId().equals(deviceId) && handleInfo.isConn() && handleInfo.getProxy() != null) {
- handleInfo.getProxy().senDataToRemote(1, score);
- break;
- }
- } catch (RemoteException e) {
- HiLog.error(TAG, handleInfo.getDeviceId() + "::GameServiceAbility::returnScore faild");
- }
- }
- }
复制代码
游戏结束
- 屏幕中所有玩家飞机被摧毁后,游戏结束(GameView.status值为3),调用Handles类并传递玩家飞机最终得分,代码示例如下:
- private void gameOver() {
- MainAbilitySlice.getHandles().get(0).gameOver(playerOneScore, playerTwoScore);
- }
复制代码
- Handles类跳转到游戏结束界面,并传递玩家飞机最终得分,代码示例如下:
- public void gameOver(int playerOneScore, int playerTwoScore) {
- Intent intent = new Intent();
- intent.setParam("playerOneScore", playerOneScore);
- intent.setParam("playerTwoScore", playerTwoScore);
- ElementName element = new ElementName("", abilitySlice.getBundleName(),
- abilitySlice.getBundleName() + ".GameOverAbility");
- intent.setElement(element);
- abilitySlice.startAbility(intent);
- GameView.setStatus(Constants.GAME_STOP);
- }
复制代码
- 游戏结束界面展示玩家飞机得分,代码示例如下:
- private static final String KEYONE = "playerOneScore";
- private static final String KEYTWO = "playerTwoScore";
-
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_game_over);
-
- // 获得玩家分数
- Object scoreOne = intent.getParams().getParam(KEYONE);
- Object scoreTwo = intent.getParams().getParam(KEYTWO);
-
- // 设置分数
- Component componentOne = findComponentById(ResourceTable.Id_playerOne);
- Component componentTwo = findComponentById(ResourceTable.Id_playerTwo);
- if (componentOne instanceof Text) {
- Text textOne = (Text) componentOne;
- textOne.setText("玩家一分数:" + scoreOne);
- }
- if (componentTwo instanceof Text) {
- Text textTwo = (Text) componentTwo;
- textTwo.setText("玩家二分数:" + scoreTwo);
- }
- }
复制代码
9. 恭喜你通过本篇codelab,你可以学到:
- 常用布局和常用组件
- 分布式设备启动与连接
- 线程间通信
- 利用canvas组件绘制图形
10. 完整示例