剛開始學U3D,入門是比難的,首先要瞭解U3D最重要的五大介面,第一:情境(Sence),構建遊戲的地方;第二:層級(Hierarchy),情境中的遊戲對象都列在這裡。第三:檢測面板(Inspector),當前選中的資源或對象的設定,是一些變數和組件的集合。第四:遊戲(Game),示範視窗,僅在播放模式中示範。第五:項目 (Project),一些資源的列表,和庫的概念一樣。
然後瞭解主功能表列的八大菜單:檔案(File),編輯(Edit),資源(Assets),遊戲對象(GameObject),組件(Component),地形(Terrain),視窗(Window),協助(Help),熟悉這些菜單每一個命令對以後的遊戲製作大有協助。
在U3D中,一定要對座標(Coordinates)有個瞭解,U3D的座標點是以(x,y,z)的順序排列的,切記。熟悉座標,在做遊戲的過程中會更加順手。
如果你沒有任何編程基礎,一樣可以學習Javascript(或C#這些都行),我學AS的時候也完全不懂編程。先學Javascript語言也無妨,因為這個引擎主要是個編程工具。開啟Script協助文檔和Monodevelop編寫器,從最簡單的位移(transform.Translate)開始吧。
Unity3D的基本操作很容易就能掌握了,接下來就是遊戲系統的核心部分:指令碼。
什麼是Script(指令碼)?簡而言之,就是使用代碼來執行一系列動作命令的特殊文本,它需要編譯器來從新解讀。U3D內部如何解讀指令碼,這不是我們所要關心的—這是引擎開發人員的活,我們所要知道的就是指令碼的使用規則。
【三種語言的特點】
U3D支援C#,JavaScript,BOO三種語言格式的代碼編寫。首先來簡單介紹下這三種語言的特點:
對U3D來說,這是入門級的指令碼語言,U3D內建的函數都能通過JS方便的調用。文法上,JS和傳統的c語言差不多,需要分號結束符,變數類型定義,大括弧……不過它的變數類型定義,是通過冒號接在變數右邊,如:Name:string=”Li”。相對其他兩種語言,使用JS文法,很多函數不需要執行個體化就能直接使用,如:
vector3 direction=vector3(1,2,3)。如果使用C#,則需要使用new關鍵字:vector3 direction=new vector3(1,2,3)。JavaScript直接繼承自U3D的MonoBehaviour類,因此不像C#和BOO那樣需要使用Using或Import來載入類庫。這看似省心,不過因為缺少了載入特殊類庫,JavaScript能調用的第三方函數不多(當然,我們可以載入net類庫給JavaScript調用,雖然看著有點奇怪……)。
*注意:JavaScript不是Java,同時,U3D中的JavaScript也有別於獨立的JavaScript語言。
C#(發音C Sharp),微軟開發的物件導向程式設計語言。由於有強大的net類庫支援,以及由此衍生出的很多跨平台語言,C#逐漸成為U3D開發人員推崇的程式語言。U3D內建的指令碼範例中,C#指令碼也佔了很大一部分(其他指令碼是JavaScript指令碼)。另外,在裝有VisualStudio的電腦上,我們也可以使用微軟的指令碼編輯工具來編寫U3D指令碼。C開頭,那麼文法上和C語言是很接近的,除了物件導向語言所具有的一些特點。當然,我不用在這進行太多說明,因為C#的相關學習資料很多。
BOO是新興的基於Python的語言。文法上,BOO和Python大同小異,都是通過換行來實現語句的結束,它省略了分號、大括弧,甚至條件陳述式的小括弧等。Python在很多大型三維圖形軟體上都有應用,由此可以看出它的跨平台效能很不錯,我也選擇使用Python來編寫maya特效指令碼;不過,對於遊戲事件的編寫,個人感到這種精簡的文法反而有些難以適應。如基本的變數類型定義,BOO(類Python)文法就顯得不那麼便捷: direction as vector3 =vector3(1,2,3)。遊戲事件不同於特效指令碼,前者是過程中的互動,而後者只需要看到結果。因此,遊戲中經常需要大量的具有明確類型的變數出現,BOO語言可以省略變數類型的優勢在這裡反而容易引發問題。
引擎編譯時間,三種語言的執行效率是一樣的,因為U3D會內部進行它自己的語言格式的轉換。儘管我們可以在不同語言編寫的指令碼之間進行變數和方法的調用,但是我不推薦那麼做,因為測試確實會存在一些意想不到的問題。使用不同語言編寫多個指令碼時,應盡量讓指令碼之間沒有直接聯絡。
最後,個人認為,在windows平台下,C#是U3D指令碼語言的最佳選擇。
【指令碼的使用規則】
U3D的指令碼作用方式很有趣,我稱之為“拖放法”。無論是作用在一個具體的情境物體還是管理著批量的物體,指令碼首先必須依附於情境中的一個元素才能被執行。要將指令碼賦予物體的方式很簡單,就是按住滑鼠左鍵將指令檔拖放到物體的屬性面板上(也可以拖放到情境的物體上)。U3D有個概念,那就是component(成分)--類似Maya的節點。包括指令碼,所有元素屬性都是遊戲物體的component。添加、刪除、停用、讀取、寫入component資訊,就是指令碼所要做的(儘管指令碼也是個component)。
net語言的C#,在不同指令碼之間調用變數和方法時,如果指令碼位於同一路徑下,那麼只需要對非static(靜態)成員進行new執行個體化即可。例如a.cs和b.cs,要呼叫指令碼a中的一個非靜態變數cc,需要在指令碼b中寫入:a c=new a(),然後c.cc的格式完成調用。不過,作為一個component,要調用不同指令碼之間的成員,U3D的規則是使用GetComponent函數來完成(其實也就相當於new的作用,只是U3D不支援這種指令碼間調用的寫法)。如:
someScript = GetComponent();
如果是在C#指令碼中調用JavaScript指令碼,則使用強制類型轉換文法:
someScript = GetComponent(“ExampleScript”) as ExampleScript ;
*<>這個特殊的符號表示使用的是C#中的泛型功能,用於避免強制類型的轉換,減少裝箱量(將實值型別專為參考型別的操作)。
根據指令碼使用的情況,可以有以下做法:
1.指令碼位於同一個物體上。
可直接使用泛型或者類型轉換文法調用。
如:someScript = GetComponent();
2.指令碼位於不同物體上。
需要使用Find或相關的搜尋函數,取得指定名稱的物體資訊後,再+”.GetComponent”函數。如:GameObject.Find("stone").GetComponent()。
3.指令碼位於同一路徑或者被呼叫指令碼位於主指令碼的路徑及以下(指令碼是否被物體使用都可)。
將被呼叫指令碼中的成員(變數或方法)使用static標識,然後可以通過”指令碼.成員”的格式直接調用。例如:
ScriptA.CS
…
public static mm();
…
ScriptB.CS
…
ScriptA.mm();
…
不過,static成員的調用雖然提高了效率,但因為它常駐記憶體,所以在會產生大量系統資源要求的情況下要慎用。
*static是C#定義變數或方法類型的關鍵字,使用static的變數或方法,不需要new執行個體化即可直接調用。
【指令碼內容】
除了JavaScript函數,C#和BOO的指令碼都需要預先載入類庫。這裡以C#為例:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour {
}
NewBehaviourScript是指令碼的名稱,它必須和指令檔的外部名稱一致(如果不同,指令碼無法在物體上被執行)。所有遊戲執行語句,都包含在這個繼承自MonoBehaviour類的自創指令碼中(大括弧內)。
以下介紹一些常用的內建運行函數(定義函數時,JavaScript的關鍵字是function,C#是void,BOO是def。如:void Start()。
Awake:在遊戲運行時調用,用於初始化。
Start : 只在遊戲開始時執行一次,在Awake()函數後執行;
Update:在遊戲每一幀都執行一次,在Start()函數後執行;
LateUpdate:同Update,只是它會在Update()函數執行後再執行;
FixedUpdate:當遊戲中引入剛體系統,使用適配的方式同步物理時鐘,可以讓動力學更精確的計算;
OnGUI:繪製遊戲介面的函數,因為每一幀執行多次,所以一些時間相關的函數要盡量避免直接在其內部使用。
OnMouseOver:滑鼠停留在物體上時執行該函數的內容。
OnMouseEnter:滑鼠進入物體範圍時執行該函數的內容。和OnMouseOver不同,該函數只執行一次。
OnMouseExit:滑鼠離開物體範圍時執行該函數的內容。
OnMouseDown:滑鼠按下時執行該函數的內容。
OnMouseUp:當滑鼠釋放時執行該函數的內容。
OnMouseDrag:按住滑鼠拖動時執行該函數的內容。
OnMouse系列函數是針對指定物體的,如果要使用全域滑鼠控制操作,則需要使用射線相關函數。還有很多特殊的遊戲觸發事件的函數,這裡就不一一列出。
U3D內建的代碼有個命名規則,開頭第一個字母大寫的片語都屬於類或者函數,而開頭小寫片語則是變數。新手經常會混淆它們之間的區別。簡單說來,函數片語可以作為變數的類型,還可以直接執行功能,片語後必接成對小括弧;變數是對應函數的分支,實現的是對一個具體屬性的控制。例如:
Camera和camera。當情境中存在一個預設的主攝像機,指令碼位於攝像機時,Camera.mainCamera.transform和camera.transform是等同的;假如指令碼在其他物體上,Camera.mainCamera.transform仍舊直接擷取了主攝像機,而camera.transform必須要使用Find函數先找到指定名稱的攝像機:GameObject.Find("mainCamera").camera.transform。當然,這裡是列出細節來比較,實際運用時,指令碼位於特殊元素上我們可以不用聲明這個元素的類別,如camera.transform可以簡化到transform。
GameObject和gameObject。前者包含尋找,破壞和產生遊戲物體的函數,同時可以將一個變數定義為“遊戲物體”類型;後者則包含豐富的遊戲物體屬性,例如名稱,變形資訊,動畫,渲染等。
同上面列舉的camera,對於直接作用於指定物體的指令碼,gameObject也可以省略掉。不過,在開始學寫代碼時,書寫完整的變數名能讓我們更深刻的記憶每個屬性和變數的關係。
通過以上對比後可以這麼理解,函數通常是全域控制(包含指令碼外的其他物體),而變數是需要指出具體的應用對象。
*如果書寫代碼時語句不在函數作用範圍內,編譯器將無法智能完成一些變數的全名顯示。
更多函數的運用方法,使用者可參考官方提供的協助文檔。
【簡單例子】
在一個箱子上按下滑鼠左鍵,箱子就開始旋轉,同時燈光被點亮,螢幕出現“Test”的字樣。
1.點擊Hierarchy欄下的Create,建立一個Cube,Plane和Spotlight。
2.在Assets目錄下,建立檔案夾(右鍵,Create->Folder),用於存放遊戲的各種資源:模型,動畫,材質,指令碼,Prefab等。
3.雙擊進入myScript檔案夾,建立一個C#指令碼。
4.給指令碼命名,然後再雙擊開啟指令碼編輯工具(如果指令碼名稱和內部的類名稱不同,一定要更正)。
5.加入滑鼠相關函數,這裡我需要用到OnMouseOver,OnMouseExit,OnMouseDown。此類特殊函數不會有智能拼字出現。
6.文法方面就不傻瓜式的一步步進行了,實現的思路是:
當滑鼠移動到物體上,物體的材質色彩變為黃色,同時將一個初始值為假的布爾變數1的值取真;當滑鼠離開後,物體材質色彩還原,布爾變數1值為假;當按下滑鼠左鍵,且布爾變數1的值為真,布爾變數2的值為真。
*OnMouse函數都是執行一次的函數,因此不能將和動畫有關的控制函數放於其內執行,通常會使用布爾開關來控制Update函數中的動畫函數。
7.接著在Update函數內實現:
當布爾變數2的值為真,物體旋轉。
8.將寫好的指令碼拖拽到情境中的方塊上(或者先選擇方塊,將指令碼拖到方塊的屬性欄),調好攝像機位置,執行測試;
9.因為指令碼只作用於其所依附的物體,要控制燈光,我們有兩個選擇:建立新的指令碼,使用尋找物體函數。很顯然,我沒必要為這麼簡單的功能加入一個新的指令碼。因此我使用Find函數擷取燈光的強度屬性。
*指令碼中遇到這樣一串長長的語句時,通常會使用一個自訂變數來替代,而且很多時候還能降低計算量。例如:
float LightInt =GameObject.Find("Spotlight").light.intensity。如果在Start函數中初始化,Update中就不必每幀執行尋找函數,降低遊戲效率。不過這裡作為一個測試,我也就很省事的忽略了。
10.GUI的使用。要在遊戲視圖顯示各種UI資訊,都需要使用U3D的UI組件或者GUI函數。為方便閱讀,我將其他函數的內容收合,並使用GUI函數的Label建立簡單的文字顯示功能(有關GUI更多的詳細資料,請參閱官方文檔,我會在後面再做討論)。
11.最終測試:
12.發布。執行File->BuildSettings,開啟發布介面。設定好要發布遊戲的平台以及一些發布的資訊,點擊Build按鈕即可發布一個完整的遊戲用戶端。U3D支援發布的平台與使用者授權和作業系統有關,如IOS遊戲需要MAC平台的U3D才能發布,Android需要在系統安裝安卓SDK包,次世代主機平台則需要使用者向官方購買額外解決方案。
*由於商業策略的分歧,U3D剛宣布了中止flash平台的後續開發,看來想玩U3D開發的網頁遊戲,還是需要先安裝U3D官方的網頁外掛程式。
【結語】
遊戲製作並不簡單,這裡僅僅是一個初入門的小例子,主要是對指令碼使用方式的練習。後面會逐步深入學習U3D豐富的函數功能。