原文鏈接:http://blog.csdn.net/candycat1992/article/details/24984347
寫在前面
這一篇我個人認為還是很常用的,一開始也是實習的時候學到的,所以我覺得實習真的是一個快速學習工程技巧的途徑。
提醒:這篇教程比較復雜,如果你不熟悉NGUI、iTween、C#的回調函數機制,那么這篇文章可能對你比較有難度,當然你可以挑戰自我。
言歸正傳,消息框,也就是Message Box,在Windows下很常見,如下圖:

在游戲里,我們也會用到這樣的消息框。例如用戶按了返回按鈕,一般都會彈出一個確認退出的按鈕。用戶在執行某些重要操作時,我們總是希望再一次確認以防用戶的無意操作,以此來提高用戶體驗。這篇教程就會詳細敘述Unity中消息框的一種實現。(由於見識有限,我相信肯定還有其他的方式,可能更簡單易用,如有不足歡迎指出。)
准備工作
插件和工具
這篇教程還是有點復雜的,需要各個方面的力量幫助我們:
- 既然是界面,那么就離不開NGUI。確保你的項目里有NGUI插件,以及必要的圖集來制作消息框的背景(為了省事你可以直接只用NGUI例子中的Atlas)。如果你需要中文顯示,那么還需要用NGUI制作一個中文字體。網絡上有很多教程。
- 單例腳本。這里需要單例模式主要是為了實現那種無需在面板中(指的是除消息框以外的其他對象)引用任何資源,這樣你看起來就像是在VS2010下調用MessageBox.Show一樣。
- iTween插件。這個插件是免費的,而且非常小巧。使用它主要是為了美化消息框的彈出效果,例如放大彈出、從上向下彈出等等。本例使用的是從上向下彈出。
准備好了這些工具后,我們需要制作本地化文字,這是為了定義消息框中的按鈕文字以及彈出時顯示的標題和內容文字,當然你可以略去這一步,但是這是非常不建議的,因為在真正的項目中管理好所有的文本是很重要的。本地化是NGUI的功能,經常被用於轉換多種語言,例如中文、英文等。你可以在NGUI的例子中找到對應的場景和教程(Example 10 - Localization),涉及到的腳本主要是Localization.cs(統一管理所有的可用語言)、UILocalize.cs(指明一個UILabel顯示的文字)。這里假設你知道這些腳本是干嘛的。
本地化文本
要實現一個本地化文本非常容易。只需要兩步:
- 創建一個純文本文件,例如cn.txt,里面將定義所有用到的字符串。
- Confirm = 確定
- Cancel = 取消
- QuitConfirmTitle = 退出確認
- QuitConfirmContent = 繼續將退出游戲。\n確定退出?
對於我們的彈出框,只用到上述四個文本。等號左邊的名字相當於這個字符串的ID,等號右邊是內容。 - 制作一個Localization Prefab。這點和NGUI例子很相似,就是為了方便以后修改。對於我們的教程,如果你不制作成一個Prefab也是可以的,但是還是不建議,還是那句話,這種思維還是很重要的。制作好的prefab如下:
細心的你可能發現除了上述提到的腳本,還有一個腳本:DontDestroyOnLoad.cs。代碼如下:
- using UnityEngine;
- using System.Collections;
- public class DontDestroyOnLoad : MonoBehaviour {
- // Use this for initialization
- void Start () {
- DontDestroyOnLoad(this.gameObject);
- }
- }
它的作用顯而易見,就是為了不讓我們的本地化文本在場景切換時被銷毀。這樣就不用每一個場景都實例化一個Localization Prefab,而只需要在游戲的第一個場景中包含一個Localization Prefab即可。 - 新建一個場景,並把之前的Localization Prefab拖進去。
實現
呼呼,下面的內容比較復雜,希望你能耐心看下去。
測試功能:檢測退出按鈕
我們首先寫一個測試腳本,它的功能就是檢測用戶是否按下了退出按鈕,否則就會嘗試調用我們的消息框(當然這里還沒有定義,我只是想從最高層向底層一層一層講解)。
下面是KeyDetecter.cs:
- using UnityEngine;
- using System.Collections;
- public class KeyDetecter : MonoBehaviour {
- private CommonUIManager m_CommonUIManager = null;
- void Start() {
- m_CommonUIManager = Singleton.getInstance("CommonUIManager") as CommonUIManager;
- }
- // Update is called once per frame
- void Update () {
- if (Input.GetKey(KeyCode.Escape) && m_CommonUIManager != null) {
- m_CommonUIManager.ShowMessageBox(
- Localization.instance.Get("QuitConfirmTitle"),
- Localization.instance.Get("QuitConfirmContent"),
- MessageBox.Style.OKAndCancel,
- OnReceiveQuitConfirmResult);
- }
- }
- void OnReceiveQuitConfirmResult(MessageBox.Result result) {
- if (result == MessageBox.Result.OK) {
- Application.Quit();
- }
- }
- }
上述代碼很短,最重要的部分是ShowMessageBox部分。由於我們還沒有實現CommonUIManager,這里你可以理解ShowMessageBox就是彈出一個消息框,它的標題是Localization.instance.Get("QuitConfirmTitle")(根據cn.txt我們知道對應的文本是“退出確認”),內容是Localization.instance.Get("QuitConfirmContent")(根據cn.txt我們知道對應文本是“繼續將退出游戲。\n確定退出?”),並且它的類型是包含OK和Cancel兩個按鈕的標准彈出框,用戶點擊后的回調函數是OnReceiveQuitConfirmResult。
OnReceiveQuitConfirmResult函數根據用戶選擇結果來判斷是否真正退出游戲。
界面
最麻煩的部分來了。我們使用NGUI制作界面。用NGUI創建一個全新的UI,並重新命名。按照類似下圖的組織方式創建其他界面元素:

