译者注:
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