1. 项目缘起:当游戏引擎遇上分布式操作系统

最近在捣鼓一个跨平台的小游戏Demo,核心需求是希望它能在搭载鸿蒙系统的设备上流畅运行,同时还要兼顾其他主流平台。我选择了“团结引擎”作为开发工具,但在实际动手时,第一个拦路虎就出现了: 消息交互 。这听起来是个基础问题,不就是发个消息、收个数据吗?但当你真正把“团结引擎”这个游戏开发环境,放到鸿蒙这个强调“分布式软总线”、“一次开发多端部署”的生态里,你会发现传统的消息传递方式处处碰壁。

简单来说,“团结引擎”内部有一套自己的事件和消息系统,用于处理UI交互、游戏逻辑通信。而鸿蒙系统,特别是其应用开发框架ArkUI,也有一套基于Ability和ParticleAbility的生命周期与事件机制。这两套系统在各自的领域内都运行良好,但当你试图让一个在团结引擎里生成的游戏界面,去调用鸿蒙系统级的服务(比如获取传感器数据、进行跨设备通信),或者反过来,让鸿蒙系统的通知能触发游戏内的某个特效时,通道就堵死了。更具体地说,你无法直接在Unity的C#脚本里调用OHOS的Java/Kotlin接口,反之亦然。这种“语言墙”和“运行时墙”是跨平台开发中最常见也最核心的挑战。

我查了不少资料,发现社区里关于这方面的讨论要么过于零散,要么停留在理论层面。很多开发者卡在“知道要桥接,但不知道具体每一步怎么走,参数怎么传,错了怎么调”的环节。所以,我决定把这次从零搭建通信桥梁的完整过程、踩过的坑以及最终验证可行的方案记录下来。这篇文章不会空谈架构,而是聚焦于一次具体的、可复现的实践: 如何让团结引擎中的游戏逻辑,可靠地接收到来自鸿蒙系统侧发送的一条自定义消息,并触发相应的游戏内事件 。无论你是想实现设备旋转同步到游戏视角,还是想把手机变成游戏手柄,其底层通信原理都是相通的。

2. 理解通信壁垒:两套生态的“柏林墙”

在开始敲代码之前,我们必须先搞清楚隔离的根源在哪里。盲目地找方法就像蒙眼过河,只有看清了“河”的宽度和深度,才能选择合适的“桥”。

2.1 团结引擎侧:基于C#的托管环境

团结引擎本质上是一个高度定制和优化的游戏运行时环境。我们的游戏逻辑通常用C#编写,运行在一个托管的、受控的虚拟机(类似于Mono或IL2CPP转换后的原生环境)中。这个环境是相对封闭的:

  • 通信边界 :它主要面向图形渲染、物理模拟、游戏循环。与操作系统底层的交互,大多通过引擎封装好的API进行,例如文件读写、网络请求。对于鸿蒙特有的能力,引擎的默认封装可能尚未覆盖或不够直接。
  • 线程模型 :Unity(团结引擎的核心基础)的主循环在单一线程中运行游戏逻辑(主线程),这对于保证游戏状态的一致性至关重要。任何来自外部的、非主线程的调用,如果直接操作游戏对象,都会引发线程安全问题,导致崩溃或难以调试的异常。

2.2 鸿蒙侧:基于ArkTS/JS的UI与系统服务

鸿蒙应用,特别是基于ArkUI开发的应用,其UI和主要业务逻辑通常使用ArkTS或JavaScript编写。系统服务则通过Ability、ExtensionAbility等组件提供,它们运行在由鸿蒙系统管理的独立进程中。

  • 通信出口 :鸿蒙应用可以通过 @ohos.rpc @ohos.want 等模块进行进程间通信(IPC),或者通过 Emitter 等事件机制进行应用内通信。但是,这些通信的终点是鸿蒙应用自身的UI或Service Ability,而不是直接指向团结引擎的C#运行时。
  • 语言与运行时 :这是最根本的障碍。ArkTS/JS代码无法直接调用C#的函数或访问C#的对象内存。两者是完全不同的语言和运行时环境。

2.3 需要的桥梁:一个双向、异步、安全的通道