從最上面說起。
CommonUIRoot,即之前的UI Root,的位置很重要,由於彈出框界面引入了一個新的Camera,因此為了防止它的視野范圍和其他場景中已有的Camera重合,應盡量把它的位置調整到一個完全空白的位置。本例設置的位置是(0,2000,-2000)。

Camera、Anchor和Panel。確保Anchor的Side設置成Center。如果你想要自適應多種大小的屏幕,那么就需要調整Camera的Size,並向Panel添加UIStretch腳本,具體過程請Google。
Window和WindowRoot。這兩個主要是為了設置彈出動畫而設置的,它們原本都是空對象,而后添加了一些腳本或者動畫。
Window設置如下,可以看到它添加了iTween的一個腳本,主要用於實現從上向下彈出的移動效果。注意,它的位置和iTween腳本中From和To參數的設置有很大關系。

這里的WindowRoot就是一個空對象,但是如果需要自定義的動畫時就需要可以再通過一些技巧設置WindowRoot。這里就不講了。
后面的元素用紅色的文字和方框注釋過了。其中需要解釋的就是LockCollider。它的作用就是通過一個BoxCollider擋住后面所有可點擊的UI,使得用戶只能點擊消息框上的按鈕。所以它的Z坐標比其他元素更靠后,而且大小應該超過屏幕的大小。

最后,還需要設置三個消息框按鈕,包括它們的文字和回調函數。
OKAndCancelButtonGroup下一共包含兩個按鈕。首先為CancelButton添加回調函數,這是通過把NGUI的UIButtonMessage腳本添加到CancelButton對象上實現的(即調用CommonUIRoot上腳本的OnCancel函數,當然這里我們還沒有實現這個腳本):

我們希望取消按鈕上的文字為“取消”,因此向CancelButton對象下面的Label添加如下腳本(通過cn.txt我們知道Cancel對應的文本是“取消”):

同理,設置另外兩個按鈕。OKAndCancelButtonGroup的ConfirmButton和OnlyOKButtonGroup的ConfirmButton設置相同,它們的回調函數名為OnConfirm,UILocalize腳本的Key設置為Confirm。
最后!!!真的是最后了。。。我們需要用腳本管理上面這些界面元素。建立一個新的腳本CommonUIDetail.cs:
- using UnityEngine;
- using System.Collections;
- public class CommonUIDetail : MonoBehaviour {
- public TweenPosition messageBoxTween;
- public UILabel messageBoxTitle;
- public UILabel messageBoxContent;
- public GameObject[] buttonGroups = new GameObject[(int)MessageBox.Style.eNumCount];
- public System.Action messageBoxConfirmCallback = null;
- public System.Action messageBoxCancelCallback = null;
- void OnConfirm() {
- if (messageBoxConfirmCallback != null) {
- messageBoxConfirmCallback();
- }
- }
- void OnCancel() {
- if (messageBoxCancelCallback != null) {
- messageBoxCancelCallback();
- }
- }
- }
並把該腳本添加到CommonUIRoot上,並給面板上的各個變量賦值:

