点关注不迷路,持续输出Unity
干货文章。
嗨,大家好,我是新发。
我们使用Unity
发布Windows
平台exe
时,生成的是一个exe
和一个Data
文件夹,而我们安装一些应用程序的时候,一般都是一个Setup.exe
安装程序,而且很多Setup.exe
的界面做的挺美观的,比如,我就做了一个,效果如下:
今天,我就来讲讲如何制作美观的Setup.exe
安装程序吧。
在做Setup.exe
安装程序之前,我们先做一个可运行的exe
程序吧。
我就做个Unity Demo
好了,运行效果如下:
发布Windows
平台exe
,生成的文件如下:
制作Setup
安装程序需要的工具链我已上传到CodeChina
,大家直接下载即可使用。
地址:https://codechina.csdn.net/linxinfa/UnitySetupSkinNSIS
下载下来后,解压它,里面的目录结构如下:
进入FilesToInstall
目录,把我们刚刚Unity
发布的Windows
平台生成的文件拷贝到这里,如下
双击执行build_unitychan_setup.bat
脚本,即可生成安装程序。
生成的安装程序在Output
目录中。
你可以在我的这个基础上进行修改,自定义自己的安装程序。
进入SetupScripts\unitychan
目录,打开unitychan_setup.nsi
文件。
定义产品信息,如下:
# ====================== 自定义宏 产品信息==============================
!define PRODUCT_NAME "Unity酱"
!define PRODUCT_PATHNAME "LINXINFA_PC" #安装卸载项用到的KEY
!define INSTALL_APPEND_PATH "linxinfa" #安装路径追加的名称
!define INSTALL_DEFALT_SETUPPATH "" #默认生成的安装路径
!define EXE_NAME "Unity酱.exe"
!define PRODUCT_VERSION "1.0.0.0"
!define PRODUCT_PUBLISHER "林新发"
!define PRODUCT_LEGAL "林新发 Copyright(c)2021"
!define INSTALL_OUTPUT_NAME "Unity酱_v1.0.0.exe"
# ====================== 自定义宏 安装信息==============================
!define INSTALL_7Z_PATH "..\app.7z"
!define INSTALL_7Z_NAME "app.7z"
!define INSTALL_RES_PATH "skin.zip"
!define INSTALL_LICENCE_FILENAME "licence.rtf"
!define INSTALL_ICO "logo.ico"
!define UNINSTALL_ICO "uninst.ico"
进入SetupScripts\unitychan\skin
目录,这里面有很多个xml
,这些xml
就是安装程序各个界面的UI
布局配置,可以理解成Android
工程的界面布局layout
文件。
配置表 | 说明 |
---|---|
install.xml | 安装程序的入口 |
configpage.xml | 打开安装包后显示的第一个界面,也是用于选择安装路径等的界面 |
licensepage.xml | 许可协议显示界面 |
installingpage.xml | 安装过程中的界面 |
finishpage.xml | 安装完成的界面 |
uninstallpage.xml | 卸载入口界面 |
uninstallingpage.xml | 卸载过程中的界面 |
uninstallfinishpage.xml | 卸载完成的界面 |
msgBox.xml | 二级弹窗 |
logo.ico
是安装器的icon
,unist.ico
是卸载器的icon
。
许可证文件源文件是txt
格式,需要转成rtf
格式,可以上这里转换:
https://convertio.co/zh/txt-rtf/
服务条款标题修改:
打开ui_nim_setup.nsh
,在DUIPage
中修改服务条款弹框的标题:
nsNiuniuSkin::SetControlAttribute $hInstallDlg "licensename" "text" "Unity酱 许可协议与服务条款"
效果:
如果需要运行过程中重新设置许可证文件,可以使用下面这个接口:
nsNiuniuSkin::ResetLicenseFile $hInstallDlg "newlicensename.rtf"
设置后,许可协议显示控件将会重新加载许可协议文件,这个比较适合用于多语言版本的不同许可协议加载显示。
安装器的界面UI
图片素材放在SetupScripts/unitychan/skin
目录中,可以新建子目录,比如form
和public
两个子目录。
form
目录中存放的图片素材如下:
如果有特殊的安装功能项,可以到SetupScripts\unitychan
目录下的ui_unitychan_setup.nsh
文件中修改,这里定义了很多函数,是整个安装程序的逻辑实现。
主要函数说明:
方法名 | 说明 |
---|---|
Function DUIPage | 安装入口脚本,用于初始化一些信息 |
un.DUIPage | 卸载入口脚本 |
BindUIControls | 绑定按钮及其他响应事件 |
ShowMsgBox | 显示二级子窗口 |
OnBtnInstall | 安装主流程控制 |
… | … |
我们的Setup
安装程序是基于NSIS
这个工具来制作的,使用了nsNiuniuSkin.dll
这个插件来负责UI
的控制,想要尝试自己制作的同学,得先学习下NSIS
的基础语法。
NSIS
脚本使用Var
关键字定义变量,使用StrCpy
命令为变量赋值。
例:
Var InstallState #是在安装中还是安装完成
StrCpy $InstallState "0" #设置未安装完成状态
在NSIS
脚本中,有20
个预置的变量:
$0,$1,$2,$3,$4,$5,$6,$7,$8,$9,$R0,$R1,$R2,$R3,$R4,$R5,$R6,$R7,$R8,$R9
这些变量和你自己写的变量用法是一样的,但通常用于共享的方法和宏中。这些变量不需要专门去声明,建议使用栈stack
来存放这些变量的值。这些变量也可被用于插件间的通信,因为它们可被插件DLL
文件读写。
例:
GetFunctionAddress $0 OnBtnInstall
另外还有四个变量:
变量 | 说明 |
---|---|
$INSTDIR |
安装目录 |
$OUTDIR |
当前的输出目录 |
$CMDLINE |
进入安装包的命令行 |
$LANGUAGE |
当前使用的语言,可以在.onInit回调中指定语言,如英语(美国)是1033,简体中文是2052 |
NSIS
脚本中有大量系统预定义好的常量可以使用。
常量 | 说明 |
---|---|
$PROGRAMFILES |
在64 位系统中指向C:\Program Files (x86) |
$PROGRAMFILES32 |
指向C:\Program Files (x86) |
$PROGRAMFILES64 |
在64 位系统中指向C:\Program Files |
$DESKTOP |
Windows 桌面地址 |
$EXEDIR |
安装包所在的目录 |
$EXEFILE |
安装程序文件名 |
$EXEPATH |
$EXEDIR 和$EXEFILE 拼合到一起的安装文件全路径 |
${NSISDIR} |
NSIS程序的安装目录地址,如D:\NSIS |
$WINDIR |
Windows 目录地址,如C:\Windows |
$SYSDIR |
Windows 下system 目录地址,如C:\Windows\System32 |
$TEMP |
系统临时目录地址,如C:\Users\linxinfa\AppData\Local\Temp |
$STARTMENU |
开始菜单地址,如C:\Users\linxinfa\AppData\Roaming\Microsoft\Windows\Start Menu |
$SMPROGRAMS |
开始菜单下Programs地址,如C:\Users\linxinfa\AppData\Roaming\Microsoft\Windows\Start Menu\Programs |
$QUICKLAUNCH |
快速启动栏,如C:\Users\linxinfa\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch |
$DOCUMENTS |
“我的文档” 目录地址,如C:\Users\linxinfa\Documents |
$FONTS |
“字体” 目录地址,如C:\Windows\Fonts |
$$ |
转义,用来表示 $ |
$\r |
用来表示一个回车(\r) |
$\n |
用来表示新的一行(\n) |
$\t |
用来表示一个 Tab(\t) |
… | … |
函数定义
Function ShowMsgBox
# 函数体
FunctionEnd
函数调用
Call ShowMsgBox
带返回值的函数示例:
Function SimpleTest
Push "OK"
FunctionEnd
调用:
Call SimpleTest
Pop $0
${If} $0 == "OK"
MessageBox MB_OK|MB_ICONEXCLAMATION "函数返回了OK"
${EndIf}
例:
#安装界面点击退出,给出提示
Function OnExitDUISetup
${If} $InstallState == "0"
StrCpy $R8 "安装尚未完成,您确定退出安装么?"
StrCpy $R7 "1"
Call ShowMsgBox
pop $0
${If} $0 == 0
goto endfun
${EndIf}
${EndIf}
nsNiuniuSkin::ExitDUISetup
endfun:
FunctionEnd
例:
!include "StrFunc.nsh"
!include "LogicLib.nsh"
!include "..\commonfunc.nsh"
例:
!define INSTALL_PAGE_CONFIG 0
!define INSTALL_PAGE_PROCESSING 1
!define INSTALL_PAGE_FINISH 2
以 ;
或#
开始的行为注释行。可以在命令后面添加注释,也可以使用C
规范的注释来注释一行或多行。
例:
; 注释
# 注释
/*
注释
注释
*/
如果参数需要由;
或#
开头,可以用双引号把它括起来。
布局分水平布局和垂直布局,如下,这个界面最外层是一个垂直布局。
对应成xml
:
<VerticalLayout>
VerticalLayout>
<HorizontalLayout>
HorizontalLayout>
界面布局中,我们有时候需要做一些留空,我们可以使用Control
来实现留空,有点类似html
中的div
。比如这里留了25
个像素的空行,
对应成xml
:
<Control height="25" />
<VerticalLayout width="480"
height="250"
roundcorner="5,5"
bkimage="file='form\pic.png'">
注意这里图片路径是相对于skin
目录的。
<Label font="5"
textcolor="#FF333333"
text="安装路径:"
padding="40,0,30,0" />
UI
在布局中,可以设置相对于父控件的边距,顺序是:左、上、右、下。
比如这个,距离左边边距40
个像素,其他以此类推:
对应成xml
:
<Label font="5"
textcolor="#FF333333"
text="安装路径:"
padding="40,0,30,0" />
<Button name="btnInstall"
padding="95,10,95,30"
height="40"
normalimage="form\btn_installation_normal.png"
hotimage="form\btn_installation_hovered.png"
pushedimage="form\btn_installation_pressed.png"
disabledimage="form\btn_installation_disable.png"
font="6"
textcolor="0xffffffff"
disabledtextcolor="0xffffffff"
margin="0,10,0,0"
text="一键安装" />
这种方式的出图是出成多张,也可以像这样出在一张图上:
不过需要通过source
字段来指明坐标和尺寸,
比如source='0,26,29,52'
表示坐标(0,26)
,大小(29,52)
。
对应成xml
:
<Button name="btnClose" width="29" height="29"
normalimage="file='form\close1.png' source='0,0,29,26'"
hotimage="file='form\close1.png' source='0,26,29,52'"
pushedimage="file='form\close1.png' source='0,52,29,78'" />
比如一键安装按钮,在xml
给按钮取名字叫"btnInstall"
。
<Button name="btnInstall"
padding="95,10,95,30"
height="40"
normalimage="form\btn_installation_normal.png"
hotimage="form\btn_installation_hovered.png"
pushedimage="form\btn_installation_pressed.png"
disabledimage="form\btn_installation_disable.png"
font="6"
textcolor="0xffffffff"
disabledtextcolor="0xffffffff"
margin="0,10,0,0"
text="一键安装" />
在ui_unitychan_setpu.nsh
中通过名字来设置按钮的点击函数:
GetFunctionAddress $0 OnBtnInstall
nsNiuniuSkin::BindCallBack $hInstallDlg "btnInstall" $0
# 开始安装
Function OnBtnInstall
# ...
FunctionEnd
<RichEdit name="editDir"
text=""
textcolor="0xFF000000"
inset="5,8,2,2"
bkimage="public\edit\edit0.png"
autohscroll="false"
bordervisible="true"
bordersize="1"
bordercolor="0xFFD1D1D1"
focusbordercolor="0xFFD1D1D1"
wantreturn="false"
wantctrlreturn="false"
multiline="false"
width="360" />
在ui_unitychan_setpu.nsh
中设置默认安装路径:
nsNiuniuSkin::SetControlAttribute $hInstallDlg "editDir" "text" "$INSTDIR\"
获取路径输入框中的文本:
nsNiuniuSkin::GetControlAttribute $hInstallDlg "editDir" "text"
Pop $0
StrCpy $INSTDIR "$0"
#根据选中的情况来控制按钮是否灰度显示
Function OnCheckLicenseClick
nsNiuniuSkin::GetControlAttribute $hInstallDlg "chkAgree" "selected"
Pop $0
${If} $0 == "0"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnInstall" "enabled" "true"
${Else}
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnInstall" "enabled" "false"
${EndIf}
FunctionEnd
<Slider name="slrProgress"
padding="30,0,30,0"
height="3"
mouse="false"
foreimage="form\fg.png"
bkimage="form\bg.png"
thumbsize="0,0"
bkcolor="#FFD8D8D8" />
在ui_unitychan_setpu.nsh
中更新进度条:
nsNiuniuSkin::SetControlAttribute $hInstallDlg "slrProgress" "value" "$0"
安装程序会有多个页面,比如安装向导界面、安装中界面、安装完成界面等,每个界面有对应的ID
我们需要在逻辑中做界面切换。
在install.xml
中会先配置好这些界面的xml
,如下:
<TabLayout name="wizardTab" >
<Include source="configpage.xml" />
<Include source="installingpage.xml" />
<Include source="finishpage.xml" />
<Include source="uninstallpage.xml" />
<Include source="uninstallingpage.xml" />
<Include source="uninstallfinishpage.xml" />
TabLayout>
在代码中,定义好它们的ID
:
!define INSTALL_PAGE_CONFIG 0
!define INSTALL_PAGE_PROCESSING 1
!define INSTALL_PAGE_FINISH 2
!define INSTALL_PAGE_UNISTCONFIG 3
!define INSTALL_PAGE_UNISTPROCESSING 4
!define INSTALL_PAGE_UNISTFINISH 5
然后通过接口进行切换界面:
nsNiuniuSkin::ShowPageItem $hInstallDlg "wizardTab" ${INSTALL_PAGE_PROCESSING}
用户可以选择自定义安装,选择安装的路径,我们可以调用nsNiuniuSkin::SetControlAttribute
这个接口弹出路径选择窗口,
Function OnBtnSelectDir
nsNiuniuSkin::SelectInstallDirEx $hInstallDlg "请选择安装路径"
Pop $0
# 如果选择路径不为空,则赋值到editDir这个编辑框中,注意Unless的含义,它等价于if的否
${Unless} "$0" == ""
nsNiuniuSkin::SetControlAttribute $hInstallDlg "editDir" "text" $0
${EndUnless}
FunctionEnd
比如关闭安装程序的时候弹出提示框。
对应的布局xml
文件是msgBox.xml
。
接口:
Function ShowMsgBox
nsNiuniuSkin::InitSkinSubPage "msgBox.xml" "btnOK" "btnCancel,btnClose" ; "提示" "${PRODUCT_NAME} 正在运行,请退出后重试!" 0
Pop $hInstallSubDlg
nsNiuniuSkin::SetControlAttribute $hInstallSubDlg "lblTitle" "text" "提示"
nsNiuniuSkin::SetControlAttribute $hInstallSubDlg "lblMsg" "text" "$R8"
${If} "$R7" == "1"
nsNiuniuSkin::SetControlAttribute $hInstallSubDlg "btnCancel" "visible" "true"
${EndIf}
nsNiuniuSkin::ShowSkinSubPage 0
FunctionEnd
$R8
变量存放显示的文本,$R7
变量控制是否显示取消按钮。
调用示例:
#安装界面点击退出,给出提示
Function OnExitDUISetup
${If} $InstallState == "0"
StrCpy $R8 "安装尚未完成,您确定退出安装么?"
StrCpy $R7 "1"
Call ShowMsgBox
pop $0
${If} $0 == 0
goto endfun
${EndIf}
${EndIf}
nsNiuniuSkin::ExitDUISetup
endfun:
FunctionEnd
每个UI
控件都有各自的属性,比如visible
、pos
、height
等。
安装程序运行中,我们需要根据情况动态修改UI
控件的属性,比如这个:
点击下拉和收起按钮,我们需要对应得调整窗口。
代码示例:
# 展开
Function OnBtnShowMore
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "enabled" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "enabled" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "moreconfiginfo" "visible" "true"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "visible" "true"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "visible" "false"
# 调整窗口高度
GetFunctionAddress $0 StepHeightSizeAsc
BgWorker::CallAndWait
nsNiuniuSkin::SetWindowSize $hInstallDlg 480 500
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "enabled" "true"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "enabled" "true"
FunctionEnd
# 收起
Function OnBtnHideMore
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "enabled" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "enabled" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "moreconfiginfo" "visible" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "visible" "false"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "visible" "true"
# 调整窗口高度
GetFunctionAddress $0 StepHeightSizeDsc
BgWorker::CallAndWait
nsNiuniuSkin::SetWindowSize $hInstallDlg 480 390
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnShowMore" "enabled" "true"
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnHideMore" "enabled" "true"
FunctionEnd
又如禁用和激活UI
,参见上面第9条。
UI
控件常用的属性,可以查阅这个表:
属性名 | 数据类型 | 默认值 | 描述 |
---|---|---|---|
pos | RECT | 0,0,0,0 | 位置 |
padding | RECT | 0,0,0,0 | 内边距 |
bkcolor | DWORD | 0x00000000 | 背景颜色 |
bordercolor | DWORD | 0x00000000 | 边框颜色 |
focusbordercolor | DWORD | 0x00000000 | 获得焦点时边框的颜色 |
bordersize | INT或RECT | 0 | 边框大小,可以用INT也可以用RECT |
leftbordersize | INT | 0 | 左边边框大小 |
topbordersize | INT | 0 | 顶部边框大小 |
rightbordersize | INT | 0 | 右边边框大小 |
bottombordersize | INT | 0 | 底部边框大小 |
borderstyle | INT | 0 | 边框样式,数值范围0~5 |
borderround | SIZE | 0,0 | 边框圆角半径,如(2,2) |
bkimage | STRING | “” | 背景图片 |
width | INT | 0 | 宽度 |
height | INT | 0 | 高度 |
minwidth | INT | 0 | 最小宽度 |
minheight | INT | 0 | 最小高度 |
maxwidth | INT | 0 | 最大宽度 |
maxheight | INT | 0 | 最大高度 |
text | STRING | “” | 显示的文本 |
tooltip | STRING | “” | 鼠标悬停提示文本 |
enabled | BOOL | true | 是否响应用户操作 |
mouse | BOOL | true | 是否响应鼠标操作 |
visible | BOOL | true | 是否可见 |
float | BOOL | false | 是否使用绝对定位 |
… |
制作安装程序时通常都会被要求支持多语言。NSIS
对于多语言的支持非常的方便。
首先在ui_unitychan_setup.nsh
文件中添加需要支持的语言的宏:
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_LANGUAGE "English"
在添加完宏之后,在unitychan_setup.nsi
中添加中英文的字符串,并在.onInit
中调用打开多语言选择框:
Function .onInit
# 打开多语言选择框
!insertmacro MUI_LANGDLL_DISPLAY
FunctionEnd
# 多语言文字定义 ###########################################################################
LangString I18_LICENSES_SERVICE ${
LANG_ENGLISH} ""
LangString I18_LICENSES_SERVICE ${
LANG_SIMPCHINESE} "《软件许可与服务条款》"
LangString I18_I_AGREE ${
LANG_ENGLISH} "I Agree"
LangString I18_I_AGREE ${
LANG_SIMPCHINESE} "我已经阅读并认可"
LangString I18_QUIT_TIPS ${
LANG_ENGLISH} "The installation is not complete, $\nare you sure to exit the installation?"
LangString I18_QUIT_TIPS ${
LANG_SIMPCHINESE} "安装尚未完成,您确定退出安装么?"
LangString I18_INSTALL_PATH ${
LANG_ENGLISH} "Install Path:"
LangString I18_INSTALL_PATH ${
LANG_SIMPCHINESE} "安装路径:"
# ...
回到ui_unitychan_setup.nsh
中,在DUIPage
函数中调用I18Language
函数,在I18Language
函数中设置文本的语言文字。
Function DUIPage
# ...
# 多语言
Call I18Language
nsNiuniuSkin::ShowPage 0
FunctionEnd
Function I18Language
nsNiuniuSkin::SetControlAttribute $hInstallDlg "btnAgreement" "text" $(I18_LICENSES_SERVICE)
nsNiuniuSkin::SetControlAttribute $hInstallDlg "chkAgree" "text" $(I18_I_AGREE)
nsNiuniuSkin::SetControlAttribute $hInstallDlg "installPathLbl" "text" $(I18_INSTALL_PATH)
# ...
FunctionEnd
然后像提示框这种,需要在对应的调用点传入I18
变量。
#安装界面点击退出,给出提示
Function OnExitDUISetup
${
If} $InstallState == "0"
StrCpy $R8 $(I18_QUIT_TIPS)
StrCpy $R7 "1"
Call ShowMsgBox
pop $0
${
If} $0 == 0
goto endfun
${
EndIf}
${
EndIf}
nsNiuniuSkin::ExitDUISetup
endfun:
FunctionEnd
为了演示,所以我没有全部翻译,大家看看效果,按照这个步骤把剩余的其他的也翻译了即可。
先写这么多吧,有空了再继续补充,感兴趣的同学可自行下载Demo
学习。