我们的目标,是在这两堵“墙”之间建立一个通道。这个通道需要满足几个关键特性:

  1. 双向性 :既能从鸿蒙向引擎发送指令(如“开始游戏”、“暂停”),也能从引擎向鸿蒙反馈状态(如“游戏得分”、“加载进度”)。
  2. 异步性 :通信不能阻塞任何一方的运行。鸿蒙发送消息后应立即返回,引擎在合适的时机(如下一帧)处理;引擎发送的消息也不应卡住鸿蒙的UI响应。
  3. 安全性 :必须妥善处理线程问题,确保从鸿蒙侧收到的消息,最终在团结引擎的主线程中被执行,以操作游戏对象。
  4. 数据协议 :需要约定一种双方都能理解的数据格式。简单消息可以用字符串,复杂数据则需要序列化(如JSON)。

理清了这些,我们的技术方案就呼之欲出了: 通过原生插件(Native Plugin)作为桥梁,利用C/C++这一“通用语言”来实现两端的中转,并通过消息队列和线程安全机制来保证异步与安全。

3. 搭建通信桥梁:原生插件的设计与实现

这是整个方案最核心的部分。我们将创建一个C/C++动态库,它将被团结引擎和鸿蒙应用共同调用,充当“翻译官”和“邮差”的角色。

3.1 创建C/C++桥接层

首先,在你的团结引擎项目目录下(或一个独立的Native插件项目中),创建C++头文件和源文件。这里我们以 UnityHarmonyBridge.h UnityHarmonyBridge.cpp 为例。

UnityHarmonyBridge.h

#ifndef UNITY_HARMONY_BRIDGE_H
#define UNITY_HARMONY_BRIDGE_H

#include <string>
#include <functional>
#include <queue>
#include <mutex>

// 定义从鸿蒙到Unity的消息回调函数类型
typedef void (*MessageFromHarmonyCallback)(const char* message);

class UnityHarmonyBridge {
public:
    // 获取单例实例
    static UnityHarmonyBridge& GetInstance();

    // 供C#端调用:初始化桥接,注册回调函数
    void Initialize(MessageFromHarmonyCallback callback);

    // 供C#端调用:向鸿蒙侧发送消息
    void SendMessageToHarmony(const char* message);

    // 供鸿蒙侧JNI/NAPI调用:接收来自鸿蒙的消息
    void ReceiveMessageFromHarmony(const char* message);

    // 供C#端每帧调用:处理消息队列中的消息
    void Update();

private:
    UnityHarmonyBridge();
    ~UnityHarmonyBridge();

    MessageFromHarmonyCallback m_callback;
    std::queue<std::string> m_messageQueue;
    std::mutex m_queueMutex; // 保证多线程下队列操作安全
};

// 为了方便C语言调用而封装的C接口
extern "C" {
    UNITYHARMONYBRIDGE_API void InitializeBridge(MessageFromHarmonyCallback callback);
    UNITYHARMONYBRIDGE_API void SendToHarmony(const char* message);
    UNITYHARMONYBRIDGE_API void UpdateBridge();
}
#endif

关键设计解析

  • 单例模式 :确保整个运行时只有一个通信桥梁实例,避免状态混乱。
  • 消息队列 ( std::queue ) 与互斥锁 ( std::mutex ) :这是实现 异步 线程安全 的关键。 ReceiveMessageFromHarmony 方法可能被鸿蒙侧的任意线程调用,它将消息放入队列并立即返回。 Update 方法由团结引擎主线程每帧调用,从队列中取出消息并执行注册的C#回调。互斥锁保护队列,防止同时读写导致崩溃。
  • C接口 :由于Unity(团结引擎)的插件交互通常通过 [DllImport] 调用C函数,因此需要暴露一组简单的C风格函数接口。

UnityHarmonyBridge.cpp 的核心实现:

#include "UnityHarmonyBridge.h"

UnityHarmonyBridge& UnityHarmonyBridge::GetInstance() {
    static UnityHarmonyBridge instance;
    return instance;
}

void UnityHarmonyBridge::Initialize(MessageFromHarmonyCallback callback) {
    m_callback = callback;
}

