3.4 Unity脚本的基本语法
通过前面两节的介绍,读者应该对Unity 中专用JavaScript和C#脚本有了一些简单的了解,下面就分别对Unity中专用JavaScript和C#脚本的基本语法进行介绍说明。
3.4.1 常用操作
Unity 中很多对游戏对象的操是通过脚本来修改对象的 Transform(变换属性)与 Rigidbody (刚体属性)参数来实现的。上述属性的参数可以非常方便地通过脚本编程实现修改,例如让物体绕x轴顺时针旋转20°,则可以使用如下的JavaScript代码片段来实现。
1 function Update(){ //声明Update方法
2 this.transform.Rotate(20, 0, 0); //绕x轴上旋转20°
3 }
脚本开发完成后,将这个脚本挂载到需要旋转的游戏对象上,在项目运行时即可实现所需功能。当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 transform.Rotate(20, 0, 0); //绕x轴旋转20°
6 }}
如果希望游戏对象沿z轴正方向移动,则可以使用如下的JavaScript代码片段来实现。
1 var gameobject : GameObject; //声明一个游戏对象
2 function Update(){ //声明Update方法
3 gameobject.transform.Translate(0, 0, 1);//实现gameobject每帧向前移动1个单位长度
4 }
上述代码运行时可以实现gameobject游戏对象每帧向前移动1个单位。当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 transform.Translate(0, 0, 1); //实现gameobject每帧向前移动1个单位长度
6 }}
提示
一般情况下,在Unity中,x轴为红色的轴表示左右,y轴为绿色的轴表示上下,z轴为蓝色的轴表示前后。
3.4.2 记录时间
在Unity中记录时间需要用到Time类。Time类中比较重要的变量为deltaTime(只读),它指的是从最近一次调用Update()或者FixUpdate()到现在的时间。
说明
系统在绘制每一帧时,都会回调一次Update函数,因此,如果想在系统绘制每一帧时都做相同的工作,可以把对应的代码写在Update函数中。
如果想匀速地旋转一个物体,不考虑帧速率的情况下,可以乘以 Time.deltaTime,具体可以使用如下的JavaScript代码片段来实现。
1 var gameobject : GameObject; //声明一个游戏对象
2 function Update(){ //声明Update方法
3 gameobject.transform.Rotate(10* Time.deltaTime,0 , 0); //绕x轴均匀旋转
4 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 transform.Rotate(10 * Time.deltaTime, 0, 0); //绕x轴均匀旋转
6 }}
同样地,也可以使用类似的方法来移动物体,具体可以使用如下的JavaScript代码片段来实现。
1 var gameobject : GameObject; //声明一个游戏对象
2 function Update(){ //声明Update方法
3 gameobject.transform.Translate(0, 0, 1 * Time.deltaTime); //沿z轴向前移动
4 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 transform.Translate(0, 0, 1 * Time.deltaTime);//沿z轴均匀平移
6 }}
如果想每秒增加或者减少一个值,需要乘以 Time.deltaTime,同时也要明确在游戏中是需要每秒1个单位还是每帧1个单位的效果。如果是乘以Time.deltaTime,那么,游戏对象就会按固定的节奏运动而不是依赖游戏的帧速率,因此,游戏对象的运动变得更容易控制。
例如想让游戏对象(Gameobject)沿 y 轴正方向每秒上升 5 个单位,具体可以使用如下的JavaScript代码片段来实现。
1 var gameobject : GameObject; //声明一个游戏对象
2 function Update(){ //声明Update方法
3 gameobject.transform.position.y+=5*Time.deltaTime;//游戏对象沿y轴每秒上升5个单位长度
4 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 public GameObject gameobject; //声明一个游戏对象
5 void Update() { //重写Update函数
6 gameobject.transform.position.y+=5*Time.deltaTime; //沿y轴每秒上升5个单位
7 }}
如果涉及刚体时,可以写在FixUpdate函数里面,这样就不需要乘以Time.deltaTime,例如,让一个刚体沿y轴正方向每秒上升2个单位,可以使用如下的JavaScript代码片段来实现。
1 var gameobject : Rigidbody; //声明一个游戏对象
2 function FixUpdate() { //声明Update方法
3 gameobject.rigidbody.transform.position.y+=2;//刚体沿y轴每秒上升2个单位长度
4 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 public GameObject gameobject; //声明一个游戏对象
5 void FixUpdate() { //重写Update函数
6 gameobject.rigidbody.transform.position.y+=2;//刚体沿y轴每秒上升2个单位长度
7 }}
说明
FixUpdate方法是按固定的物理时间被系统回调执行的,其中的代码的执行和游戏的帧速率无关。
如果涉及刚体的力的时候,通常也不需要乘以Time.deltaTime,因为Unity引擎系统内部事先处理好了。
3.4.3 访问其他组件
组件属于游戏对象,比如把一个 Renderer(渲染器)组件附加到游戏对象上,可以使游戏对象显示到游戏场景中;把Camera(摄像机)组件附加到游戏对象上可以使该对象具有摄像机的所有属性。由于所有的脚本都是组件,因此一般的脚本都可以附加到游戏对象上。
常用的组件可以通过简单的成员变量取得,下面介绍了一些常见的成员变量,如表3-1所示。
表3-1 常见的成员变量
提示
这里的组件体现在属性查看器中,而变量是在脚本中体现的。一个游戏对象的所有组件及其所带的属性参数都能够在属性查看器中查看。如果想通过挂载在游戏对象上的脚本代码来实现获得该游戏对象上的对应组件及其属性,可以通过变量名来获得,例如,获得游戏对象( gameobject )上的 Transform 组件,可这样写gameobject.transform;如果想进一步获得其 position 属性,则可这样写gameobject.transform.position。
如果想查看所有的预定义成员变量,可以查看关于 Component、Behavior 和 MonoBehaviour类的文档,本书不再一一介绍。如果游戏对象中没有想要取得的值,那么上面的变量将为null。
在 Unity 中,附加到游戏对象上的组件可以通过 GetComponent 获得,具体可以使用如下的JavaScript代码片段来实现。
1 var gameobject : Rigidbody; //声明游戏对象
2 function Update(){ //声明Update方法
3 gameobject.transform.Translate(1, 0, 0); //沿x轴移动一个单位
4 gameobject.GetComponent(Transform).Translate(1, 0, 0);//沿x轴移动一个单位
5 }
第4行和第5行代码作用是一样的,都是使游戏对象沿x轴正方向移动。当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 transform.Translate(1, 0, 0); //沿x轴移动一个单位
6 GetComponent<Transform>().Translate(1, 0, 0); //沿x轴移动一个单位
7 }}
提示
注意transfom和Transform之间大小写的区别,前者是变量(小写),后者是类或脚本名称(大写)。大小写不同使你能够从类和脚本名中区分变量。
同样地,也可以通过GetComponent获取其他的脚本。比如有一个HelloWorld脚本,里面有一个sayHello函数。HelloWorld脚本要与调用它的脚本附加在同一游戏对象上,具体可以使用如下的JavaScript代码片段来实现。
1 var otherScript : HelloWorld; //声明一个HelloWorld脚本
2 function Update (){ //声明Update方法
3 otherScript = GetComponent (HelloWorld); //调用HelloWorld脚本
4 otherScript.sayHello(); //执行sayHello方法
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 OtherScript otherScript = GetComponent<OtherScript>(); //得到其他脚本组件
6 otherScript.sayHello(); //执行sayHello方法
7 }}
提示
声明otherScript变量时也可以不指定类型,但是指定类型后,Unity就不需要进行逻辑判断otherScript,这样可以优化性能,提高运行速度。
3.4.4 访问其他游戏对象
大部分脚本不单单控制一个游戏对象。Unity 脚本中有很多方法访问它们的游戏对象和游戏组件。比如有一个Test脚本,并将其附加到一个游戏对象上,具体可以使用如下的JavaScript代码片段来实现。
1 var otherScript : Test; //声明一个Test脚本
2 function Update (){ //声明Update方法
3 otherScript = GetComponent(Test); //调用Test脚本
4 otherScript.DoSomething(); //执行Test脚本中的DoSomething方法
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 OtherScript otherScript = GetComponent<OtherScript>(); //得到其他脚本组件
6 otherScript.DoSomething(); //调用脚本组件中的DoSomething()方法
7 }}
1.通过属性查看器指定参数
可以通过属性查看器来确定一些变量的值,具体可以使用如下的JavaScript代码片段来实现。
1 // 将要转换的对象拖曳到target位置
2 var target : Transform; //声明一个游戏对象
3 function Update (){ //声明Update方法
4 target.Translate(0, 0, 2); //沿z轴每帧移动2个单位
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 // 将要平移的对象拖曳到target位置
2 using UnityEngine;
3 using System.Collections; //引入系统包
4 public class example : MonoBehaviour { //声明一个example类
5 public Transform target; //定义一个Transform类型的target变量
6 void Update() { //重写Update函数
7 target.Translate(0, 0, 2); //在z轴上平移2个单位
8 }}
读者也能将参数显示在属性查看器,然后就可以拖曳Test脚本到属性查看器的gameobject位置,具体可以使用如下的JavaScript代码片段来实现。
1 //设置a DoSomething到target变量指定在属性查看器
2 var target : Test; //声明一个游戏对象
3 function Update (){ //声明Update方法
4 //设置Test对象的a变量
5 target.a = 2;
6 //调用Test的Dosomething
7 target.DoSomething("Hello");
8 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 //设置foo DoSomething到target变量指定在属性查看器
2 using UnityEngine;
3 using System.Collections; //引入系统包
4 public class example : MonoBehaviour { //声明一个example类
5 public Test target; //定义一个Test类型的target变量
6 void Update() { //重写Update函数
7 target.a = 2; //设置target对象的a变量
8 target.DoSomething("Hello"); //调用target的DoSomething方法
9 }}
2.确定对象的层次关系
可以通过Transform组件去找到它的子对象和父对象,具体可以使用如下的JavaScript代码片段来实现。
1 //获得子游戏对象“Hand”
2 var gameobject : GameObject; //声明一个游戏对象
3 function Update (){ //声明Update方法
4 gameobject.transform.Find("Hand").Translate(0, 0, 1);//找到子对象“Hand”并沿Z轴每帧移动1个单位
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 //找到子对象“Hand”,并沿z轴每帧移动1个单位
6 transform.Find("Hand").Translate(0, 0, 1);
7 }}
一旦读者在Hierarchy面板找到Transform,读者就可以通过GetComponent获得其他脚本,例如有一个Test.js脚本挂载在子对象Hand上,具体可以使用如下的JavaScript代码片段来实现。
1 var gameobject : GameObject;
2 function Update (){
3 //找到子对象 "Hand"
4 //获取Test,设置a为2
5 gameobject.transform.Find("Hand").GetComponent(Test).a = 2;
6 //获得子对象"Hand"
7 //调用附属于它的Test的DoSomething
8 gameobject.transform.Find("Hand").GetComponent(Test).DoSomething("Hello");
9 //获得子对象"Hand"
10 //加一个力到刚体上
11 gameobject.transform.Find("Hand").rigidbody.AddForce(0, 0, 2);
12 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Update() { //重写Update函数
5 //找到子对象 "Hand",并得到该对象上的Test脚本组件,同时设置a为2
6 transform.Find("Hand").GetComponent<Test>().a = 2;
7 //找到子对象 "Hand",并得到该对象上的Test脚本组件,同时调用DoSomething函数
8 transform.Find("Hand").GetComponent<Test>().DoSomething("Hello");
9 //找到子对象 "Hand",在该对象的刚体属性上加一个沿z轴的大小为2的力
10 transform.Find("Hand").rigidbody.AddForce(0, 0, 2);
11 }}
也可以使用脚本来循环到所有的子对象,然后对子对象做某种操作,如平移。具体可以使用如下的JavaScript代码片段来实现。
1 //y轴正方向移动所有的子对象5个单位
2 for (var child : Transform in transform) {
3 child.Translate(0, 5, 0);
4 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 //y轴正方向移动所有的子对象5个单位
2 foreach (Transform child in transform) {
3 child.Translate(0, 5, 0);
4 }
如果想要得到更多关于Transform的信息,可以参考相关的文档。
3.指定名字或标签
可以使用GameObject.FindWithTag和GameObject.Find GameObjectsWithTag搜索指定标签的游戏对象;使用GameObject.Find搜索指定名字的游戏对象,具体可以使用如下的JavaScript代码片段来实现。
1 function Start (){
2 //通过名字搜索
3 var name = GameObject.Find("SomeName");
4 name.transform.Translate(0, 0, 1);
5 //通过标签搜索
6 var tag = GameObject.FindWithTag("SomeTag");
7 tag.transform.Translate(0, 0, -1);
8 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Start() { //重写Start函数
5 //找到名称为SomeName的游戏对象
6 GameObject go = GameObject.Find("SomeName");
7 go.transform.Translate(0, 0, 1); //沿z轴平移1
8 //找到Tag为SomeTag的游戏对象
9 GameObject tag = GameObject.FindWithTag("SomeTag");
10 tag.transform.Translate(0, 0, -1); //沿z轴平移-1
11 }}
这样,通过 GetComponent 就能得到指定游戏对象上的任意脚本或组件,具体可以使用如下的JavaScript代码片段来实现。
1 function Start (){
2 //通过名字搜索
3 var name = GameObject.Find("SomeName");
4 name.GetComponent(Test).DoSomething();
5 //通过标签搜索
6 var tag = GameObject.FindWithTag("SomeTag");
7 tag.GetComponent(Test).DoSomething();
8 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明一个example类
4 void Start() { //重写Start函数
5 //找到名称为SomeName的游戏对象
6 GameObject go = GameObject.Find("SomeName");
7 //得到go对象上的Test脚本组件,并调用脚本中的方法
8 go.GetComponent<Test>().DoSomething();
9 //找到Tag为SomeTag的游戏对象
10 GameObject tag = GameObject.FindWithTag("SomeTag");
11 //得到go对象上的Test脚本组件,并调用脚本中的方法
12 tag.GetComponent<Test>().DoSomething();
13 }}
但是这里也有一些特殊的变量,比如主摄像机(Camera.main),即第一个有效的摄像机被标示为主摄像机(只读),如果当前游戏场景中没有主摄像机则返回null。
4.传递参数
一些事件中包含了特殊的信息,例如触发碰撞事件的Collider组件。在OnTiggerStay函数中有一个碰撞体参数,通过这个参数,能得到碰撞的刚体,具体可以使用如下的 JavaScript 代码片段来实现。
1 function OnTriggerStay( other : Collider ) {
2 //如果碰撞体是一个刚体
3 //则给它一个向前的力
4 if (other.rigidbody)
5 other.rigidbody.AddForce(0, 0, 2);
6 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine; //引入系统包
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour {
4 void OnTriggerStay(Collider other) {
5 if (other.rigidbody) //如果碰撞体是刚体,则给它一个向前的力
6 other.rigidbody.AddForce(0, 0, 2);
7 }}
或者通过Collider得到这个对象上挂载的脚本,具体可以使用如下的JavaScript代码片段来实现。
1 function OnTriggerStay( other : Collider ) {
2 //一般碰撞体没有附脚本,所以读者需要首先检查是否为null
3 if (other.GetComponent(Test))
4 //如果其他的碰撞体附加了Test,则调用它的DoSomething方法
5 other.GetComponent(Test).DoSomething();
6 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour {
4 void OnTriggerStay(Collider other) {
5 //如果other对象上存在Test脚本组件,就调用该脚本的方法
6 if (other.GetComponent<Test>())
7 other.GetComponent<Test>().DoSomething();
8
9 }}
说明
在上面的代码中使用后缀方式访问其他变量,也同样能访问到碰撞对象所包含的任意组件。
5.某个类型的脚本
可以使用FindObjectsOfType函数找到特定的组件,具体可以使用如下的JavaScript代码片段来实现。
1 function Start (){
2 //获得附加在场景里的游戏对象的Test组件
3 var test : Test = FindObjectOfType(Test);
4 test.DoSomething();
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour {
4 void Start() { //重写Start函数
5 //找到Test类型的组件
6 Test test = FindObjectOfType(typeof(Test));
7 test.DoSomething(); //调用脚本中的DoSomething函数
8 }}
说明
如果使用FindObjectsOfType函数得到的Test类型的组件不止一个,则返回第一个Test类型脚本组件。
3.4.5 向量
在Unity中使用Vector3表示空间中的所有向量,具体可以使用如下的JavaScript代码片段来实现。
1 var position : Vector3 ;
2 position.x = 3; //x轴分量
3 position.y = 4; //y轴分量
4 position.z = 5; //z轴分量
也可以直接给position赋值,不必分别给x、y、z赋值,具体可以使用如下的JavaScript代码片段来实现。
1 var position : Vector3 ;
2 position = Vector3(3,4,5);
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine; //引入系统包
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour {
4 public Vector3 position = new Vector3(3, 4, 5); //new一个新向量
5 }
Vector3也自定义了一些固定的值,例如Vector3.up等同于Vector3(0,1,0),Vector3.zero等同于Vector3(0,0,0)等,这样可以简化代码。
Vector3类中有很多实用的方法,例如想要获得两点之间的距离时,可以使用Vector3.Distance()方法,具体可以使用如下的JavaScript代码片段来实现。
1 var position1 : Vector3 = Vector3(2,1,3); //声明向量position1
2 var position2 : Vector3 = Vector3(3,4,5); //声明向量position2
3 var theDistance = Vector3.Distance(position1, position2);//求向量position1和position2之间的距离
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour {
4 public Vector3 position1 = new Vector3(2, 1, 3); //声明向量
5 public Vector3 position2 = new Vector3(3, 4, 5); //声明向量
6 //求两个向量之间的距离
7 public float theDistance = Vector3.Distance(position1, position2);
8 }
Vector3也支持运算符,比如:JavaScript中 var position= position1+ position2,C#中 public float theDistance = position1+ position2。
3.4.6 成员变量和全局变量
一般情况下,定义在方法体外的变量是成员变量,这个变量可以在属性查看器查看到,若进行修改,而且它会随着项目一起自动保存,如在JavaScript中:
var a : int = 1;
而在C#脚本中:
public int a = 1;
在属性查看器中可以看到这个变量,名字为“a”,值为“1”,读者可以修改它的值。
如果声明的是一个组件类型的变量(类似GameObject、Transform、Rigidbody等),需要在属性查看器拖曳游戏对象到变量处并确定它的值,具体可以使用如下的JavaScript代码片段来实现。
1 //声明一个敌人对象
2 var enemy: GameObject;
3 function Update(){
4 //如果敌人和初始点的距离小于10
5 if ( Vector3.Distance( enemy.position, Vector3.zero ) < 10 ) {
6 print("I sense the enemy is near!");
7 } }
这样,在属性查看器中直接拖曳敌人对象到 enemy 变量即可。当然,也可以使用如下的 C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 public Transform enemy; //声明一个Transform
5 void Update() { //重写Update方法
6 //如果enemy和transform的距离小于10,则打印出一句话
7 if (Vector3.Distance(enemy.position, transform.position) < 10)
8 print("I sense the enemy is near!");
9 }}
可以通过private创建私有变量,这样在属性查看器中就不会显示该变量,避免错误地修改。具体可以使用如下的JavaScript代码片段来实现。
1 private var lastCollider : Collider;
2 //碰撞检测事件
3 function OnCollisionEnter(collisionInfo : Collision ) {
4 lastCollider = collisionInfo.collider;
5 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 private Collider lastCollider; //声明一个Collider
5 //碰撞检测事件
6 void OnCollisionEnter(Collision collisionInfo) {
7 lastCollider = collisionInfo.collider;
8 }}
也可以通过 static 来创建全局变量,这样就可以在不同脚本间调用这个变量,具体可以使用如下的JavaScript代码片段来实现。
1 //脚本里的静态变量名为 'test'
2 static var test : int = 5;
3 //读者可以像普通变量一样去调用它
4 print(test);
5 test = 1;
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 public static int test = 5; //声明一个静态整型变量
5 print(test); //打印
6 test = 1; //赋值
7 }
如果想从另外一个脚本调用变量 test,读者可以通过“脚本名.变量名”的方式调用,具体可以使用如下的JavaScript代码片段来实现。
1 //脚本名称为“Hello.js”
2 print(Test.test);
3 Test.test = 10;
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class hello : MonoBehaviour { //声明example类
4 void Start() { //重写Start方法
5 print(Test.test); //打印Test.cs脚本中的变量
6 Test.test = 10; //给Test.cs脚本中的变量赋值
7 }}
3.4.7 实例化
Unity 中如果要创建很多相同的物体(比如射击出去的子弹,保龄球瓶等)时,可以通过实例化(Instantiate)快速实现。而且实例化出来的对象包含了这个对象所有的属性,这样就能保证原封不动地创建所需的对象。实例化在 Unity 中有很多用途,充分使用它非常必要。实例化常和预制件(Prefabs)一起使用。
例如,创建一个脚本“Hit.js”,当一个碰撞体撞击到一个物体时,销毁这个物体,并创建一个损坏的物体,具体可以使用如下的JavaScript代码片段来实现。
1 var broke : Transform ;
2 //当一个碰撞发生自毁
3 //在相同位置实例化一个代替物体
4 function OnCollisionEnter (){
5 //撞击发生1s后销毁物体
6 Destroy (broke.gameObject,1);
7 var brokes : Transform ;
8 //在物体原来的位置创建一个和原来物体姿态一样的物体
9 theClonedExplosion = Instantiate(brokes,broke.position, broke.rotation);
10 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 public Transform explosion; //声明explosion变量
5 void OnCollisionEnter() { //重写OnCollisionEnter方法
6 Destroy(gameObject,1); //撞击发生1s后销毁对象
7 Transform theClonedExplosion; //声明变量
8 //在物体原来的位置创建一个和原来物体姿态一样的物体
9 theClonedExplosion = Instantiate(explosion, transform.position,
10 transform.rotation) as Transform;
11 }}
说明
Destroy(gameobject,n)是在n秒后销毁物体,也可以使用DestroyImmediate(gameobject,boolean),这样就可以根据布尔值,判断是否直接销毁物体。
3.4.8 协同程序和中断
如果通过写代码去实现游戏的连续步骤的话,有时会产生大量重复的工作,具体可以使用如下的JavaScript代码片段来实现。
1 private var state = 0;
2 function Update() {
3 if (state == 0) {
4 //做步骤0
5 state = 1;
6 return;
7 }
8 if (state == 1) {
9 // 做步骤1
10 state = 2;
11 return;
12 }
13 // ...
14 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 private int state = 0; //声明state变量
5 void Update() { //重写Update函数
6 if (state == 0) { //做步骤0
7 state = 1;
8 return;
9 }
10 if (state == 1) { // 做步骤1
11 state = 2;
12 return;
13 }}}
这时候,如果使用中断可以更方便,具体可以使用如下的JavaScript代码片段来实现。
1 while(true) {
2 // 做步骤0
3 yield;
4 // 等待一帧
5 // 做步骤1
6 yield;
7 // 等待一帧
8 // ...
9 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 IEnumerator Example() { //声明Example函数
5 while (true) {
6 yield return null; // 等待一帧
7 yield return null; // 等待一帧
8 }}}
说明
在脚本中,中断语句是一个特殊的返回类型,它可以使函数的执行跳到中断语句的下一行。
也可以传递时间值到中断语句,Update函数会在中断时间结束后执行下一语句,具体可以使用如下的JavaScript代码片段来实现。
1 //...
2 yield WaitForSeconds (2.0); //等待2s
3 //...
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 //...
2 yield return new WaitForSeconds(2.0F); //等待2s
3 //...
下面的代码片段先执行 Do()函数,但是 Do()函数之后的 print 语句也会立即执行,具体JavaScript代码片段来实现。
1 Do (); //执行Do函数
2 print ("This is printed immediately"); //打印提示信息
3 function Do (){ //声明Do函数
4 print("Do now"); //打印提示信息
5 yield WaitForSeconds (2); //休眠2s
6 print("Do 2 seconds later"); //打印提示信息
7 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 IEnumerator Do() { //声明Do函数
5 print("Do now"); //打印
6 yield return new WaitForSeconds(2); //等待2s
7 print("Do 2 seconds later"); //再次打印
8 }
9 void Example() { //声明Exame函数
10 Do(); //调用Do方法
11 print("This is printed immediately"); //打印
12 }}
修改上面的代码,使用连接协同程序,那么程序将先执行Do函数,等待,执行完Do函数之后再执行其他语句,具体可以使用如下的JavaScript代码片段来实现。
1 //连接协同程序
2 yield StartCoroutine("Do");
3 print("Also after 2 seconds"); //打印提示信息
4 print ("This is after the Do coroutine has finished execution"); //打印提示信息
5 function Do (){ //声明D函数
6 print("Do now"); //打印提示信息
7 yield WaitForSeconds (2); //休眠2s
8 print("Do 2 seconds later"); //打印提示信息
9 }
当然,也可以使用如下的C#代码片段来实现相同的功能。
1 using UnityEngine;
2 using System.Collections; //引入系统包
3 public class example : MonoBehaviour { //声明example类
4 IEnumerator Do() { //声明Example函数
5 print("Do now"); //打印
6 yield return new WaitForSeconds(2); //等待2s
7 print("Do 2 seconds later"); //再次打印
8 }
9 IEnumerator Example() { //声明Example函数
10 yield return StartCoroutine("Do"); //启动Do协同程序
11 print("Also after 2 seconds"); //打印
12 print("This is after the Do coroutine has finished execution");//打印
13 }}
说明
任何时间处理程序都是协同程序,但是在Update()和FixUpdate()中不能使用协同程序。
3.4.9 一些重要的类
本小节将向读者介绍Unity脚本中的一些重要的类,由于篇幅的限制,所以本小节只对这些类中的比较常用的变量和函数进行简单的介绍说明,其他具体的信息读者可参考官方脚本参考手册。
1.MonoBehaviour类
MonoBehaviour 是每个脚本的基类,其继承自 Behaviour 类。每个 JavaScript 脚本自动继承MonoBehaviour,使用C#时,需要显式继承MonoBehaviour。
MonoBehaviour 类中的方法可以重写,这些方法在两种脚本中只是在方法声明上有一点差别外,其他大致相同。下面介绍一下主要可重写的方法,如表3-2所示。
表3-2 MonoBehaviour类中主要可重写的函数
MonoBehaviour类中有很多的继承函数,下面将介绍主要的继承函数,如表3-3所示。
表3-3 MonoBehaviour类中主要的继承函数
MonoBehaviour类中还有很多继承类函数,下面将介绍主要的继承类函数,如表3-4所示。
表3-4 MonoBehaviour类中主要的继承类函数
说明
读者可能对“继承函数”和“继承类函数”两个概念比较疑惑。“继承函数”指的是其函数都是从父类直接继承或者从父类间接继承而来的函数,而“继承类函数”指的是其函数均从根类Object中间接继承而来,而非从其他父类继承而来。
2.Transform类
场景中的每一个物体都有一个Transform。用于储存并操控物体的位置、旋转和缩放。每一个Transform可以有一个父级,允许你分层次应用位置、旋转和缩放。可以在Hierarchy面板查看层次关系。Transform类中包含了很多变量,下面将介绍主要的变量,如表3-5所示。
表3-5 Transform类中主要的变量
Transform类中同时还有很多函数,下面将介绍主要的函数,如表3-6所示。
表3-6 Transform类中主要的函数
3.Rigidbody类
Rigidbody可以模拟物体在物理效果下的状态。它可以让物体接收力和扭矩,让物体相对真实地移动。如果一个物体想被重力所约束,其必须含有Rigidbody属性。Rigidbody类中包含很多变量,下面将介绍主要的变量,如表3-7所示。
Rigidbody类中包含很多函数,下面将介绍主要的函数,如表3-8所示。
表3-7 Rigidbody类中主要的变量
表3-8 Rigidbody类中主要的函数
4.CharacterController类
角色控制器用于第三人称或者第一人称游戏主角控制。它可以根据碰撞检测判断是否能够移动,而不必给角色控制器添加Rigidbody。而且角色控制器不会受到力的影响,当调用Move函数时,系统会先检查角色控制器是否和其他物体发生碰撞,然后才决定移动与否。
在属性查看器中可以查看到其属性,具体的属性如表3-9所示。
表3-9 Inspector面板中角色控制器的主要属性
CharacterController类中包含很多变量,下面将介绍一下CharacterController类中的主要变量,如表3-10所示。
表3-10 CharacterControler类中的主要变量
CharacterController类中包含很多函数,下面将介绍一下CharacterController类中的主要函数,如表3-11所示。
表3-11 CharacterControler类中的主要函数
3.4.10 性能优化
Unity 中专用的 JavaScript 对性能进行了大量的优化措施,比如使用静态类型,使用#pragma strict,缓存组件查询,使用内建数组以及尽量少调用函数等措施。下面将对各个措施进行介绍。
1.使用静态类型
在JavaScript中,很重要的优化就是使用静态类型代替动态类型。Unity中有一种逻辑推理的技术自动将JavaScript转换为静态脚本,无需做额外的工作。例如:
var a = 1;
此时,Unity会推断a为整形,不需要使用动态名称查找等技术,节省了大量的时间。但是,有些变量是无法被推断的,比如下面的代码片段所示:
1 function Start (){ //声明Start方法
2 var test = GetComponent(Test); //调用Test脚本
3 test.DoSomething(); //执行Test脚本中的DoSomething方法
4 }
因为 test 是未知类型,所以在调用 DoSomeThing()函数之前,Unity 需要检查 test 是否支持DoSomeThing()函数,所以很耗时间。
如果想要获得更好的性能,可将上面的代码改成如下代码片段:
1 function Start () { //声明Start方法
2 var test: Test = GetComponent(Test); //调用Test脚本
3 test.DoSomething(); //执行Test脚本中的DoSomething方法
4 }
说明
在脚本中,声明变量时明确变量的类型,避免了Unity系统对类型的检查,可以获得更快的速度,这对于移动开发更重要。
2.使用#pragma strict
有时可能会在无意识的情况下写出未知类型的变量,如果在脚本的开始加入#pragma strict语句后,Unity就会禁用JavaScript的动态类型,如果脚本中有动态类型的声明,Unity就会报告编译错误。例如,在禁用动态类型的情况下,下面的代码片段在Unity中编译就会报错。
var test = GetComponent(Test);
3.缓存组件查询
这种优化不一定是必须的,但是如果想更高的提升性能的话,优化组件缓冲也是可以考虑的。当通过GetComponent查询一个组件时,可以设置一个私有变量去储存这个组件。这样,Unity无需在每一帧中去查询Transform类型的组件。实现方式可以参考如下代码片段:
1 private var myTransform : Transform ; //声明静态变量
2 function Awake (){ //声明Awake方法
3 myTransform = transform;
4 }
5 function Update (){ //声明Update方法
6 myTransform.Translate(0, 0, 2); //沿z轴每帧移动2个单位
7 }
说明
这种先声明一个私有变量,然后在Awake方法中赋值,把对应的组件对象存放在私有变量的方式同样适用于脚本组件。
4.使用内建数组
虽然ArrayList和Array很容易使用,而且很方便,但是相比内建数组而言,他们的速度还是有很大的差异。内建数组直接嵌入 struct 数据类型存入第一个缓冲区里,不需要其他类型信息或者其他资源。因此做缓存遍历更快捷,如:
1 private var positions : Vector3 []; //声明静态向量
2 function Awake (){ //声明Awake方法
3 positions = new Vector3 [100]; //创建一个向量
4 for (var i=0;i<100;i++){ //执行for循环
5 positions[i] = Vector3.zero ; //为每个向量赋值
6 } }
5.尽量少调用函数
最简单和最有效的优化就是干最少的工作。Unity中Update()函数每一帧都在运行,所以减少Update()函数里面工作量,可以大量优化性能。读者通过协调程序或者加入标志位就能实现。
说明
在实际开发中,一般把标志位检查放在函数外面,这样就无需每一帧都检查标志位,减少了系统性能的消耗。
3.4.11 脚本编译
Unity 可以把脚本编译为.dll 文件,.dll 文件将在运行时编译运行。这样做可以提高执行的速度,比传统的JavaScript脚本要快20倍左右。
脚本具体的编译需要以下4步。
(1)所有的“Standard Assert”、“Pro Standard Assert”或者“Plugins”文件夹里的脚本会被首先编译。
(2)所有的“Standard Assert/Editor”、“Pro Standard Assert/Editor”或者“Plugins/Editor”文件夹里的脚本会被首先编译。
(3)所有在“Editor”文件夹里面的脚本接着被编译。
(4)其他脚本在最后编译。所有这一步里编译的脚本,可以访问第一组提到的所有脚本(即“Standard Assets”、“Pro Standard Assets”或者“Plugins”文件夹里的脚本)。
提示
最后编译的脚本可以访问最先编译的脚本,实现了不同的脚本语言之间的沟通。例如,想在一个JavaScript脚本中引用一个C#脚本,可以将C#脚本放到“Standard Assets”文件夹下,然后将JavaScript脚本放在此文件夹之外,JavaScript脚本便可以直接引用C#脚本。在“WebPlayer Templates”文件夹下的脚本不会被编译。
根据Unity的版本进行编译。在脚本的开始写入一些代码:
1 // 判断Unity的版本
2 #if UNITY_2_6_0
3 //使用2.6.0特性
4 #endif
5 // 判断Unity的版本
6 #if UNITY_2_6
7 // 使用2.6.x特性
8 #endif
说明
上面的代码写在脚本的开始处,然后,当Unity编译器在编译时,是从Unity2.6版本开始的。
这样,就能确保游戏的特性只在对应的版本中使用。这样也可以标记脚本,确保在对应的Unity版本中才能使用。
3.4.12 泛化方法
手册中的一些方法可以进行泛化通过方法名字后面加T或者.<T>。如下面的代码片段:
function FuncName.<T>(): T;
这些就是泛化方法。这对于脚本的意义就是,当调用这个方法时,可以指定参数的类型或者返回值的类型。在JavaScript中,泛化方法可以用于动态类型的局限性:
1 //类型推断正确由于被定义为函数调用
2 var obj = GetComponent.<Rigidbody>();
手册中任何一种方法都可以通过特殊的调用语句进行泛化。