多设备适配 原创

游戏技术分享
发布于 2025-5-7 15:59
浏览
0收藏

1 多设备适配

游戏一般需要适配不同屏幕尺寸的设备,例如手机、折叠屏、平板等。

不同屏幕尺寸适配

游戏通常使用XComponent组件自定义渲染,渲染流程如下图所示:

多设备适配-鸿蒙开发者社区cke_1686.png

其中OnSurfaceCreated和OnSurfaceChanged两个接口可分别在surface创建和surface大小变化时进行回调,因此适配不同屏幕尺寸设备时,可重点关注这两个接口。

  • OnSurfaceCreated:surface创建时进行回调。
  • OnSurfaceChanged:surface大小变化时进行回调。具体使用场景在游戏横竖屏切换引起的surface宽高变化时,或折叠屏在折叠和展开状态下不同surface大小变化时回调。

示例说明

下述代码中在OnSurfaceCreated和OnSurfaceChanged设置的显示画面的宽高都跟XComponent组件一致,且每次surface改变后都会重新获取对应的宽高及方向,以达到不同设备及设备显示区域变化的适配。

// OnSurfaceCreated回调
void PluginRender::OnSurfaceCreated(OH_NativeXComponent* component, void* window)
{
    OHOS_LOGD("PluginRender::OnSurfaceCreated");
    eglCore_ = new EGLCore();
    // 获取XComponent组件宽高
    int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);

    // OnSurfaceCreated逻辑,通过EGL绘制window上需要显示的画面
    if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        eglCore_->GLContextInit(window, width_, height_);
        // 设置XComponent组件宽高为显示画面宽高,并设置显示方向
        native_manager::GetInstance()->onSurfaceCreated(width_, height_, Js_GameEngineHelper::getDisplayOrientation());
    }
}

// OnSurfaceChanged回调
void PluginRender::OnSurfaceChanged(OH_NativeXComponent* component, void* window)
{
    OHOS_LOGD("PluginRender::OnSurfaceChanged");
    // 重新获取XComponent组件宽高
    int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);

    // OnSurfaceChanged逻辑
    if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        native_manager *instance = native_manager::GetInstance();
        // 改变XComponent组件宽高为显示画面宽高,并设置显示方向
        instance->onSurfaceChanged(width_, height_, Js_GameEngineHelper::getDisplayOrientation());
    }
}

窗口安全区(内容规避区)适配

部分游戏存在需要适配安全区的情况,在沉浸式体验中内容规避区会做额外处理。下图中展示了竖屏、横屏、反向横屏三种不同情况下的安全区示意图。

多设备适配-鸿蒙开发者社区cke_2967.png

示例代码

1. 获取安全区信息。

onWindowStageCreate(windowStage: window.WindowStage) {
    try {
      let windowClass:window.Window = windowStage.getMainWindowSync();

      // 获取屏幕安全区域,系统默认区,顶部状态栏位置
      let avoidArea: window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);

    // 刘海屏区域 -- 一般为前置摄像头位置
    //   let avoidArea: window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_CUTOUT);

    //  导航条区域 -- 一般为底部
    //   let avoidArea: window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      GlobalContext.storeGlobalThis(GlobalContextConstants.AVOID_AREA, avoidArea);

      // 省略部分代码  
    } catch (exception) {
      console.error('Failed to get or set the window, cause ' + JSON.stringify(exception));
    }
  }

2. 计算可显示内容区域的top、bottom、left和right。

static getTop() :number{
    let avoidArea: window.AvoidArea = GlobalContext.loadGlobalThis(GlobalContextConstants.AVOID_AREA);
    let result:number = 0;
    if (avoidArea != undefined && avoidArea.visible) {
      let orientation = display.getDefaultDisplaySync().orientation;
      if (orientation == display.Orientation.LANDSCAPE) {
        // 横屏
        result = avoidArea.leftRect.left + avoidArea.leftRect.width; // 0
      } else if (orientation == display.Orientation.LANDSCAPE_INVERTED) {
        // 反向横屏
        result = avoidArea.rightRect.left + avoidArea.rightRect.width; // 0
      } else {
        // 竖屏
        result = avoidArea.topRect.top + avoidArea.topRect.height;
      }
    } else {
      console.log("Cannnot get TopSafeAreaPixel, avoidArea visible false");
    }
    return result;
  }

  static getBottom() {
    let avoidArea: window.AvoidArea = GlobalContext.loadGlobalThis(GlobalContextConstants.AVOID_AREA)
    if (avoidArea != undefined && avoidArea.visible) {
      let orientation = display.getDefaultDisplaySync().orientation;
      if (orientation == display.Orientation.LANDSCAPE) {
        return avoidArea.rightRect.left;
      } else if (orientation == display.Orientation.LANDSCAPE_INVERTED) {
        return avoidArea.leftRect.left + avoidArea.leftRect.width;
      } else {
        return avoidArea.bottomRect.top;
      }
    } else {
      console.error("Cannnot get BottomSafeAreaPixel");
      return 0;
    }
  }

  static getLeft() {
    let avoidArea: window.AvoidArea = GlobalContext.loadGlobalThis(GlobalContextConstants.AVOID_AREA)
    if (avoidArea != undefined && avoidArea.visible) {
      let orientation = display.getDefaultDisplaySync().orientation;
      if (orientation == display.Orientation.LANDSCAPE) {
        return avoidArea.bottomRect.top;
      } else if (orientation == display.Orientation.LANDSCAPE_INVERTED) {
        return avoidArea.topRect.top + avoidArea.topRect.height
      } else {
        return avoidArea.leftRect.left + avoidArea.leftRect.width;
      }
    } else {
      console.error("Cannnot get LeftSafeAreaPixel");
      return 0;
    }
  }

  static getRight() {
    let avoidArea: window.AvoidArea = GlobalContext.loadGlobalThis(GlobalContextConstants.AVOID_AREA)
    if (avoidArea != undefined && avoidArea.visible) {
      let orientation = display.getDefaultDisplaySync().orientation;
      if (orientation == display.Orientation.LANDSCAPE) {
        return avoidArea.topRect.top + avoidArea.topRect.height
      } else if (orientation == display.Orientation.LANDSCAPE_INVERTED) {
        return avoidArea.bottomRect.top;
      } else {
        return avoidArea.rightRect.left;
      }
    } else {
      console.error("Cannnot get RightSafeAreaPixel");
      return 0;
    }
  }

参考文档


更多问题可关注:

鸿蒙游戏官方网站:​​已有游戏移植-鸿蒙游戏-华为开发者联盟​

公开课:​​华为开发者学堂​

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
收藏
回复
举报
回复
    相关推荐