void UnityHarmonyBridge::SendMessageToHarmony(const char* message) {
    // 这里实现将消息传递给鸿蒙侧的逻辑。
    // 在实际实现中,这里可能需要调用JNI或NAPI来触发鸿蒙侧的函数。
    // 例如:调用一个Java静态方法。此处为示意,仅打印日志。
    // CallHarmonyJavaMethod(message);
    printf("[C++ Bridge] Message to Harmony: %s\n", message);
    // 实际传输逻辑见下文与鸿蒙的JNI/NAPI连接部分。
}

void UnityHarmonyBridge::ReceiveMessageFromHarmony(const char* message) {
    std::lock_guard<std::mutex> lock(m_queueMutex);
    m_messageQueue.push(std::string(message));
    printf("[C++ Bridge] Received from Harmony and queued: %s\n", message);
}

void UnityHarmonyBridge::Update() {
    std::lock_guard<std::mutex> lock(m_queueMutex);
    while (!m_messageQueue.empty()) {
        std::string msg = m_messageQueue.front();
        m_messageQueue.pop();
        if (m_callback) {
            printf("[C++ Bridge] Dispatching to Unity: %s\n", msg.c_str());
            m_callback(msg.c_str()); // 在主线程执行C#回调!
        }
    }
}

// C接口实现
extern "C" {
    void InitializeBridge(MessageFromHarmonyCallback callback) {
        UnityHarmonyBridge::GetInstance().Initialize(callback);
    }
    void SendToHarmony(const char* message) {
        UnityHarmonyBridge::GetInstance().SendMessageToHarmony(message);
    }
    void UpdateBridge() {
        UnityHarmonyBridge::GetInstance().Update();
    }
}

3.2 编译为原生库

你需要将上述C++代码编译为鸿蒙系统所能加载的动态库。对于鸿蒙(通常基于ARM架构),你需要使用鸿蒙的NDK(Native Development Kit)进行交叉编译。

# 假设使用鸿蒙NDK,这是一个示例性的编译命令
$OHOS_NDK_HOME/build-tools/llvm/bin/clang++ \
    -shared \
    -fPIC \
    -o libUnityHarmonyBridge.so \
    UnityHarmonyBridge.cpp \
    -I./include \
    -std=c++11 \
    -llog # 链接鸿蒙的日志库

编译完成后,你会得到 libUnityHarmonyBridge.so 文件。这个文件需要被放入鸿蒙应用的 libs/arm64-v8a/ (或其他对应ABI)目录下,同时,团结引擎项目也需要在插件设置中引用这个库(或它的一个副本),以便在打包时包含进去。

实操心得一:库的兼容性与放置位置 这是第一个大坑。确保你编译的SO库的ABI(如arm64-v8a, armeabi-v7a)与你的目标鸿蒙设备完全匹配。在鸿蒙应用的 entry/src/main/resources/rawfile/ 目录下放置SO库是一种方式,但更规范的做法是在 build-profile.json5 中配置 nativeLibraryPath ,并在安装应用时让系统自动解压到应用私有目录。在团结引擎侧,你需要将SO库放在 Assets/Plugins/Android (或 HarmonyOS )下的对应ABI子文件夹中,并在Player Settings中确保它被正确包含。

4. 鸿蒙侧接入:建立NAPI或JNI连接

现在,我们需要在鸿蒙应用中创建接口,让ArkTS/JS代码能够调用我们C++桥接层中的函数。鸿蒙推荐使用NAPI(Native API)来实现JS与C/C++的交互,它比传统的JNI更高效、更安全。

4.1 创建NAPI模块

在鸿蒙应用的 cpp 目录下,创建 bridge_module.cpp :

#include "napi/native_api.h"
#include <hilog/log.h>
#include <string>
// 假设我们有一个头文件声明了C++桥接的函数
#include "unity_harmony_bridge_jni.h" // 这个头文件需要暴露ReceiveMessageFromHarmony的C接口

// 声明一个全局引用,指向C++桥接实例中接收消息的函数
extern "C" void NativeReceiveMessageFromHarmony(const char* msg);

