Unity打包运行于鸿蒙手机

软件版本

Tuanjie Editor 1.9.2

DevEco Studio 6.1.0 Release

第一、打包运行步骤

Build Settings

Install with Tuanjie Hub

安装鸿蒙开发包OpenHarmony

Switch Platform 切换到 OpenHarmony

勾选 Export Project

导出工程 Export

用鸿蒙IDE打开工程

用DevEcoStudio打开刚刚导出的工程

自动设置相同的sdk版本

在DevEcoStudio中运行程序

可能需要配置一下签名

文件 -> 项目结构 -> 项目 -> 签名配置

勾选自动生成签名文件

第二、完成自动打包运行于手机遇到的问题

1.每次都提示"当前项目没有配置targetSdkVersion,请配置以确保应用兼容性。"

Project Settings -> Other Settings -> Identfication -> Compatible API Level (6.1.0(23))

仍然提示

build-profile.json5 文件中缺少targetSdkVersion字段,因为添加的鸿蒙打包模块没有适配

可以在Unity编辑器打包后调用接口IPostprocessBuildWithReport的OnPostprocessBuild方法中向build-profile.json5中写入 "targetSdkVersion": "6.1.0(23)", 字段

2.每次都要设置签名密码

设置包名【注意:包名的大小写

Project Settings -> Player -> 鸿蒙选卡 -> Other Settings -> Bundle Identifier

创建Key

Project Settings -> Player -> 鸿蒙选卡 -> Publishing Settings -> Keystore Manager创建一个新的key也就是.csr文件

AppGallery Connect

AppGallery Connect

在"证书 APP ID Profile"选卡

申请 证书 .cer文件

申请 Profile .p7b文件

获取手机的udid

hdc shell bm get --udid

步骤

1.创建项目(位置:开发与服务)

2.创建一个APP(位置:证书、APP ID和Profile > APP ID)

绑定到项目上

3.申请证书(位置:证书、APP ID和Profile > 证书)

证书请求文件(CSR):本机生成的 .csr文件

生成.cer文件

4.生成Profile(位置:证书、APP ID和Profile > Profile)

1)选择应用

2)选择证书

3)调试情况下 选择设备

生成.p7b文件

5.如果指定设备(位置:证书、APP ID和Profile > 设备)

获取手机的udid

hdc shell bm get --udid

  • Unity 的安全策略 :Unity 在导出鸿蒙工程时,为了避免密码以明文形式硬编码在项目文件中,有意将 storePasswordkeyPassword 这两个字段留空,不会自动带出。这主要是出于安全考虑,防止密码泄露。

可以在Unity编辑器打包后调用接口IPostprocessBuildWithReport的OnPostprocessBuild方法中向build-profile.json5中写入 "storePassword"和"keyPassword", 字段 密码是经过密钥加密是固定的

3.每次都要打开DevEco studio编译

用hvigorw.bat实现自动打包

build_ohos_app.bat

复制代码
@echo off
chcp 65001 >nul

set DEVECO_SDK_HOME=D:\Program Files\Huawei\DevEco Studio\sdk
set OHPM_HOME=D:\Program Files\Huawei\DevEco Studio\tools\ohpm
set NODE_HOME=D:\Program Files\Huawei\DevEco Studio\tools\node
set PATH=%OHPM_HOME%\bin;%NODE_HOME%\bin;%PATH%

cd /d D:\Project\Unity\AllInOne\Harmony

echo ========================================
echo 1. 清理node_modules
echo ========================================
if exist "node_modules" (
    rmdir /s /q node_modules
)
if exist "entry\node_modules" (
    rmdir /s /q entry\node_modules
)

echo 当前路径是:%cd%

echo ========================================
echo 2. 执行ohpm install
echo ========================================
call ohpm install

echo 当前路径是:%cd%

echo ========================================
echo 3. 执行hvigor打包
echo ========================================
set hvigorw="D:/Program Files/Huawei/DevEco Studio/tools/hvigor/bin/hvigorw.bat"
call %hvigorw% clean
call %hvigorw% assembleApp --mode project -p product=default

