Unity 打包 Windows 后傻眼了:一堆文件加个 exe,这怎么发给客户?

0 阅读5分钟

Unity发布Windows平台后是一堆库资源,以及一个可执行的exe文件,需要制作成安装包,需要再进一步打包。

如果你不想在卸载脚本上折腾——装插件、建 WiX 项目、写 C#、生成 dll、配执行顺序——可以了解一下我们团队最近在用的这款打包工具:Installer Studio。

广4.jpg  

可视化配置,无需编码,非常多的安装包模板,一键构建。安装、卸载、快捷方式、注册表这些常见操作都封装好了,卸载时清理文件这类需求也不用自己写脚本。支持在线/离线安装一键切换,多语言。

下面的内容还是 Advanced Installer 的完整实操,想手写配置的可以继续看;想省掉脚本那一步的,可以去官网看看。

 

一、打开Advanced Installer后选择对应应用程序,新建项目

图3.jpeg  

二、填写信息,分别是应用名称,版本,发布者,下方的控制面板根据需求是否开启(应用名称这边截图中填写的是属性变量对应的属性页面设置的)

图4.jpeg

 

属性页

图5.jpeg

三、导入需要打包的应用程序

图6.jpeg

 

四、设置开始菜单和桌面快捷方式(如有需求,没需求也可以不设置)选中需要创建的快捷方式的文件,右键创建,然后将快捷方式存放至桌面以及开始菜单中

图7.jpeg

图8.jpeg

图9.jpeg

 

五、注册表的设置(如有需要,例如开机自启,其实就是把应用信息写入注册表,以及网页打开应用也是需要把信息写入注册表,网页打开应用后续会单独写一篇文章)

图10.jpeg

 

六、设置安装参数,安装文件夹指的是打开安装包选择安装位置时默认的位置,图片中指向的C盘的路径,下面的安装选项中:静默安装勾选后,就不会再跳出选择框等一些设置,直接会出现进度条进行安装。

因此正常安装包需要勾选,除非你发布的是冷更新的安装包(后续会单独出一遍文章,写冷更新的操作),管理员身份运行,建议勾上,有些操作必须管理员权限才能操作,例如在注册表的Root中写入信息,必须要管理员权限才行,不勾选这种操作就不能正常执行。

图11.jpeg  

七、设置构建信息,导出安装包保存位置,一个是exe的文件名,以及exe的图标

图12.jpeg  

八、运行环境和启动条件(根据需求修改)

图13.jpeg

  九、设置对话框,主题也就是对话框的不同默认的样式,对话框可以全部删除后自定义,主要可能涉及到的就是最后完成安装后,可能需要打开描述文件,或者直接启动应用程序,以及以管理员身份运行等。因此需要设置启动文件

图14.jpeg

 

十、最后设置自定义操作,为什么需要这个呢,是因为安装包,安装后如果需要卸载,他不会把文件夹下涉及到的文件全部卸载掉。因此需要自行编写一段删除文件的脚本。

图15.jpeg  

十一、编写脚本文件流程(注意我这边使用的VS2022,C#编写的,也可以使用其他的,请自行研究)

这时候就需要用到开头提供的另外两个文件,安装完成后,打开VS新建项目(选择 C# Custom Action Project for WiX v3)注意如果那两个文件没安装成功,是不会显示该模块项目的。

图16.jpeg

图17.jpeg

 

十二、在CustomAction1中编写对应的执行事件即可,例如卸载应用后删除指定文件,那么怎么获取指定文件位置,可以通过注册表中的信息获取。注册表中的信息,怎么写入呢毕竟也不是一开始就有的,这时候就有两种写入方式,一种就是上方提到的在安装时设置好,直接写入即可,第二种代码运行后写入。(以下代码仅供参考,具体需要执行什么事件自行编写)

using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace AdvancedInstallerCustomAction
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult CustomAction1(Session session)
        {
            session.Log("Begin CustomAction1");

            string manufacturer = session["Manufacturer"];
            string productName = session["ProductName"];

            using (RegistryKey handlerKey = Registry.CurrentUser.OpenSubKey($"SOFTWARE\{manufacturer}\{productName}"))
            {
                if (handlerKey != null)
                {
                    string value = (string)handlerKey.GetValue("Path");
                    if (!string.IsNullOrEmpty(value))
                    {
                        //var path = Path.Combine(value, productName);
                        RemoveSubDirectory(value, false);
                    }
                }            
            }

            var desktopShutout = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"{productName}.lnk");
            RemoveFile(desktopShutout);

            var startMenuShutout = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), productName);
            RemoveSubDirectory(startMenuShutout);

            return ActionResult.Success;
        }

        private static void RemoveFile(string path)
        {
            FileInfo file = new FileInfo(path);
            try
            {
                if (file.Exists)
                {
                    file.Delete();
                }
            }
            catch
            {
                return;
            }
        }

        private static void RemoveSubDirectory(string path,bool isDeleteRoot=true)
        {
            DirectoryInfo uper = new DirectoryInfo(path);
            try
            {
                if (uper.Exists)
                {
                    foreach (FileInfo subFile in uper.GetFiles())
                    {
                        subFile.Delete();
                    }
                    foreach (DirectoryInfo sub in uper.GetDirectories())
                    {
                        if (sub.GetFiles().Length > 0 || sub.GetDirectories().Length > 0)
                            RemoveSubDirectory(sub);
                    }

                    if (isDeleteRoot) uper.Delete(true);
                }
            }
            catch
            {
                return;
            }

        }

        private static void RemoveSubDirectory(DirectoryInfo uper, bool isDeleteRoot = true)
        {
            try
            {
                if (uper.Exists)
                {
                    foreach (FileInfo subFile in uper.GetFiles())
                    {
                        subFile.Delete();
                    }
                    foreach (DirectoryInfo sub in uper.GetDirectories())
                    {
                        if (sub.GetFiles().Length > 0 || sub.GetDirectories().Length > 0)
                            RemoveSubDirectory(sub);
                    }
                    if (isDeleteRoot) uper.Delete(true);
                }
            }
            catch
            {
                return;
            }
        }
    }
}
```

十三、生成dll,点击生成=》生成解决方案(或者点击重新生成解决方案)生成完后,下面会告诉你dll地址

图18.jpeg  

十四、应用dll文件。点击添加自定义操作,通过DLL,选择刚生成的dll即可,

图19.jpeg

十五、设置自定义操作执行时间和顺序,可以选中后设置,安装时执行,或者卸载时执行,并且可以在左侧调整执行顺序。

图20.jpeg

  十六、构建安装包,回到产品详细页面,点击构建即可。构建完成后就能在你设置的输出位置看到构建好的安装包。

图21.jpeg