譯者注:
Unity3D中支持三種語言:JavaScript、C#、Boo,很多人不知道如何選擇,通過這篇譯文,我們可以搞清楚這三者語言的來龍去脈,對選擇主語言有一定的借鑒意義。
首先,Unity是基於Mono也就是.Net的運行環境的,所以它肯定支持C#;然后,Unity團隊自行開發了一種Boo的語言;后面可能考慮到用戶的接受程度的問題,又開發了類似JS的一種語言,但那絕對不是JS,勉強可以稱之為UnityScript。這三種語言的代碼最后都會被編譯執行,而且可以互相訪問。
花了一上午,才譯完,文章有點長,估計有耐心看完的都不多,呵呵。
一、Unity中的"JavaScript"與你所了解的JavaScript對比
1. 使用 #pragma strict
#pragma strict
進行這樣的聲明,是一種很好的習慣,並且對於進行iOS開發來說也是必須的。 #pragma strict 意味着強制進行更嚴格的類型檢測、盡早生成更多有用的錯誤信息、養成更好的編程習慣。
2. 使用枚舉 enum
enum WeaponType { pistol, rifle, launcher } var type : WeaponType = WeaponType.pistol;
這種方式更加簡潔,並且是比使用字符串產生更少潛在錯誤的方法。
3. 其實是大不相同的
盡管Unity中的JavaScript嘗試盡量做得至少某種程度上要像ECMAScript標准,但它與同樣基於ECMAScript的JavaScript在其他實現方面有很多不同。也許它與微軟的JScript更加相似,尤其是它們都是.NET平台上的語言。當然,Unity的JavaScript版本是自己獨立開發實現的,並且兩者之間也有很多不同之處。
4. 運行速度快
Unity JavaScript 是編譯型的,所以性能很高,但瀏覽器中的JavaScript是動態解釋型的。
在Unity中,JavaScript、C#與Boo在運行速度上顯然沒有差異。它們各有優缺點,但速度上是一致的。
說明:如果你關注過瀏覽器之爭的話,你應該知道現代瀏覽器中JavaScript已經不是簡單的解釋執行,而是以JIT方式編譯執行。當然,肯定是不支持嚴格類型定義的。如果ECMAScript標准改成允許顯式聲明變量類型(像Adobe公司所提倡的,譯注:其實個人覺得UnityScript更像是ActionScript3),JavaScript的性能還能以數量級的提升。盡管如此,現實是真正的JavaScript就算是拿Safari瀏覽器的Squirrelfish Extreme引擎進行測試,比Unity中的UnityScript仍要慢上兩個數量級。
5. 必須用var關鍵字聲明變量
JavaScript中,如果你定義變量時不用var關鍵字,該變量將會作為全局變量處理。
function DoSomeStuff() { x = 3; } DoSomeStuff(); alert(x); // returns 3 ... in JavaScript (not in Unity's UnityScript)
為了避免JS老用戶在Unity碰到這種模棱兩可的情況,就要求在定義變量時加上var關鍵字,那樣就可以自動將變量的作用域限定在當前范圍。
function DoSomeStuff() { var x = 3; } DoSomeStuff(); print(x); // raises an error because x is not global in any sense.
6. UnityScript是基於類式繼承,而不是原型式繼承
UnityScript中,沒有.prototype那樣混亂的寫法。要定義類,你只要這樣簡單的定義:
// Foo.js var foo = "hello, world"; function doEet () { // does nothing, intended to be overridden }
編譯器最后在編譯之前會自動補全一些代碼,構造一個完整的類定義結構。最終形式應該類似如下:
// Foo.js import UnityEngine; class Foo extends MonoBehaviour { public var foo = "hello, world"; public function doEet () { // does nothing, intended to be overridden } }
請注意,文件名就是對應的類名。
子類寫法:
// PrintingFoo.js class PrintingFoo extends Foo { function doEet() { print( foo ); } }
虛函數可用於重載函數
在UnityScript中,你可以創建虛函數。
class Foo { virtual function DoSomething () { Debug.Log("from base class"); } } //SubFoo.js class SubFoo extends Foo { virtual function DoSomething() { Debug.Log("from sub class"); } } //Elsewhere var foo : Foo = new SubFoo(); foo.DoSomething();//prints from sub class
如果你要調用父類的方法,用關鍵字super。示例如下:
class SubFoo extends Foo { virtual function DoSomething() { super.DoSomething(); Debug.Log("from sub class"); } } //Elsewhere var foo : Foo = new SubFoo(); foo.DoSomething();//prints "from base class" and "from sub class"
考慮用編寫Mixins與Helpers的方式替代子類繼承
可以很容易編寫相互訪問與調用的類,但還有一種方式可能比用對指定對象進行子類繼承具有更好的維護性。
如:
/* Foo.js */ var bar : Bar; function Start(){ bar = gameObject.GetComponent(Bar); } function doEet(){ // do my own thing if( bar ){ bar.doEet(); } } /* Bar.js */ function doEet(){ // do something special }
7. 聲明字符串類型是使用String (代表Mono中的String類) 而不是string
var x : String;
你在JavaScript所知道及喜歡的字符串函數都在,不同的是調用時首字母大寫。
比如如何分割字符串,寫法如下:
var qualifiedName = "System.Integer myInt"; var name = qualifiedName.Split(" "[0]);
分割后,name[1] 就包含"myInt"。
要查看可用的字符串函數清單,請訪問Mono文檔(http://go-mono.com/docs/monodoc.ashx?link=T%3aSystem.String%2f*)
8. 變量在使用前必須進行聲明
a = "fred"; // works in JavaScript (a is treated as a global), error in Unity var a = "fred"; // a is now a string variable containing 'fred' var b: String; // b is now a string variable, with no assigned value b = "wilma"; var c; // c is now a dynamically typed variable with no assigned value c = "barney"; c = 17;
a) 你可以(通常也應該這么做)顯式聲明變量的作用域,如private、public等。不聲明的話,默認代表public。
b) 在你聲明一個變量時,如果直接賦值給它,Unity就會隱式的給它定義一個數據類型,所以:
var a = "fred"; // a is now of type String a = 5; // ERROR! -- a is a String var b : String = "fred"; // redundant
但:
var a; // a is dynamically typed; a = "fred"; // works a = 5; // works
9. 方法名與類名通常都是首字母大寫
方法名與類名一般是首字母大寫的,除非當它們不是遵循這一原則的時候。這句話很矛盾。本質上,UnityScript是處在.NET的命名約定的環境 - 方法名采用CamelCase這種駱駝峰式及camelCase這種首字母大寫的寫法、屬性采用駱駝峰式且首字母小寫,但它也試着像JavaScript的寫法 - 像C一樣,嚴重分化成小寫命名及camelCase駱駝峰式。
如 JavaScript 中, typeof("fred") == 'string', 但在Unity中你的寫法是 var a: String;
10. 多了很多數據類型、兩種數組、無對象語法糖
JavaScript本質上有三種類型:數值、字符串與對象(函數與數組都是對象)。UnityScript則具有更多的數據類型,包括:
1)對象:不能與array、Array進行互相轉換;
var a = new Object(); // works a.fred = "wilma"; // runtime exception!
2)原生數組:無法進行動態調整;
var a = [1, 2, 3]; a.Push(4); // ERROR -- won't work!
如果要定義指定類型的數組,語法如下:
public var friendsOfCarlotta : Transform[];
3)UnityScript Array:可以動態調整
var a = new Array(); a.Push(4); // This works
你可以把UnityScript Array轉換成原生array,效率更高但靈活性降低,具體是使用方法ToBuiltIn(ArrayType),如:
var a = new Array(); a.Push(1); a.Push(3.1415926535); a.Push(17); var b = a.ToBuiltin(float);
4)整型(包括int、uint32、等等):
Unity支持各種整型的大數,你不需要擔心。
5)Unity的大量內置類(如Vector3):
你在使用Unity的過程中,你會越來越熟悉這些類,就像Web開發時那些DOM類一樣。Unity中的類相比DOM要少。
使得用Unity非常有趣的一件事是,它的類采用了非常自由的mixin策略。通常你可以非常快速簡單的查到你所需要的類。最通用的一個例子是Transform類,對於你正在處理的一個對象,所有被附加到該對象的相關聯的類,你都可以簡單快速的獲取。(譯注:這段話沒有翻譯好)
比如,典型的動作就是你會訪問名叫"transform"的變量,它代表與該對象關聯的Transform類的實例。如果你需要相關的位置坐標,就訪問transform.position(一個 Vector3對象);如果你需要它的GameObject,就訪問transform.gameObject;如果你需要它的渲染器,就訪問transform.renderer。等等。一般如果你一個對象的主要屬性,你就可以快速獲取所有其他的屬性。
a) Unity的String類缺少JavaScript中字符串的好的特性;
b) Unity的內部數組遠不如JavaScript中數組和對象的靈活。當然,可以用各種集合類如List、Queue、Dictionary等來配合實現。內部數組速度是最快的。
11. 每個.js文件默認代表一個類
一般來說,貼入如下代碼:
var x : int; function y(){}
並保存到Foo.js文件中,等同於在該文件中輸入如下代碼:
class Foo extends MonoBehaviour { var x : int; function y(){} }
但是,你可以在同一個文件中聲明多個類,尤其是當你需要使用一些輔助工具類時非常有用,一般這種輔助類沒有繼承自MonoBehaviour。
如
class ButtonState { var currentState : int; var offset : Vector2; }
如果你在一個文件中聲明了MonoBehaviour的子類,但類名與文件名不匹配的話,即使是大小寫不一致,你也會碰到麻煩。
一定要理解,當你在js文件中編寫行為腳本是,你實際上在編寫一個類:
a) 文件名就是類名,如果文件名是foo.js,你就可以在其他地方以var x = new foo()的格式進行調用;
b) 有一些特定的方法是實現系統預先定義的一些事件處理器,如Start、FixedUpdate等。任何事件中,聲明的一個函數就是這個文件所代表的類的一個方法。
c) 文件中在函數定義之外編寫的代碼都在該類的范圍之內執行,聲明的變量也是該類的成員變量。
d) 類中的靜態函數、變量本質上是類的方法與屬性。
這種方式遠比真正的JavaScript中實現的類來的更優雅,但某種程度上來說是限定你以更好的方式進行編碼。
例如,創建一個行為腳本,命名為foo,那文件名就應該是foo.js。假設文件的內容如下:
public name : String; // when you drag the behavior onto a gameobject, these values will be visible and editable public age : int; // other scripts which have a reference to this object (e.g. if they're attached to the same object) can see public functions private favoriteColor : Color; // private members are NOT visible to other scripts, even if they have a reference to this object public bestFriend : foo; // you can assign a value to bestFriend by dragging a gameObject with an attached copy of the foo behavior to this property. This will give you access to bestFriend's public methods and members function Update(){ // this function will be called every frame by Unity, so it's actually an event handler var t = transform; // transform is a property inherited from the gameObject the behavior is attached to } function Bar(){ // this is just a function, if you don't call it yourself, it will never do anything }
調用父類方法
super()代表父類的構造函數,supper則相當於父類中的原本應該以this訪問的成員函數,如super.foo()代表代用父類的foo()方法。
12. 分號不可缺少
JavaScript中分號是可寫可不寫的,但在Unity中必須要寫。
var foo = 3 // OK in JavaScript but an error in Unity foo += 17
13. Math 對應 Mathf; Math.abs 對應 Mathf.Abs
JavaScript的Math庫在Unity中對應為Mathf庫,並且方法名是首字母大寫的,如Math.abs()在Unity中就應該是Mathf.Abs()。
二、Mono運行環境 (.NET)
UnityScript運行環境使用Mono - .NET的開源克隆。實際上,UnityScript是用Boo實現的,Boo是運行在Mono虛擬機上的一種語言,並且編譯成本機代碼。JavasScript很多典型的運行環境如String和Math庫由Mono提供。你也就知道了為什么UnityScript中方法名要大寫了,因為要與Mono中相同。
要使用Mono庫,就需要導入它們,如:
import System;
import System.IO;
否則,你帶指定完整的命名空間來調用函數,如System.IO.File.Open(),而不是File.Open()。
Mono中的數據類型
當Mono函數需要字符char作為輸入參數時,你可以簡單的使用索引來獲取,比如你像將小寫的a作為字符char a傳遞,你可以這樣寫:
"a"[0]
如,當使用String.Replace()函數時,寫法如下:
var s : String = "Whatever_it_may_be"; s = s.Replace("_"[0], " "[0]); // replace all the underscores with spaces
當處理Array對象時,可以把它轉換成更快的內置array類型數據:
fastArray : SomeType[] = monoArray.ToBuiltin(SomeType);
UnityScript可以使用泛型,所以當用到動態大小的數組時,最好用List來替代Array。基本上就沒有什么理由要用到Array了,List更快並且功能更多。如果你需要混合類型的數組,你可以用Object List。UnityScript中泛型的語法與C#中接近,除了要加一個額外的“.”符號在“<>”之前。如C#中的"var myList = new List<int>();"在UnityScript中對應的寫法為:"var myList = new List.<int>();"。
使用第三方.NET庫
第三方.NET庫如XML-RPC可以以新建資源Asset的方式引入。
三、調試
腳本錯誤 會在Unity窗口狀態欄中以紅色x圖標顯示。點擊該圖標,會打開console視圖,顯示錯誤列表,並且可以快速跳轉到腳本中出錯的那一行。
Unity也會生成有用的警告,這些警告以黃色的!圖標顯示,比如會告訴你聲明的一個變量沒有用到過。努力讓編寫的代碼不會生成警告信息是一個非常好的習慣。
print()函數將會生成消息,並輸出到狀態欄及控制台中,但僅限MonoBehavioour類范圍內。更好的辦法是使用Debug.Log("insert message here");,該方法到處都可使用。還可用Debug.LogWarning 和 Debug.LogError生成警告和錯誤消息。
Debug.Break(); 可以將游戲暫停在一個精確的點。當一種特定的情條件發生時,如果你想檢查對象的狀態的時候,這個特性非常有用。
開發環境運行項目時,編輯界面也是完全實時更新的,你可以查看對象實例的內部狀態。
本文由Tonio Loewald (a.k.a. podperson)編寫。翻譯:http://x3d.cnblogs.com/p/3838619.html