用hdc实现安装到手机和在手机上打开

install_hap.bat

复制代码
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion

:: ========== 配置区(已适配你的项目) ==========
set "ROOT=%~dp0"
set "HAP_DIR=%ROOT%entry\build\default\outputs\default"
set "HAP_FILE=entry-default-signed.hap"
set "HAP_FULL=%HAP_DIR%\%HAP_FILE%"
set "PACKAGE_NAME=com.scw.allinone"
:: 关键:替换为 module.json5 里的 ability name
set "MAIN_ABILITY=TuanjiePlayerAbility"
:: ==============================================

echo ==============================================
echo         HDC 安装HAP + 鸿蒙自动启动APP
echo ==============================================
echo HAP路径:%HAP_FULL%
echo 应用包名:%PACKAGE_NAME%
echo 主Ability:%MAIN_ABILITY%
echo.

:: 校验安装包是否存在
if not exist "%HAP_FULL%" (
    echo [错误] 未找到 hap 安装包,请先编译项目!
    pause
    exit /b 1
)

:: 查看连接设备
echo 1. 已连接设备:
hdc list targets
echo.

:: 先卸载旧版本(避免签名冲突)
echo 2. 卸载旧应用 %PACKAGE_NAME%
hdc uninstall %PACKAGE_NAME%
echo.

:: 安装新包
echo 3. 开始安装应用...
hdc install "%HAP_FULL%"
set INSTALL_RET=%errorlevel%

if %INSTALL_RET% neq 0 (
    echo.
    echo [失败] 安装出错,请检查上方日志!
    pause
    exit /b %INSTALL_RET%
)

echo.
echo [成功] 应用安装完成,正在启动APP
echo.

:: 鸿蒙 aa start 正确命令 -a 填你的Ability名称
hdc shell aa start -a %MAIN_ABILITY% -b %PACKAGE_NAME%

echo.
echo ==============================================
echo 全部操作执行完毕
echo ==============================================

endlocal

第三、终极一键运行于手机指南

只是本地测试不需要到官网申请证书,用本地生成的证书就可以。

第一步 打包项目

Unity打包出一个鸿蒙项目

第二步 获取证书

用DevEcoStudio运行一次,通过下面方式获取签名证书

文件 -> 项目结构 -> 项目 -> 签名配置

勾选自动生成签名文件

证书路径C:\Users\用户名\.ohos\config

第三步 记录变量

打开项目路径下build-profile.json5文件

保存以下几个字段的内容,这些是Unity打包不会自动写入的内容

"storePassword"

"keyPassword"

"keyAlias"

额外要写入的

"targetSdkVersion": "6.1.0(23)",

第四步 整理资源

在Unity项目中创建两个文件夹

一个用于存放证书("签名证书")

复制C:\Users\用户名\.ohos\config中的内容,避免其他项目导致覆盖

一个用于存放自动打包和运行的.bat文件("打包运行")

上面写过的两个文件

build_ohos_app.bat

install_hap.bat

第五步 Unity设置

Unity中Player Settings -> Publshing Settings

设置

Store file

Profile file

Certificate file

第六步 代码

代码功能

1.工具栏中可以勾选是否导出项目后直接打包运行

2.重写build-profile.json5文件内容,包括以下字段

"storePassword"

"keyPassword"

"keyAlias"

"targetSdkVersion": "6.1.0(23)",

3.复制build_ohos_app.bat和install_hap.bat到项目中

4.运行build_ohos_app.bat和install_hap.bat

复制代码
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using UnityEditor;

public class HarmonyBuildProcessor : IPostprocessBuildWithReport
{
    public int callbackOrder => 0;

    // 定义要写入的密码密文
    private const string STORE_PASSWORD = "0000001A6472324C71000390BADF1A442534003741E5B4C470062D21A4DACEA13AA94AE04BF314E7E9C1";
    private const string KEY_PASSWORD = "0000001A5FB17BB3DD908CF7FCB94CBDAF3E0591BFF6ADB8901A487B75F1E65CBF0E32587D4C5B3C74CC";
    // 新增:keyAlias 全局变量
    private const string KEY_ALIAS = "debugKey";