調整所有UI的位置,使它們看起來像一個彈出框。
完成后,把整個CommonUIRoot及其所有子對象制作成一個Prefab,並且放在Assets/Resources文件夾下,這樣才能通過Resources.Load來動態加載它。
單例腳本
還記得之前測試腳本里未實現的CommonUIManager嗎?現在我們就來實現最關鍵的ShowMessageBox代碼。CommonUIManager.cs如下:
- using UnityEngine;
- using System.Collections;
- public class MessageBox {
- public delegate void OnReceiveMessageBoxResult(MessageBox.Result result);
- public enum Style {
- OnlyOK,
- OKAndCancel,
- eNumCount
- }
- public enum Result {
- OK,
- Cancel,
- eNumCount
- }
- }
- public class CommonUIManager : MonoBehaviour {
- public GameObject commonUIPrefab = null;
- public GameObject root;
- public TweenPosition messageBoxTween;
- public UILabel messageBoxTitle;
- public UILabel messageBoxContent;
- public GameObject[] buttonGroups = new GameObject[(int)MessageBox.Style.eNumCount];
- private MessageBox.OnReceiveMessageBoxResult messageBoxCallback = null;
- // Use this for initialization
- void Start () {
- }
- // Update is called once per frame
- void Update () {
- }
- public void ShowMessageBox(string title, string content, MessageBox.Style style,
- MessageBox.OnReceiveMessageBoxResult callback) {
- if (root == null) {
- commonUIPrefab = Resources.Load("CommonUIRoot") as GameObject;
- root = GameObject.Instantiate(commonUIPrefab) as GameObject;
- root.transform.parent = this.transform;
- CommonUIDetail uiDetail = root.GetComponent<CommonUIDetail>();
- messageBoxTween = uiDetail.messageBoxTween;
- messageBoxTitle = uiDetail.messageBoxTitle;
- messageBoxContent = uiDetail.messageBoxContent;
- buttonGroups[(int)MessageBox.Style.OnlyOK] = uiDetail.buttonGroups[(int)MessageBox.Style.OnlyOK];
- buttonGroups[(int)MessageBox.Style.OKAndCancel] = uiDetail.buttonGroups[(int)MessageBox.Style.OKAndCancel];
- uiDetail.messageBoxConfirmCallback = OnConfirm;
- uiDetail.messageBoxCancelCallback = OnCancel;
- }
- messageBoxTitle.text = title;
- messageBoxContent.text = content;
- messageBoxCallback = callback;
- switch ((int)style) {
- case (int)MessageBox.Style.OnlyOK:
- buttonGroups[(int)MessageBox.Style.OnlyOK].SetActive(true);
- buttonGroups[(int)MessageBox.Style.OKAndCancel].SetActive(false);
- break;
- case (int)MessageBox.Style.OKAndCancel:
- buttonGroups[(int)MessageBox.Style.OnlyOK].SetActive(false);
- buttonGroups[(int)MessageBox.Style.OKAndCancel].SetActive(true);
- break;
- }
- messageBoxTween.Play(true);
- }
- void OnConfirm() {
- if (messageBoxCallback != null) {
- messageBoxCallback(MessageBox.Result.OK);
- messageBoxCallback = null;
- }
- messageBoxTween.Play(false);
- }
- void OnCancel() {
- if (messageBoxCallback != null) {
- messageBoxCallback(MessageBox.Result.Cancel);
- messageBoxCallback = null;
- }
- messageBoxTween.Play(false);
- }
- }
我只能幫你到這里了,這代碼不長,相信如果你肯花時間一定可以看懂。
把之前的KeyDetector.cs代碼添加到場景中的MainCamera上,運行,點擊鍵盤上的ESC按鍵,應該就會彈出你的消息框了。
至此,整個過程結束。
效果
保存所有代碼,此時不應該再有任何報錯,如果有的話,自己找找原因吧……
下面是我制作的游戲中實現的彈出框:

結束語
啊哦,這是最近寫的最復雜的一篇教程了,里面有些部分肯定說的不是非常清楚,如果實在遇到無法理解的錯誤(有明顯編輯錯誤提示的,請調動您珍貴的大腦,看一下代碼,去自己解決),樂意解答。