static napi_value SendMessageToUnity(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 1. 从JS参数中获取字符串
    size_t strLen;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &strLen);
    char* buffer = new char[strLen + 1];
    napi_get_value_string_utf8(env, args[0], buffer, strLen + 1, &strLen);

    // 2. 调用C++桥接层函数,将消息传递过去
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "BridgeNAPI", "Send to Unity: %{public}s", buffer);
    NativeReceiveMessageFromHarmony(buffer); // 这个函数内部会调用 UnityHarmonyBridge::ReceiveMessageFromHarmony

    // 3. 清理资源
    delete[] buffer;
    napi_value result;
    napi_get_undefined(env, &result);
    return result;
}

// 模块导出定义
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"sendMessageToUnity", nullptr, SendMessageToUnity, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}

// 定义模块
extern "C" __attribute__((visibility("default"))) void NAPI_bridge_module_RegisterModule(void) {
    static napi_module module = {
        .nm_version = 1,
        .nm_flags = 0,
        .nm_filename = nullptr,
        .nm_register_func = Init,
        .nm_modname = "bridgeModule",
        .nm_priv = nullptr,
        .reserved = {0},
    };
    napi_module_register(&module);
}

同时,你需要创建一个头文件 unity_harmony_bridge_jni.h (名称不固定),来声明那个供NAPI调用的C函数:

// unity_harmony_bridge_jni.h
#ifndef UNITY_HARMONY_BRIDGE_JNI_H
#define UNITY_HARMONY_BRIDGE_JNI_H

#ifdef __cplusplus
extern "C" {
#endif

void NativeReceiveMessageFromHarmony(const char* msg);

#ifdef __cplusplus
}
#endif

#endif

并在你的 UnityHarmonyBridge.cpp 中实现这个函数:

// 在UnityHarmonyBridge.cpp末尾添加
extern "C" void NativeReceiveMessageFromHarmony(const char* msg) {
    UnityHarmonyBridge::GetInstance().ReceiveMessageFromHarmony(msg);
}

4.2 在ArkTS中调用NAPI

在鸿蒙应用的ArkTS文件中,你可以这样调用原生模块:

// entry/src/main/ets/utils/BridgeModule.ets
import bridgeModule from 'libbridgeModule.z.so'; // 导入编译后的原生模块,名称和路径需根据实际配置

export class HarmonyToUnityBridge {
  static sendMessage(message: string): void {
    try {
      // 调用NAPI暴露的方法
      bridgeModule.sendMessageToUnity(message);
      console.log(`[Harmony] Message sent to Unity: ${message}`);
    } catch (error) {
      console.error(`[Harmony] Failed to send message to Unity: ${JSON.stringify(error)}`);
    }
  }
}

// 在任意UI或业务逻辑中调用
// 例如,一个按钮的点击事件
Button('发送开始游戏指令')
  .onClick(() => {
    HarmonyToUnityBridge.sendMessage('{"command": "game_start", "level": 1}');
  })

实操心得二:NAPI的注册与加载 确保你的 CMakeLists.txt 正确编译了包含NAPI模块的源文件,并生成了正确的SO库。在 package.json nativeLibrary 字段中声明这个模块。鸿蒙应用在启动时会自动加载这些模块。如果遇到 undefined is not a function 错误,十有八九是模块名不匹配或者SO库没有被打包进应用。

5. 团结引擎侧整合:C#封装与主线程调度