    // 静态变量保存勾选状态
    public static bool EnableAutoBuildAndInstall = true;

    public void OnPostprocessBuild(BuildReport report)
    {
        Debug.Log($"导出鸿蒙工程");
        string outputFilePath = report.summary.outputPath;
        Debug.Log($"📄 输出文件夹路径:{outputFilePath}");
        
        string targetBuildProfilePath = string.Empty;
        
        if (Directory.Exists(outputFilePath))
        {
            var files = Directory.GetFiles(outputFilePath, "build-profile.json5", SearchOption.AllDirectories);
            if (files.Length > 0)
            {
                targetBuildProfilePath = files[0];
                Debug.Log($"✅ 找到鸿蒙工程:{outputFilePath}");
                Debug.Log($"📄 配置文件:{files[0]}");
                ModifyBuildProfile(targetBuildProfilePath);
                
                // 根据勾选状态决定是否执行批处理
                if (EnableAutoBuildAndInstall)
                {
                    Debug.Log($"✅ 自动构建和安装已启用,执行批处理脚本");
                    RunBatchScriptAfterModifyProfile(outputFilePath, outputFilePath);
                }
                else
                {
                    Debug.Log($"⏸️ 自动构建和安装已禁用,跳过批处理脚本执行");
                }
            }
        }
        else
        {
            Debug.LogWarning($"❌ 未找到鸿蒙工程");
        }
    }

    private void ModifyBuildProfile(string filePath)
    {
        try
        {
            string content = File.ReadAllText(filePath);
            bool modified = false;

            if (!content.Contains("\"targetSdkVersion\""))
            {
                string pattern = @"(""products""\s*:\s*\[\s*\{)";
                string replacement = "$1\n        \"targetSdkVersion\": \"6.1.0(23)\",";
                string newContent = Regex.Replace(content, pattern, replacement, RegexOptions.Singleline);

                if (content != newContent)
                {
                    content = newContent;
                    modified = true;
                    Debug.Log($"✅ 已添加 targetSdkVersion");
                }
                else
                {
                    int idx = content.IndexOf("\"products\"");
                    if (idx != -1)
                    {
                        int braceIdx = content.IndexOf("{", idx);
                        if (braceIdx != -1)
                        {
                            content = content.Insert(braceIdx + 1,
                                "\n        \"targetSdkVersion\": \"6.1.0(23)\",");
                            modified = true;
                            Debug.Log($"✅ 备用方案添加 targetSdkVersion 成功");
                        }
                    }
                }
            }
            else
            {
                Debug.Log($"ℹ️ targetSdkVersion 已存在,跳过");
            }

            // ===================== 新增 keyAlias 替换逻辑 =====================
            string aliasPattern = @"""keyAlias""\s*:\s*""[^""]*""";
            string aliasReplacement = $"\"keyAlias\": \"{KEY_ALIAS}\"";
            if (Regex.IsMatch(content, aliasPattern))
            {
                string newContent = Regex.Replace(content, aliasPattern, aliasReplacement);
                if (content != newContent)
                {
                    content = newContent;
                    modified = true;
                    Debug.Log($"✅ 已更新 keyAlias 为 {KEY_ALIAS}");
                }
            }
            else
            {
                Debug.LogWarning($"⚠️ 未找到 keyAlias 字段,跳过");
            }
            // ==================================================================

            string storePattern = @"""storePassword""\s*:\s*""[^""]*""";
            string storeReplacement = $"\"storePassword\": \"{STORE_PASSWORD}\"";
            if (Regex.IsMatch(content, storePattern))
            {
                string newContent = Regex.Replace(content, storePattern, storeReplacement);
                if (content != newContent)
                {
                    content = newContent;
                    modified = true;
                    Debug.Log($"✅ 已更新 storePassword");
                }
            }
            else
            {
                Debug.LogWarning($"⚠️ 未找到 storePassword 字段,跳过");
            }

            string keyPattern = @"""keyPassword""\s*:\s*""[^""]*""";
            string keyReplacement = $"\"keyPassword\": \"{KEY_PASSWORD}\"";
            if (Regex.IsMatch(content, keyPattern))
            {
                string newContent = Regex.Replace(content, keyPattern, keyReplacement);
                if (content != newContent)
                {
                    content = newContent;
                    modified = true;
                    Debug.Log($"✅ 已更新 keyPassword");
                }
            }
            else
            {
                Debug.LogWarning($"⚠️ 未找到 keyPassword 字段,跳过");
            }

            if (modified)
            {
                File.SetAttributes(filePath, FileAttributes.Normal);
                File.WriteAllText(filePath, content);
                Debug.Log($"✅ 修改已保存到:{filePath}");
            }
            else
            {
                Debug.Log($"ℹ️ 无需修改:{filePath}");
            }
        }
        catch (System.Exception e)
        {
            Debug.LogError($"❌ 修改失败:{filePath}\n{e.Message}");
        }
    }

