这是一个关于Unity内部脚本如何工作的简单概览。
Unity内部的脚本,是通过附加自定义脚本对象到游戏物体组成的。在脚本对象内部不同志的函数被特定的事件调用。最常用的列在下面:
Update:这个函数在渲染一帧之前被调用,这里是大部分游戏行为代码被执行的地方,除了物理代码。
FixedUpdate:这个函数在每个物理时间步被调用一次,这是处理基于物理游戏的地方。
在任何函数之外的代码:
在任何函数之外的代码在物体被加载的时候运行,这个可以用来初始化脚本状态。
注意:文档的这个部份假设你是用Javascript,参考用C#编写获取如何使用C#和Boo编写脚本的信息。
你也能定义事件句柄,它们的名称都以On开始,(例如OnCollisionEnter),为了查看完整的预定义事件的列表,参考MonoBehaviour 文档。
常用操作
大多数游戏物体的操作是通过游戏物体的Transform或Rigidbody来做的,在行为脚本内部它们可以分别通过transform和rigidbody访问,因此如果你想绕着Y轴每帧旋转5度,你可以如下写:
function Update(){
transform.Rotate(0,5,0);
}
如果你想向前移动一个物体,你应该如下写:
function Update(){
transform.Translate(0,0,2);
}
跟踪时间
Time类包含了一个非常重要的类变量,称为deltaTime,这个变量包含从上一次调用Update或FixedUpdate(根据你是在Update函数还是在FixedUpdate函数中)到现在的时间量。
所以对于上面的例子,修改它使这个物体以一个恒定的速度旋转而不依赖于帧率:
function Update(){
transform.Rotate(0,5*Time.deltaTime,0);
}
移动物体:
function Update(){
transform. Translate (0, ,0,2*Time.deltaTime);
}
如果你加或是减一个每帧改变的值,你应该将它与Time.deltaTime相乘。当你乘以Time.deltaTime时,你实际的表达:我想以10米/秒移动这个物体不是10米/帧。这不仅仅是因为你的游戏将独立于帧而运行,同时也是因为运动的单位容易理解。( 米/秒)
另一个例子,如果你想随着时间增加光照的范围。下面的表达式,以2单位/秒改变半径。
function Update (){
light.range += 2.0 * Time.deltaTime;
}
当通过力处理刚体的时候,你通常不必用Time.deltaTime,因为引擎已经为你考虑到了这一点。
访问其他组件
组件被附加到游戏物体,附加Renderer到游戏物体使它在场景中渲染,附加一个Camera使它变为相机物体,所有的脚本都是组件,因为它们能被附加到游戏物体。
最常用的组件可以作为简单成员变量访问:
Component 可如下访问
Transform transform
Rigidbody rigidbody
Renderer renderer
Camera camera (only on camera objects)
Light light (only on light objects)
Animation animation
Collider collider
…等等。
对于完整的预定义成员变量的列表。查看Component,Behaviour和MonnoBehaviour类文档。如果游戏物体没有你想取的相同类型的组件,上面的变量将被设置为null。
任何附加到一个游戏物体的组件或脚本都可以通过GetComponent访问。
transform.Translate(0,3,0); //等同于
GetComponent(Transform).Translate(0, 1, 0);
注意transfom和Transform之间大小写的区别,前者是变量(小写),后者是类或脚本名称(大写)。大小写不同使你能够从类和脚本名中区分变量。
应用我们所学,你可以使用GetComponent找到任何附加在同一游戏物体上的脚本和组件,请注意要使用下面的例子能够工作,你需要有一个名为OtherScript的脚本,其中包含一个DoSomething函数。OtherScript脚本必须与下面的脚本附加到相同的物体上。
function Update(){
otherScript = GetComponent(OtherScript); //这个在同一游戏物体桑找到名为OtherScript的脚本
otherScript.DoSomething(); //并调用它上加的DoSomething
}
向量
Unity使用Vector3类同一表示全体3D向量,3D向量的不同组件可以通过想x,y和z成员变量访问。
var aPosition : Vector3;
aPosition.x = 1;
aPosition.y = 1;
aPosition.z = 1;
你也能够使用Vector3构造函数来同时初始化所有组件。
var aPosition = Vector3(1, 1, 1);
Vector3也定义了一些常用的变量值。
var direction = Vector3.up; // 与 Vector3(0, 1, 0);相同
单个向量上的操作可以使用下面的方式访问:
someVector.Normalize();
使用多个向量的操作可以使用Vector3类的数;
theDistance = Vector3.Distance(oneVector, otherVector);
(注意你必须在函数名之前写Vector3来告诉JavaScript在哪里找到这个函数,这适用于所有类函数)
你也可以使用普通数学操作来操纵向量。
combined = vector1 + vector2;
查看Vector3类文档获取完整操纵和可用属性的列表。
成员变量 & 全局变量
定义在任何函数之外的变量是一个成员变量。在Unity中这个变量可以通过检视面板来访问,任何保存在成员变量中的值也可以自动随工程保存。
var memberVariable = 0.0;
上面的变量将在检视面板中显示为名为"Member Variable"的数值属性。
如果你设置变量的类型为一个组件类型(例如Transform, Rigidbody, Collider,任何脚本名称,等等)然后你可以在检视面板中通过拖动一个游戏物体来设置它们。
var enemy : Transform;
function Update()
{
if ( Vector3.Distance( enemy.position, transform.position ) < 10 );
print("I sense the enemy is near!");
}
}
你也可以创建私有成员变量。私有成员变量可以用来存储那些在该脚本之外不可见的状态。私有成员变量不会被保存到磁盘并且在检视面板中不能编辑。当它被设置为调试模式时,它们在检视面板中可见。这允许你就像一个实时更新的调试器一样使用私有变量。
private var lastCollider : Collider;
function OnCollisionEnter( collisionInfo : Collision ) {
lastCollider = collisionInfo.other;
}
全局变量
你也可以使用static关键字创建全局变量,这创造了一个全局变量,名为someGlobal
static var someGlobal = 5;// 你可以在脚本内部像普通变量一样访问它
print(someGlobal);// 'TheScriptName.js'中的一个静态变量
someGlobal = 1;
为了从另一个脚本访问它,你需要使用这个脚本的名称加上一个点和全局变量名。
print(TheScriptName.someGlobal);
TheScriptName.someGlobal = 10;
用C#编写脚本
除了语法,使用C#或者Boo编写脚本还有一些不同。最需要注意的是:
1.从MonoBehaviour继承
所有的行为脚本必须从MonoBehaviour继承(直接或间接)。在Javascript中这自动完成,但是必须在C#或Boo脚本中显示申明。如果你在Unity内部使用Asset -> Create -> C Sharp/Boo Script菜单创建脚本,创建模板已经包含了必需的定义。
public class NewBehaviourScript : MonoBehaviour {...} // C#
class NewBehaviourScript (MonoBehaviour): ... # Boo
2.使用Awake或Start函数来初始化
Javascript中放置在函数之外的代码,在C#或Boo中要放置在Awake或Start中。
Awake和Start的不同是Awake在场景被加载时候运行,而Start在第一次调用Update或FixedUpdate函数之前被调用,所有Awake函数在任何Start函数调用之前被调用。
3.类名必须与文件名相同
Javascript中,类名被隐式地设置为脚本的文件名(不包含文件扩展名)。在c#和Boo中必须手工做。
4.在C#中Coroutines有不同语法。
Coroutines必有一个IEnumerator返回类型,并且yield使用yield return… 而不是yield…
using System.Collections;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
// C# coroutine
IEnumerator SomeCoroutine ()
{
yield return 0;// 等一帧
yield return new WaitForSeconds (2);//等两秒
}
}
5.不要使用命名空间
目前Unity还不支持将代码放置在一个命名空间中,这个需要将会出在未来的版本中。
6.只有序列化的成员变量会显示在检视面板中
私有和保护成员变量只在专家模式中显示,属性不被序列化或显示在检视面板中。
7.避免使用构造函数
不要在构造函数中初始化任何变量,使用Awake或Start实现这个目的。即使是在编辑模式中Unity也自动调用构造函数,这通常发生在一个脚本被编译之后,因为需要调用构造函数来取向一个脚本的默认值。构造函数不仅会在无法预料的时刻被调用,它也会为预设或未激活的游戏物体调用。
单件模式使用构造函数可能会导致严重的后果,带来类似随机null引用异常。
因此如果你想实现,如,一个单件模式,不要使用构造函数,而是使用Awake。其实上,没有理由一定要在继续自MononBehaviour类的构造函数中写任何代码。
最重要的类
Javascript中可访问的全局函数或C#中的基类
移动/旋转物体
动画系统
刚体
FPS或第二人称角色控制器
脚本编译在4个步骤中执行:
1.所有在"Standard Assets", "Pro Standard Assets" 或 "Plugins"的脚本被首先编译。
在这些文件夹之内的脚本不能直接访问这些文件夹之外脚本。不能直接引用或它的 变量,但是可以使用GameObject.SentMessage与它们通信。
2.所有在"Standard Assets/Editor", "Pro Standard Assets/Editor" 或 "Plugins/Editor"的脚本被首先编译。
如果你想使用UnityEditor命名空间你必须放置你的脚本在这些文件夹中,例如添加菜单项或自定义的向导,你都需要放置脚本到这些文件夹。这些脚本可以访问前一组中的脚本。
3.然后所有在"Editor"中的脚本被编译。
如果你想使用UnityEditor命名空间你必须放置你的脚本在这些文件夹中。例如添加菜单单项或自定义的向导,你都需要放置脚本到这些文件夹。这些脚本可以访问所有前面组中的脚本,然而它们不能访问后面组中的脚本。这可能会是一个问题,当编写编辑器代码编辑那些在后面组中的脚本时。有两个解决方法:1、移动其他脚本到"Plugins"文件夹 2、利用JavaScript的动态类型,在javascript中你不需要知道类的类型。在使用GetComponent时你可以使用字符串而不是类型。你也可以使用SendMessage,它使用一个字符串。
4.所有其他的脚本被最后编译
所有那些没有在上面文件夹中的脚本被最后编译。所有在这里编译的脚本可以访问第一个组中的所有脚本("Standard Assets","ProStandard Assets" or "Plugins")。这允许你让不同的脚本语言互操作。例如,如果你想创建一个JavaScript。它使用一个C#脚本;放置C#脚本到"Standard Assets"文件夹并且JavaScript放置在"Standard Assets"文件夹之外。现在JavaScript可以直接引用c#脚本。放置在第一个组中的脚本,将需要较长的编译时间,因为当他们被编译后,第三组需要被重新编译。因此如果你想减少编译时间,移动那些不常改变的到第一组。经常改变的到第四组。