點關注不迷路,持續輸出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>
再比如,這裡是水平佈局
對應成xml
:
<HorizontalLayout>
<!-- 水平佈局 -->
</HorizontalLayout>
介面佈局中,我們有時候需要做一些留空,我們可以使用Control
來實現留空,有點類似html
中的div
。比如這裡留了25
個畫素的空行,
對應成xml
:
<Control height="25" />
比如介面這塊圖片的顯示:
對應成xml
:
<VerticalLayout width="480"
height="250"
roundcorner="5,5"
bkimage="file='form\pic.png'">
注意這裡圖片路徑是相對於skin
目錄的。
比如安裝路徑這個文字:
對應成xml
:
<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" />
比如一鍵安裝這個按鈕:
需要指明按鈕不同狀態下的圖片
對應成xml
:
<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
對應的xml
:
<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} "<Licenses and Service>"
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
學習。