    /// <summary>
    /// 独立批处理流程
    /// batCopyRoot = Harmony文件夹 D:/Project/Unity/AllInOne/Harmony(bat复制存放目标)
    /// bat源目录 = Harmony上级目录 D:/Project/Unity/AllInOne/打包运行
    /// </summary>
    private void RunBatchScriptAfterModifyProfile(string batCopyRoot, string harmonyProjectRoot)
    {
        try
        {
            // 【核心修改】源文件夹在 Harmony 的上级目录
            string parentDir = Directory.GetParent(batCopyRoot).FullName;
            string batSourceFolder = Path.Combine(parentDir, "打包运行");

            // 目标bat存放在 Harmony 根目录
            string batBuildDst = Path.Combine(batCopyRoot, "build_ohos_app.bat");
            string batInstallDst = Path.Combine(batCopyRoot, "install_hap.bat");

            string batBuildSrc = Path.Combine(batSourceFolder, "build_ohos_app.bat");
            string batInstallSrc = Path.Combine(batSourceFolder, "install_hap.bat");

            Debug.Log($"📁 bat脚本存放目标目录(Harmony):{batCopyRoot}");
            Debug.Log($"📁 bat脚本源文件夹(上级目录):{batSourceFolder}");
            System.Diagnostics.Debug.Flush();

            if (!Directory.Exists(batSourceFolder))
            {
                Debug.LogError($"❌ 未找到【打包运行】文件夹:{batSourceFolder}");
                return;
            }
            if (!File.Exists(batBuildSrc) || !File.Exists(batInstallSrc))
            {
                Debug.LogError($"❌ 缺少bat脚本:{batBuildSrc} / {batInstallSrc}");
                return;
            }

            // 复制bat到 Harmony 根目录
            File.Copy(batBuildSrc, batBuildDst, true);
            File.Copy(batInstallSrc, batInstallDst, true);
            Debug.Log($"✅ bat脚本已复制至:{batCopyRoot}");

            // 执行打包bat,工作目录设为Harmony根目录
            Debug.Log($"▶️ 执行打包脚本:{batBuildDst}");
            RunBatFile(batBuildDst, batCopyRoot);
            Debug.Log($"✅ build_ohos_app.bat 执行完毕");
            System.Diagnostics.Debug.Flush();

            // 等待签名hap生成
            string hapCheckPath = Path.Combine(harmonyProjectRoot, @"entry\build\default\outputs\default\entry-default-signed.hap");
            Debug.Log($"⏳ 监控hap文件:{hapCheckPath}");

            int waitMaxSecond = 20;
            int waited = 0;
            bool hapExist = false;
            while (waited < waitMaxSecond)
            {
                if (File.Exists(hapCheckPath))
                {
                    hapExist = true;
                    break;
                }
                Thread.Sleep(1000);
                waited++;
            }

            if (!hapExist)
            {
                Debug.LogError($"❌ 超时{waitMaxSecond}秒未生成 entry-default-signed.hap");
                return;
            }
            Debug.Log($"✅ 检测到hap文件,开始安装");

            // 执行安装bat
            Debug.Log($"▶️ 执行安装脚本:{batInstallDst}");
            RunBatFile(batInstallDst, batCopyRoot);
            Debug.Log($"✅ install_hap.bat 执行完成,全流程结束");
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"❌ 批处理流程异常:{ex.Message}\n{ex.StackTrace}");
        }
    }

    private void RunBatFile(string batPath, string workDir)
    {
        System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo()
        {
            FileName = batPath,
            WorkingDirectory = workDir,
            UseShellExecute = true,
            CreateNoWindow = false
        };
        using (System.Diagnostics.Process proc = System.Diagnostics.Process.Start(startInfo))
        {
            proc.WaitForExit();
            int exitCode = proc.ExitCode;
            if (exitCode != 0)
            {
                Debug.LogWarning($"⚠️ 脚本异常退出,ExitCode:{exitCode} 脚本:{batPath}");
            }
        }
    }
}

