Cocos Creator HarmonyOS Next 工程改造成 ArkUI 组件并接入首页 Tab

本文记录一种通用改造方式:把 Cocos Creator 生成的 HarmonyOS Next 原生游戏工程,拆成一个可被纯 HarmonyOS ArkUI 应用引用的 HAR 组件,然后挂到宿主首页的某个 Tab 中。文中项目名、包名、域名、资源名均使用占位名。

1. 适用场景

Cocos Creator 构建 HarmonyOS Next 后,默认产物是一个完整的 Stage 模型应用,入口在 entry 模块中。如果宿主本身已经是一个纯 HarmonyOS 应用,直接把整个 Cocos 工程合进去通常会带来几个问题:

  • Cocos 生成工程有自己的 EntryAbilitypages/index,不能直接成为宿主首页的一部分。
  • Cocos 依赖 native so、Worker、XComponent、rawfile/native 资源,单纯拷贝页面 ArkTS 文件不够。
  • 首页 Tab 切换时需要暂停/恢复 Cocos 页面生命周期,否则容易出现后台继续渲染、重复启动引擎或黑屏。
  • Cocos 生成文件会在每次重新构建后变化,需要把“可覆盖的生成文件”和“宿主适配代码”分开。

推荐做法是把 Cocos 相关内容封装成一个本地 HAR,例如 common/cocos_widget,对外只暴露少量稳定对象:

  • CocosWidgetView:ArkUI 组件,内部包含 XComponent、EditBox/WebView/VideoPlayer 桥接层。
  • CocosWidgetRuntime:宿主 Ability 生命周期适配器,负责初始化 native app lifecycle、resourceManager 和 writablePath。
  • CocosWidgetController:宿主与 Cocos 游戏之间的通信入口,负责发送 pause/resume 等命令,并分发游戏回传事件。

2. 参考文档要点

  • Cocos Creator 3.8 文档说明,HarmonyOS Next 构建后需要使用 DevEco Studio 打开 native/engine/harmonyos-next 工程继续编译运行。
  • Cocos 生成工程中,entry/src/main/ets/cocos/WorkerManager.etsworkers/cocos_worker.etscomponentscommoncpp/types/libcocos 都是 Cocos 原生工程运行链路的一部分。
  • Cocos 的 ArkTS 原生通信文档强调,Cocos 运行时存在主线程和游戏线程,跨线程调用需要通过 Worker 消息传递,不能随意跨线程直接访问对象。
  • HarmonyOS Worker 的 scriptURL 路径和模块类型有关。HAR 内部创建自己的 Worker 时,优先使用“创建 Worker 的文件到 Worker 文件”的相对路径,并确保 build-profile.json5sourceOption.workers 已登记 Worker 文件。

3. 目录规划

下面用通用目录名说明关系:

cocos_project/
  native/engine/harmonyos-next/       # Cocos 生成的 HarmonyOS Next 原生工程
  native/engine/common/               # Cocos native 公共 C++ 工程
  build/harmonyos-next/               # Cocos 构建出的 JS/资源/native 配置

host_app/
  entry/                              # 宿主应用 entry
  common/cocos_widget/                # 从 Cocos 工程拆出来的 HAR 组件

common/cocos_widget 建议结构:

common/cocos_widget/
  Index.ets
  oh-package.json5
  build-profile.json5
  CMakeLists.txt
  src/main/module.json5
  src/main/ets/
    CocosWidgetView.ets
    CocosWidgetRuntime.ets
    host/CocosWorkerManager.ets
    cocos/                           # 从 Cocos 生成工程拷贝
    common/                          # 从 Cocos 生成工程拷贝
    components/                      # 从 Cocos 生成工程拷贝
    workers/                         # 从 Cocos 生成工程拷贝
  src/main/cpp/
    common/                          # 从 native/engine/common 拷贝
    res/                             # 从 build/harmonyos-next 拷贝
    types/libcocos/                  # 从 entry/src/main/cpp/types/libcocos 拷贝
  src/main/resources/
    rawfile/                         # 视项目是否需要保留
    base/                            # 视项目是否需要保留

