Cocos Creator HarmonyOS Next 工程改造成 ArkUI 组件并接入首页 Tab(就是把cocos项目变成har包当成一个组件给原来的鸿蒙项目接入)
Cocos Creator HarmonyOS Next 工程改造成 ArkUI 组件并接入首页 Tab
本文记录一种通用改造方式:把 Cocos Creator 生成的 HarmonyOS Next 原生游戏工程,拆成一个可被纯 HarmonyOS ArkUI 应用引用的 HAR 组件,然后挂到宿主首页的某个 Tab 中。文中项目名、包名、域名、资源名均使用占位名。
1. 适用场景
Cocos Creator 构建 HarmonyOS Next 后,默认产物是一个完整的 Stage 模型应用,入口在 entry 模块中。如果宿主本身已经是一个纯 HarmonyOS 应用,直接把整个 Cocos 工程合进去通常会带来几个问题:
- Cocos 生成工程有自己的
EntryAbility和pages/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.ets、workers/cocos_worker.ets、components、common、cpp/types/libcocos都是 Cocos 原生工程运行链路的一部分。 - Cocos 的 ArkTS 原生通信文档强调,Cocos 运行时存在主线程和游戏线程,跨线程调用需要通过 Worker 消息传递,不能随意跨线程直接访问对象。
- HarmonyOS Worker 的
scriptURL路径和模块类型有关。HAR 内部创建自己的 Worker 时,优先使用“创建 Worker 的文件到 Worker 文件”的相对路径,并确保build-profile.json5的sourceOption.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.ets、CocosWidgetRuntime.ets、host/*、模块配置文件和同步脚本中。
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。XComponent的libraryname与 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、.cxx、build、oh_modules |
不迁移、不提交 | 都是构建缓存或 IDE 本地文件 |
3.1.4 迁移后必须改造的内容
迁移不是简单复制,还需要做下面几类改造。
- 页面改组件
把 Cocos 生成的:
@Entry
@Component
struct Index {
// ...
}
改成:
@Component
export struct CocosWidgetView {
@Prop @Watch('onActiveChange') active: boolean = true;
onExit?: () => void;
// ...
}
- WorkerManager 改为 HAR 内部管理类
Cocos 生成页面默认引用 ../cocos/WorkerManager。拆成 HAR 后建议新增 src/main/ets/host/CocosWorkerManager.ets,并在组件里引用它。这样 Cocos 生成目录可以被脚本覆盖,宿主适配逻辑不会丢。
- Worker 路径改为 HAR 内部相对路径
如果管理类在 src/main/ets/host,Worker 在 src/main/ets/workers,则路径是:
new worker.ThreadWorker('../workers/cocos_worker.ets', {
type: 'classic',
name: 'CocosWorker'
});
onXCLoad延迟到组件可见后再发
Cocos 生成页面通常在 XComponent.onLoad 里直接发 onXCLoad。Tab 组件应该先判断:
XComponent是否已加载。- 当前 Tab 是否 active。
- 引擎是否已经启动过。
满足条件再启动 native engine,避免非当前 Tab 提前启动。
- 事件注册要幂等
Cocos 生成页面只会注册一次事件;Tab 组件可能反复 aboutToAppear。如果继续每次 workPort.on(...),会出现一次 Worker 消息触发多次回调。建议加 listenersRegistered,或在组件销毁时按 target 清理事件。
- 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 内部路径。
- 宿主声明模块和依赖
根 build-profile.json5 要声明 HAR module,宿主 entry/oh-package.json5 要声明本地依赖。只做其中一个不够。
- 对外只导出稳定入口
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 failed或modules.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_widget、demo_user、demo_reward 这类占位值。
9. 资源同步策略
第一次改造可以保守拷贝:
cocos_project/build/harmonyos-next->common/cocos_widget/src/main/cpp/rescocos_project/native/engine/common->common/cocos_widget/src/main/cpp/commoncocos_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/libcocosentry/src/main/resources/rawfile-> HAR 的src/main/resources/rawfile
之后再根据实际使用裁剪:
- Cocos native 资源通常依赖
src/main/cpp/res。 - Cocos 生成的 ArkUI VideoPlayer 本地资源示例可能依赖
$rawfile('Resources/...')。 - 如果
cpp/res和rawfile/Resources存在重复大资源,要确认运行链路后再裁剪,避免包体翻倍。 - 不要把
.cxx、build、oh_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' })是否和 nativeCC_LIB_NAME一致。libcocos.so的 types 包是否在 HARoh-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.txt的RES_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.abc、hsp 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
更多推荐



所有评论(0)