// 在Unity工具栏中添加勾选选项
[InitializeOnLoad]
public static class HarmonyBuildMenu
{
    static HarmonyBuildMenu()
    {
        // 从EditorPrefs中读取保存的状态
        HarmonyBuildProcessor.EnableAutoBuildAndInstall = EditorPrefs.GetBool("HarmonyAutoBuildAndInstall", true);
    }

    [MenuItem("Harmony/自动构建并安装HAP")]
    public static void ToggleAutoBuildAndInstall()
    {
        HarmonyBuildProcessor.EnableAutoBuildAndInstall = !HarmonyBuildProcessor.EnableAutoBuildAndInstall;
        EditorPrefs.SetBool("HarmonyAutoBuildAndInstall", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
        
        // 更新菜单项的勾选状态
        Menu.SetChecked("Harmony/自动构建并安装HAP", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
        
        Debug.Log($"🔄 自动构建并安装HAP: {(HarmonyBuildProcessor.EnableAutoBuildAndInstall ? "启用" : "禁用")}");
    }

    [MenuItem("Harmony/自动构建并安装HAP", true)]
    public static bool ValidateToggleAutoBuildAndInstall()
    {
        // 设置菜单项的勾选状态
        Menu.SetChecked("Harmony/自动构建并安装HAP", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
        return true;
    }
}

第七步 注意

遇到过失败的情况

1.包名大小写不对

2.证书和密码不匹配

3.build-profile.json5配置有遗漏

相关推荐
●VON1 小时前
鸿蒙 PC Markdown 编辑器离线专业渲染管线
华为·编辑器·harmonyos·鸿蒙
Helen_cai1 小时前
OpenHarmony 项目统一全局样式、尺寸、色彩主题封装 ThemeUtil(API23)
开发语言·前端·javascript·华为·harmonyos
玖玥拾1 小时前
Unity 3D 笔记(十一)UI 框架进阶:栈弹窗交互、BasePanel 基类虚方法、DoTween 界面动画
笔记·3d·unity
youtootech2 小时前
HarmonyOS《柚兔学伴》项目实战12-Lottie 动画集成
华为·harmonyos
l134062082352 小时前
HarmonyOS应用开发实战:小事记 - 多媒体文件上传:@ohos.net.http 的 multipart/form-data 请求构造
后端·华为·harmonyos·鸿蒙系统
youtootech2 小时前
HarmonyOS《柚兔学伴》项目实战16-录音功能与权限管理
华为·harmonyos
xd1855785552 小时前
睡眠质量评估 —— 鸿蒙AI智能助手开发全流程解析
人工智能·华为·harmonyos·鸿蒙
wulechun2 小时前
Firebook-Library开源项目深度解析:从环境搭建到分布式架构设计的实战应用指南
智能手机
Jcc2 小时前
鸿蒙 Navigation 模块化实践
harmonyos