原则:Cocos 生成目录保持可替换,宿主适配代码只放在 CocosWidgetView.etsCocosWidgetRuntime.etshost/*、模块配置文件和同步脚本中。

3.1 从 Cocos 生成工程迁移到 HAR 的清单

从 Cocos 生成工程迁移到宿主 HAR 时,最容易出错的是“哪些文件要原样迁移,哪些文件只能参考,哪些内容要放到宿主”。可以按下面的清单处理。

3.1.1 必须迁移的内容

这些内容是 Cocos 原生运行链路的一部分,缺任何一类都可能导致编译失败、Worker 启动失败、XComponent 黑屏或资源加载失败。

Cocos 生成工程位置 迁移到 HAR 的位置 作用
native/engine/harmonyos-next/entry/src/main/ets/cocos common/cocos_widget/src/main/ets/cocos Cocos ArkTS 适配层,包含原始 WorkerManager、系统能力 polyfill 等
native/engine/harmonyos-next/entry/src/main/ets/common common/cocos_widget/src/main/ets/common WorkerPort、事件分发、常量等公共桥接代码
native/engine/harmonyos-next/entry/src/main/ets/components common/cocos_widget/src/main/ets/components Cocos EditBox、WebView、VideoPlayer 的 ArkUI 实现
native/engine/harmonyos-next/entry/src/main/ets/workers common/cocos_widget/src/main/ets/workers Cocos 游戏线程 Worker,必须同时登记到 sourceOption.workers
native/engine/harmonyos-next/entry/src/main/cpp/types/libcocos common/cocos_widget/src/main/cpp/types/libcocos libcocos.so 的 ArkTS 类型声明和本地依赖描述
native/engine/common common/cocos_widget/src/main/cpp/common Cocos native 公共 C++ 工程和 CMake 配置
build/harmonyos-next common/cocos_widget/src/main/cpp/res Cocos 构建出的 JS、资源、proj/cfg.cmake 等 native 资源

迁移后必须确认:

  • src/main/cpp/res/proj/cfg.cmake 存在,否则 CMake include 会失败。
  • src/main/cpp/types/libcocos/oh-package.json5 存在,否则 import cocos from 'libcocos.so' 无法解析。
  • src/main/ets/workers/cocos_worker.ets 已登记到 HAR 的 build-profile.json5
  • XComponentlibraryname 与 CMake 中 CC_LIB_NAME 保持一致,通常都是 cocos

3.1.2 按需迁移的内容

这些文件不是所有项目都必须保留,建议先保守迁移,跑通后再裁剪。

Cocos 生成工程位置 迁移建议 说明
entry/src/main/resources/rawfile 按需迁移到 HAR 的 src/main/resources/rawfile 如果 Cocos 生成的 VideoPlayer、本地文件或 $rawfile('Resources/...') 仍在使用,就需要保留
entry/src/main/resources/base 按需迁移到 HAR 的 src/main/resources/base 如果只需要 Cocos 组件,不一定要保留生成工程的图标、入口文案等资源
entry/src/main/module.json5 里的 requestPermissions 不建议原样放进 HAR,应该迁移到宿主 entry/src/main/module.json5 网络、震动、网络状态、加速度计、陀螺仪等权限由最终 HAP 承担
entry/src/main/resources/base/profile/main_pages.json 通常不迁移 Cocos 页面不再通过路由作为 @Entry 页面打开,由宿主 Tab 直接挂载组件

权限特别容易漏。Cocos 生成工程常见权限包括:

{
  "name": "ohos.permission.INTERNET"
},
{
  "name": "ohos.permission.VIBRATE"
},
{
  "name": "ohos.permission.GET_NETWORK_INFO"
},
{
  "name": "ohos.permission.ACCELEROMETER"
},
{
  "name": "ohos.permission.GYROSCOPE"
}

是否全部需要取决于游戏功能:如果没有传感器输入,可以不加传感器权限;如果没有网络请求,可以不加网络权限。原则是“宿主 HAP 需要什么能力,就在宿主 entry 声明什么权限”。

3.1.3 不建议原样迁移的内容

这些文件在 Cocos 独立应用里有用,但拆成 HAR 后不能照搬。

Cocos 生成工程内容 处理方式 原因
entry/src/main/ets/entryability/EntryAbility.ets 不迁移为 Ability,抽取生命周期到 CocosWidgetRuntime 宿主应用只能由宿主自己的 Ability 管理窗口和全局生命周期
entry/src/main/ets/pages/index.ets 不原样迁移,改造成 CocosWidgetView.ets 需要去掉 @Entry,改成可被 Tab 调用的普通组件
生成工程根 AppScope/app.json5 不迁移 宿主已经有自己的应用级配置
生成工程根 build-profile.json5 的签名配置 不迁移,不公开 签名、证书、包名都属于宿主工程配置,不能进入通用 HAR 文档
生成工程 oh-package.json5 的项目名 不原样迁移 HAR 应使用通用模块名,例如 cocos_widget
.hvigor.idea.cxxbuildoh_modules 不迁移、不提交 都是构建缓存或 IDE 本地文件

3.1.4 迁移后必须改造的内容

迁移不是简单复制,还需要做下面几类改造。

  1. 页面改组件

把 Cocos 生成的:

@Entry
@Component
struct Index {
  // ...
}

改成:

@Component
export struct CocosWidgetView {
  @Prop @Watch('onActiveChange') active: boolean = true;
  onExit?: () => void;
  // ...
}
  1. WorkerManager 改为 HAR 内部管理类

Cocos 生成页面默认引用 ../cocos/WorkerManager。拆成 HAR 后建议新增 src/main/ets/host/CocosWorkerManager.ets,并在组件里引用它。这样 Cocos 生成目录可以被脚本覆盖,宿主适配逻辑不会丢。

  1. Worker 路径改为 HAR 内部相对路径

如果管理类在 src/main/ets/host,Worker 在 src/main/ets/workers,则路径是:

new worker.ThreadWorker('../workers/cocos_worker.ets', {
  type: 'classic',
  name: 'CocosWorker'
});
  1. onXCLoad 延迟到组件可见后再发

Cocos 生成页面通常在 XComponent.onLoad 里直接发 onXCLoad。Tab 组件应该先判断:

  • XComponent 是否已加载。
  • 当前 Tab 是否 active。
  • 引擎是否已经启动过。

满足条件再启动 native engine,避免非当前 Tab 提前启动。

  1. 事件注册要幂等

Cocos 生成页面只会注册一次事件;Tab 组件可能反复 aboutToAppear。如果继续每次 workPort.on(...),会出现一次 Worker 消息触发多次回调。建议加 listenersRegistered,或在组件销毁时按 target 清理事件。

  1. CMake 指向 HAR 内资源

Cocos 原始工程依赖自己的工程目录。迁移后必须把:

set(RES_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp/res CACHE STRING "Resource path" FORCE)
set(COMMON_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp/common)

固定到 HAR 内部路径。

  1. 宿主声明模块和依赖

build-profile.json5 要声明 HAR module,宿主 entry/oh-package.json5 要声明本地依赖。只做其中一个不够。

  1. 对外只导出稳定入口

Index.ets 建议只导出:

export { CocosWidgetView } from './src/main/ets/CocosWidgetView'
export { CocosWidgetRuntime } from './src/main/ets/CocosWidgetRuntime'

不要让宿主页面直接 import HAR 内部的 Cocos 生成文件,否则后续 Cocos 重新构建时容易破坏宿主代码。

4. HAR 模块配置

src/main/module.json5

{
  "module": {
    "name": "cocos_widget",
    "type": "har",
    "deviceTypes": ["phone", "tablet", "2in1", "default"]
  }
}

oh-package.json5

{
  "name": "cocos_widget",
  "version": "1.0.0",
  "description": "Cocos HarmonyOS Next widget module",
  "main": "Index.ets",
  "author": "",
  "license": "Apache-2.0",
  "dependencies": {
    "libcocos.so": "file:./src/main/cpp/types/libcocos"
  }
}

build-profile.json5 需要登记 Worker 和 native 构建配置:

{
  "apiType": "stageMode",
  "buildOption": {
    "sourceOption": {
      "workers": [
        "./src/main/ets/workers/cocos_worker.ets"
      ]
    },
    "externalNativeOptions": {
      "path": "./CMakeLists.txt",
      "arguments": "arm-linux-ohos -DOHOS_STL=c++_shared -DOPENHARMONY=1",
      "abiFilters": ["arm64-v8a"],
      "cppFlags": ""
    }
  },
  "targets": [
    { "name": "default" },
    { "name": "ohosTest" }
  ]
}

CMakeLists.txt 的关键是把 RES_DIR 指向 HAR 内的 Cocos 构建资源,并复用 Cocos native 的 common/CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
set(CC_LIB_NAME cocos)
set(CC_PROJ_SOURCES)
set(CC_COMMON_SOURCES)
set(CC_ALL_SOURCES)

option(APP_NAME "Project Name" "demo_game")
project(${APP_NAME} CXX)

set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb -Werror=return-type")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

set(RES_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp/res CACHE STRING "Resource path" FORCE)
if(NOT COMMON_DIR)
    set(COMMON_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp/common)
endif()

include(${COMMON_DIR}/CMakeLists.txt)

cc_openharmony_before_target(${CC_LIB_NAME})
add_library(${CC_LIB_NAME} SHARED ${CC_ALL_SOURCES})
cc_openharmony_after_target(${CC_LIB_NAME})

对外导出:

// Index.ets
export { CocosWidgetView } from './src/main/ets/CocosWidgetView'
export { CocosWidgetRuntime } from './src/main/ets/CocosWidgetRuntime'

5. Runtime:把 Cocos Ability 生命周期交给宿主管理

Cocos 生成工程默认在自己的 EntryAbility 中调用 app lifecycle。拆成组件后,宿主 EntryAbility 要显式转发:

import { common } from '@kit.AbilityKit';
import cocos, { context } from 'libcocos.so';
import { ContextType } from './common/Constants';
import { CocosWorkerManager } from './host/CocosWorkerManager';

const nativeContext: context = cocos.getContext(ContextType.ENGINE_UTILS);
const nativeAppLifecycle: context = cocos.getContext(ContextType.APP_LIFECYCLE);

export class CocosWidgetRuntime {
  private static created: boolean = false;
  private static visible: boolean = false;

  static onCreate(context: common.UIAbilityContext): void {
    if (CocosWidgetRuntime.created) {
      return;
    }
    CocosWidgetRuntime.created = true;
    nativeAppLifecycle.onCreate();
    nativeContext.resourceManagerInit(context.resourceManager);
    nativeContext.writablePathInit(context.cacheDir);
  }

  static onDestroy(): void {
    if (!CocosWidgetRuntime.created) {
      return;
    }
    nativeAppLifecycle.onDestroy();
    CocosWorkerManager.destroy();
    CocosWidgetRuntime.created = false;
    CocosWidgetRuntime.visible = false;
  }

  static onShow(): void {
    if (!CocosWidgetRuntime.created || CocosWidgetRuntime.visible) {
      return;
    }
    CocosWidgetRuntime.visible = true;
    nativeAppLifecycle.onShow();
  }

  static onHide(): void {
    if (!CocosWidgetRuntime.created || !CocosWidgetRuntime.visible) {
      return;
    }
    CocosWidgetRuntime.visible = false;
    nativeAppLifecycle.onHide();
  }
}

宿主 EntryAbility 中使用:

import { CocosWidgetRuntime } from 'cocos_widget';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    CocosWidgetRuntime.onCreate(this.context);
  }

  onDestroy(): void {
    CocosWidgetRuntime.onDestroy();
  }

  onForeground(): void {
    CocosWidgetRuntime.onShow();
  }

  onBackground(): void {
    CocosWidgetRuntime.onHide();
  }
}

6. Worker:HAR 内部必须使用正确路径

假设 Worker 管理类在:

src/main/ets/host/CocosWorkerManager.ets

Worker 文件在:

src/main/ets/workers/cocos_worker.ets

那么创建 Worker 的路径应为:

import worker from '@ohos.worker';

export class CocosWorkerManager {
  private static instance?: CocosWorkerManager;
  private cocosWorker: worker.ThreadWorker;

  private constructor() {
    this.cocosWorker = new worker.ThreadWorker('../workers/cocos_worker.ets', {
      type: 'classic',
      name: 'CocosWorker'
    });
  }

  static getInstance(): CocosWorkerManager {
    if (!CocosWorkerManager.instance) {
      CocosWorkerManager.instance = new CocosWorkerManager();
    }
    return CocosWorkerManager.instance;
  }

  static destroy(): void {
    CocosWorkerManager.instance?.cocosWorker.terminate();
    CocosWorkerManager.instance = undefined;
  }

  getWorker(): worker.ThreadWorker {
    return this.cocosWorker;
  }
}

注意点:

  • Worker 文件必须写入 HAR 的 build-profile.json5 -> buildOption.sourceOption.workers
  • 相对路径是“创建 Worker 的文件”到 Worker 文件的路径,不是宿主 entry 到 Worker 文件的路径。
  • 如果移动了管理类目录,必须同步调整相对路径。
  • 如果遇到 resolveBufferCallback get hsp buffer failedmodules.abc 相关崩溃,优先检查 Worker 路径、HAR 依赖声明、旧安装包缓存、是否误用了 HSP/HAR 路径规则。

7. View:把 Cocos 页面改成 ArkUI 组件

Cocos 生成的页面通常是 @Entry @Component struct Index。作为宿主 Tab 组件时,应改成普通导出组件:

@Component
export struct CocosWidgetView {
  @Prop @Watch('onActiveChange') active: boolean = true;
  onExit?: () => void;

  private xcomponentLoaded: boolean = false;
  private engineStarted: boolean = false;
  private listenersRegistered: boolean = false;
  private workPort: WorkerPort = WorkerPort.getInstance();

  aboutToAppear(): void {
    WorkerPort.getInstance().initPort(CocosWorkerManager.getInstance().getWorker());
    this.registerListenersOnce();
  }

  private registerListenersOnce(): void {
    if (this.listenersRegistered) {
      return;
    }
    this.listenersRegistered = true;

    this.workPort.on('exitGame', () => {
      this.onExit?.();
    });

    // 继续注册 Cocos 生成的 EditBox/WebView/VideoPlayer 事件。
  }

  private startEngineIfNeeded(): void {
    if (!this.active || !this.xcomponentLoaded || this.engineStarted) {
      return;
    }
    this.engineStarted = true;
    this.workPort.postMessage('onXCLoad');
    nativePageLifecycle.onPageShow();
  }

  onActiveChange(): void {
    if (this.active) {
      const wasStarted = this.engineStarted;
      this.startEngineIfNeeded();
      if (wasStarted) {
        nativePageLifecycle.onPageShow();
      }
    } else if (this.engineStarted) {
      nativePageLifecycle.onPageHide();
    }
  }

  build() {
    Flex({ direction: FlexDirection.Column }) {
      XComponent({ id: 'xcomponentId', type: 'surface', libraryname: 'cocos' })
        .onLoad(() => {
          this.xcomponentLoaded = true;
          this.startEngineIfNeeded();
        })
        .onDestroy(() => {
          if (this.engineStarted) {
            nativePageLifecycle.onPageHide();
          }
        })
    }
    .width('100%')
    .height('100%')
  }
}

这里有三个关键改造:

  • 去掉 @Entry,组件由宿主页面控制挂载。
  • 增加 active,Tab 可见时启动/恢复,不可见时隐藏。
  • 事件注册要幂等。Cocos 生成页面一般只出现一次,改成 Tab 组件后可能多次 aboutToAppear,重复注册会导致一次消息触发多次回调。

8. 宿主工程接入

build-profile.json5 增加模块:

{
  "name": "cocos_widget",
  "srcPath": "./common/cocos_widget"
}

宿主 entry/oh-package.json5 增加依赖:

{
  "dependencies": {
    "cocos_widget": "file:../common/cocos_widget"
  }
}

首页 Tab 中使用:

import { CocosWidgetView } from 'cocos_widget';

TabContent() {
  CocosWidgetView({
    active: this.currentTab === TabIndex.Game,
    pauseOnInactive: true,
    onExit: () => {
      this.tabsController.changeIndex(TabIndex.Home);
      this.currentTab = TabIndex.Home;
    }
  })
}
.tabBar(this.tabBuilder('游戏', TabIndex.Game))

如果宿主有多个首页版本或多个 Tab 容器,尽量只挂载一个 Cocos 组件实例。Cocos native 引擎通常不适合在同一进程内同时创建多个独立实例。

8.1 为后续业务通信预留 Controller

如果后续要做登录态同步、任务状态更新、游戏奖励回传、Tab 切换暂停等交互,不建议让宿主页面直接操作 WorkerPort。更稳的方式是在 HAR 里提供一个 Controller,宿主只认 send/on/off/pause/resume 这些稳定 API。

宿主页面示例:

import { CocosWidgetController, CocosWidgetView } from 'cocos_widget';

@State currentTab: number = 0;
private cocosController: CocosWidgetController = new CocosWidgetController();

aboutToAppear(): void {
  this.cocosController.on('ready', () => {
    this.cocosController.send('updateUser', {
      userId: 'demo_user',
      token: 'demo_token'
    });
  });

  this.cocosController.on('rewardChanged', (payload) => {
    console.info(`game reward changed: ${JSON.stringify(payload)}`);
  });
}

build() {
  Tabs({ controller: this.tabsController }) {
    TabContent() {
      CocosWidgetView({
        active: this.currentTab === TabIndex.Game,
        pauseOnInactive: true,
        controller: this.cocosController,
        onEvent: (name, payload) => {
          console.info(`game event: ${name}, ${JSON.stringify(payload)}`);
        },
        onExit: () => {
          this.currentTab = TabIndex.Home;
          this.tabsController.changeIndex(TabIndex.Home);
        }
      })
    }
  }
  .onChange((index: number) => {
    this.currentTab = index;
    if (index === TabIndex.Game) {
      this.cocosController.resume('tabActive');
    } else {
      this.cocosController.pause('tabInactive');
    }
  })
}

这里有两个暂停入口:

  • active=false:组件内部自动调用 nativePageLifecycle.onPageHide(),并在 pauseOnInactive=true 时向 Cocos 派发 pause
  • controller.pause(reason):宿主主动暂停,适合弹窗打开、页面半遮挡、低电量策略、业务流程阻塞等场景。

Cocos 脚本侧需要实现宿主命令入口。下面是通用示例,具体暂停逻辑要按游戏项目实际处理动画、调度器、音频、网络和物理模拟:

import { director, game, AudioSource } from 'cc';

globalThis.__CocosHallBridge = {
  onHostCommand(name: string, payload?: Record<string, unknown>) {
    if (name === 'pause') {
      director.pause();
      game.pause();
      // 按项目情况暂停 AudioSource、Tween、倒计时、网络轮询等。
      return;
    }

    if (name === 'resume') {
      game.resume();
      director.resume();
      // 按项目情况恢复 AudioSource、Tween、倒计时、网络轮询等。
      return;
    }

    if (name === 'updateUser') {
      // 读取 payload 中的用户信息、关卡信息或业务状态。
      return;
    }
  }
};

Cocos 回传宿主事件可以走同一条 Worker 通道。HAR 侧可以提供 postEvent(JSON.stringify({ name, payload })) 这样的 ArkTS reflection 出口,也可以在 Worker eval 后暴露 globalThis.cocosHallPostEvent(name, payload)。例如:

globalThis.cocosHallPostEvent?.('ready', {
  scene: 'home'
});

globalThis.cocosHallPostEvent?.('rewardChanged', {
  rewardId: 'demo_reward',
  count: 1
});

如果使用 Cocos 的 ArkTS reflection 调用 Worker 导出函数,方法路径要以实际 HAR 模块名和 Worker 文件路径为准。不要在公开文章中贴真实模块名、包名或业务事件名,示例里统一用 cocos_widgetdemo_userdemo_reward 这类占位值。

9. 资源同步策略

第一次改造可以保守拷贝:

  • cocos_project/build/harmonyos-next -> common/cocos_widget/src/main/cpp/res
  • cocos_project/native/engine/common -> common/cocos_widget/src/main/cpp/common
  • cocos_project/native/engine/harmonyos-next/entry/src/main/ets/{cocos,common,components,workers} -> HAR 的 src/main/ets/
  • entry/src/main/cpp/types/libcocos -> HAR 的 src/main/cpp/types/libcocos
  • entry/src/main/resources/rawfile -> HAR 的 src/main/resources/rawfile

之后再根据实际使用裁剪:

  • Cocos native 资源通常依赖 src/main/cpp/res
  • Cocos 生成的 ArkUI VideoPlayer 本地资源示例可能依赖 $rawfile('Resources/...')
  • 如果 cpp/resrawfile/Resources 存在重复大资源,要确认运行链路后再裁剪,避免包体翻倍。
  • 不要把 .cxxbuildoh_modules 提交到仓库。

建议写一个同步脚本,只刷新生成文件,不覆盖宿主适配文件。脚本执行前确认所有目标路径都在 common/cocos_widget 目录下,避免误删宿主代码。

10. 常见问题排查

10.1 HAR 能编译,但运行时报 hsp buffer failed

优先检查:

  • common/cocos_widget/build-profile.json5 是否登记了 ./src/main/ets/workers/cocos_worker.ets
  • 创建 Worker 的路径是否为 HAR 内部相对路径,例如 ../workers/cocos_worker.ets
  • 宿主 entry/oh-package.json5 是否依赖了本地 HAR。
  • build-profile.json5 是否声明了 HAR module。
  • 是否安装过旧包。清理构建缓存、卸载设备上的旧应用后再安装。
  • 是否把 HAR 当 HSP 配置,或把 HSP 路径规则误用到 HAR Worker。

10.2 XComponent 黑屏

检查:

  • XComponent({ libraryname: 'cocos' }) 是否和 native CC_LIB_NAME 一致。
  • libcocos.so 的 types 包是否在 HAR oh-package.json5 中声明。
  • CocosWidgetRuntime.onCreate() 是否执行了 resourceManagerInit()writablePathInit()
  • active 初始值是否为 false,导致 onXCLoad 没有发送。
  • native 只配置了 arm64-v8a 时,测试设备或模拟器 ABI 是否匹配。

10.3 Tab 切走后游戏仍在跑

检查 active 是否随 Tab index 改变,并在 active=false 时调用 nativePageLifecycle.onPageHide() 和宿主命令 pause。只依赖 aboutToDisappear() 不够,因为很多 Tab 容器不会销毁非当前页。

如果 pause 已经发出但仍然发热,问题通常在 Cocos 脚本侧:需要确认 onHostCommand('pause') 里是否真正暂停了动画、调度器、音频、网络轮询和物理模拟。HAR 只能负责把暂停命令送到游戏线程,具体停哪些系统要由游戏逻辑决定。

10.4 回调触发多次

检查 aboutToAppear() 是否重复注册 workPort.on(...)。建议加 listenersRegistered,或在 aboutToDisappear() 使用 off/removeAll/targetOff 清理当前组件注册的事件。

10.5 资源找不到

检查:

  • src/main/cpp/res/proj/cfg.cmake 是否存在。
  • CMakeLists.txtRES_DIR 是否指向 src/main/cpp/res
  • $rawfile('Resources/...') 使用的资源是否在 src/main/resources/rawfile/Resources
  • Cocos 重新构建后是否执行了同步脚本。

11. 发布文章前的脱敏清单

如果要把经验整理成公开文章,不要发布以下信息:

  • 真实 App 名、业务模块名、包名、bundleName、ability 名。
  • 签名文件路径、证书名、keyAlias、storePassword、keyPassword、profile 路径。
  • 真实接口域名、WebView URL、AppID、渠道号、第三方 SDK 私有配置。
  • Cocos 构建配置里的真实 APP_NAME、项目名、资源中可识别的品牌图、截图。
  • 崩溃日志中的进程名、UID、设备标识、内部路径。

建议统一替换为:

host_app
cocos_project
cocos_widget
demo_game
com.example.host
https://example.com

12. 本次改造的通用结论

  • Cocos 生成的 HarmonyOS Next 工程不能只拷贝一个 ArkUI 页面,native、Worker、资源和生命周期都要一起迁移。
  • 抽成 HAR 是比较稳的方式:宿主只依赖 cocos_widget,Cocos 生成文件可以通过脚本刷新。
  • 作为 Tab 组件时,重点不是“显示出来”,而是“只启动一次、可暂停恢复、事件不重复注册、通信 API 可持续扩展”。
  • 宿主和 Cocos 的业务交互建议统一走 Controller,不要让页面直接依赖 Worker 细节。
  • 如果遇到 modules.abchsp buffer failed 这类运行期问题,优先从 Worker 路径和模块依赖声明排查。
  • 公开分享时只讲通用结构和关键代码,不要贴真实仓库配置、构建日志和签名信息。

参考链接

  • Cocos Creator:发布到 HarmonyOS Next
    https://docs.cocos.com/creator/3.8/manual/zh/editor/publish/publish-openharmony.html
  • Cocos Creator:ArkTS 与 Cocos 引擎脚本交互、线程安全说明
    https://docs.cocos.com/creator/3.8/manual/zh/advanced-topics/arkts-reflection.html
  • HarmonyOS:HAR 静态共享包官方文档
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/har-package-V5
  • HarmonyOS:Worker 官方文档
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