鸿蒙侧的消息已经能到达我们的C++桥接层并进入队列。现在,我们需要在团结引擎(C#侧)初始化这个桥接,并每帧检查队列。

5.1 创建C#桥接管理器

// UnityHarmonyBridgeManager.cs
using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class UnityHarmonyBridgeManager : MonoBehaviour
{
    // 定义与C++库交互的DllImport
    [DllImport("UnityHarmonyBridge")]
    private static extern void InitializeBridge(MessageFromHarmonyDelegate callback);

    [DllImport("UnityHarmonyBridge")]
    private static extern void SendToHarmony(string message);

    [DllImport("UnityHarmonyBridge")]
    private static extern void UpdateBridge();

    // 定义与C++回调匹配的委托
    private delegate void MessageFromHarmonyDelegate(string message);

    // 单例实例
    public static UnityHarmonyBridgeManager Instance { get; private set; }

    // 供外部订阅的消息到达事件
    public event Action<string> OnHarmonyMessageReceived;

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(this.gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(this.gameObject); // 常驻场景,保证通信持续

        // 初始化C++桥接,注册回调函数
        InitializeBridge(OnNativeMessageReceived);
        Debug.Log("[Unity] Unity-Harmony Bridge Initialized.");
    }

    void Update()
    {
        // 每帧调用C++桥接的Update,处理消息队列
        UpdateBridge();
    }

    // 这个函数由C++层在主线程回调
    [AOT.MonoPInvokeCallback(typeof(MessageFromHarmonyDelegate))]
    private static void OnNativeMessageReceived(string message)
    {
        // 因为此回调是从C++经P/Invoke调用,已经处于Unity主线程?
        // 注意:通过[DllImport]注册的回调,其调用线程取决于C++层调用它的线程。
        // 在我们的设计里,C++的UpdateBridge()是在Unity主线程的Update中调用的,
        // 而消息分发(m_callback)发生在UpdateBridge()内部,因此这个回调也必然在主线程。
        // 但为绝对安全,可以用Queue或直接调用Instance的方法。
        if (Instance != null)
        {
            // 使用Unity的主线程调度器确保万无一失(对于某些复杂情况)
            // MainThreadDispatcher.Instance.Enqueue(() => { Instance.HandleMessage(message); });
            Instance.HandleMessage(message);
        }
    }

    private void HandleMessage(string message)
    {
        Debug.Log($"[Unity] Received from Harmony: {message}");
        OnHarmonyMessageReceived?.Invoke(message);
        // 这里可以解析JSON,并分发到具体的游戏管理器
        // ParseAndDispatchMessage(message);
    }

    // 供其他C#脚本调用,向鸿蒙发送消息
    public void SendMessageToHarmony(string message)
    {
        if (string.IsNullOrEmpty(message)) return;
        Debug.Log($"[Unity] Sending to Harmony: {message}");
        SendToHarmony(message);
    }

    void OnDestroy()
    {
        if (Instance == this)
        {
            Instance = null;
        }
    }
}

5.2 在游戏逻辑中消费消息

创建一个游戏内的管理器来响应具体的事件:

// GameCommandHandler.cs
using UnityEngine;
using System; // 使用System.Text.Json或Newtonsoft.Json解析JSON

public class GameCommandHandler : MonoBehaviour
{
    void Start()
    {
        // 订阅桥接管理器的事件
        if (UnityHarmonyBridgeManager.Instance != null)
        {
            UnityHarmonyBridgeManager.Instance.OnHarmonyMessageReceived += ProcessHarmonyMessage;
        }
    }

    void OnDestroy()
    {
        if (UnityHarmonyBridgeManager.Instance != null)
        {
            UnityHarmonyBridgeManager.Instance.OnHarmonyMessageReceived -= ProcessHarmonyMessage;
        }
    }

    private void ProcessHarmonyMessage(string jsonMessage)
    {
        try
        {
            // 示例:使用Unity自带的JsonUtility或第三方库解析
            // 这里假设消息格式为 {"command":"xxx", "data":{}}
            var messageObj = JsonUtility.FromJson<HarmonyMessage>(jsonMessage);
            if (messageObj != null)
            {
                switch (messageObj.command)
                {
                    case "game_start":
                        int level = messageObj.data?.level ?? 1;
                        GameManager.Instance.StartGame(level);
                        break;
                    case "player_move":
                        float x = messageObj.data?.x ?? 0f;
                        float y = messageObj.data?.y ?? 0f;
                        PlayerController.Instance.Move(new Vector2(x, y));
                        break;
                    case "pause_game":
                        GameManager.Instance.PauseGame();
                        break;
                    default:
                        Debug.LogWarning($"[GameCommandHandler] Unknown command: {messageObj.command}");
                        break;
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"[GameCommandHandler] Failed to process message: {jsonMessage}. Error: {e}");
        }
    }

    // 定义一个简单的消息结构体来匹配JSON
    [System.Serializable]
    private class HarmonyMessage
    {
        public string command;
        public MessageData data;
    }

    [System.Serializable]
    private class MessageData
    {
        public int level;
        public float x;
        public float y;
        // ... 其他字段
    }
}

实操心得三:主线程回调的绝对安全 尽管我们的设计理论上保证了回调在主线程,但在复杂的原生插件交互中,线程上下文可能因系统调度变得微妙。我强烈建议在 OnNativeMessageReceived 中不直接处理复杂逻辑,而是将消息字符串放入一个线程安全的队列(如 ConcurrentQueue ),然后在 Update 中从这个队列取出处理。或者使用一个成熟的 MainThreadDispatcher 单例。这能彻底避免“非主线程操作Unity对象”的致命错误。我曾在一次测试中,因为鸿蒙侧通过JNI调用时线程策略配置不当,导致回调不在主线程,引发了难以复现的随机崩溃。加上一层队列缓冲后,问题彻底消失。

6. 完整流程验证与深度调试技巧

现在,让我们串联起整个流程,并分享一些确保它跑通的调试技巧。

通信全链路梳理:

  1. 鸿蒙UI触发 :用户点击鸿蒙应用上的按钮,调用 HarmonyToUnityBridge.sendMessage()
  2. NAPI转发 :ArkTS调用NAPI模块的 sendMessageToUnity 函数。
  3. C++桥接入队 :NAPI函数调用 NativeReceiveMessageFromHarmony ,进而调用 UnityHarmonyBridge::ReceiveMessageFromHarmony ,将消息字符串压入线程安全队列。
  4. 引擎轮询处理 :团结引擎每帧的 Update() 中, UnityHarmonyBridgeManager.Update() 被调用,它调用C++的 UpdateBridge()
  5. C++桥接出队并回调 UpdateBridge() 从队列中取出消息,调用之前注册的C#委托 m_callback
  6. C#事件分发 :C#委托将消息传递给 UnityHarmonyBridgeManager.Instance.HandleMessage() ,进而触发 OnHarmonyMessageReceived 事件。
  7. 游戏逻辑响应 GameCommandHandler 订阅了该事件,解析JSON消息,并执行对应的游戏指令(如开始游戏、移动角色)。

调试技巧与排坑指南:

  1. 日志是生命线 :在C++层、NAPI层、C#层的关键节点都加上详细的日志。使用鸿蒙的 OH_LOG_Print 和Unity的 Debug.Log 。查看鸿蒙的 hilog 输出和Unity的 Logcat Editor Console ,对照时间戳,可以清晰看到消息流到了哪一步卡住。
  2. 验证库加载 :在C#的 Awake 方法中,尝试调用一个简单的 DllImport 函数(如返回一个版本号)。如果调用失败,说明SO库未正确加载或签名有问题。确保SO库在鸿蒙应用的正确目录,并且团结引擎的插件设置无误。
  3. 线程检查 :在C#的 OnNativeMessageReceived 回调开头,使用 Debug.Log(Thread.CurrentThread.ManagedThreadId); Debug.Log(System.Threading.SynchronizationContext.Current); 来验证是否在主线程。如果不是,立即启用之前提到的消息队列缓冲方案。
  4. 数据序列化 :复杂数据(如结构体、数组)传递时,JSON是最稳妥的文本协议。确保双方使用的JSON库对特殊字符(如引号、换行)的转义规则一致。二进制协议(如Protobuf)效率更高,但集成复杂度也增加。
  5. 内存管理 :C/C++层手动分配的内存(如从NAPI中获取字符串的buffer)必须手动释放,否则会造成内存泄漏。使用 std::string 或智能指针可以简化管理。
  6. 鸿蒙权限 :如果你的通信涉及网络(用于跨设备)、传感器等,别忘了在鸿蒙应用的 module.json5 中声明相应的权限。

通过以上步骤,你应该能在团结引擎和鸿蒙系统之间建立起一条稳定、高效的消息通道。这套方案的核心思想—— 通过原生C/C++层作为中介,利用消息队列解耦线程和时序 ——不仅适用于团结引擎与鸿蒙,也适用于其他需要与原生平台深度交互的跨引擎开发场景。关键在于理解每一层的边界和职责,并做好充分的错误处理和日志记录。当看到鸿蒙界面上的一个按钮点击,瞬间触发了游戏世界里的一个爆炸特效时,那种跨越生态的协同感,便是对这番折腾最好的回报。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