Unity3D内部脚本编程入门
来源:第三维度
整理:海涛
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米 /帧。这不仅仅是因为你的游戏将独立于帧率运行,同时也是因为运动的单位容易理解。( 10米 /秒)
另一个例子,如果你想随着时间增加光照的范围。下面的表达式,以2单位/秒改变半径.
function Update() {
light.range += 2.0 * Time.deltaTime;
}
当通过力处理刚体的时候,你通常不必用Time.deltaTime乘,因为引擎已经为你考虑到了这一点。
访问其他组件
组件被附加到游戏物体。附加Renderer到游戏物体使它在场景中渲染,附加一个Camera使它变为相机物体。所有的脚本都是组件,因为它们能被附加到游戏物体。
最常用的组件可以作为简单成员变量访问:
Component
可如下访问
Transform
Rigidbody
Renderer
Camera (only on camera objects)
Light (only on light objects)
Animation
Collider
... 等等。
对于完整的预定义成员变量的列表,查看Component, Behaviour和MonoBehaviour类文档。如果游戏物体没有你想取回类型的组件,上面的变量将被设置为null。
任何附加到一个游戏物体的组件或脚本都可以通过GetComponent访问。
transform.Translate(0, 1, 0);
// 等同于
GetComponent(Transform).Translate(0, 1, 0);
注意transform和Transform之间大小写的区别. 前者是变量(小写),后者是类或脚本名称(大写). 大小写不同使你能够从类和脚本名中区分变量。
应用我们所学,你可以使用GetComponent找到任何附加在同一游戏物体上的脚本和组件。请注意要使下面的例子能够工作,你需要有一个名为OtherScript的脚本,其中包含一个DoSomething函数。OtherScript脚本必须与下面的脚本附加到相同的游戏物体上。
//这个在同一个游戏物体上找到名为OtherScript的脚本
//并调用它上面的DoSomething.
function Update () {
otherScript = GetComponent(OtherScript);
otherScript.DoSomething();
}
访问其他游戏物体
大多数高级的代码不仅需要操作一个物体。Unity脚本接口有各种方法来找到并访问其他游戏物体和组件。在下面,我们假定有一个名为OtherScript.js的脚本附加到场景的游戏物体上。
var foo = 5;
function DoSomething ( param : String) {
print(param + " with foo: " + foo);
}
1. 通过检视面板赋值引用.
你可以通过检视面板赋值变量到任何物体。
// 变换拖动到target槽的物体
var target : Transform;
function Update ()
{
target.Translate(0, 1, 0);
}
你也可以在检视面板中公开到其他物体的引用。下面你可以拖动一个包含OtherScript的游戏物体到检视面板中的target槽。
// 设置在检视面板中赋值的target变量上的foo,调用DoSomething.
var target : OtherScript;
function Update ()
{
//设置target物体的foo变量
target.foo = 2;
// 调用target上的DoSomething
target.DoSomething("Hello");
}
2. 通过物体层次定位.
对于一个已存在的物体,可以通过游戏物体的Transform组件来找到子和父物体:
// 找到脚本所附加的
// 游戏物体的子"Hand"
transform.Find("Hand").Translate(0, 1, 0);
一旦在层次视图中找到这个变换,你可以使用GetComponent来获取其他脚本.
//找到名为"Hand"的子.
//在附加到它上面的OtherScript中, 设置foo为2.
transform.Find("Hand").GetComponent(OtherScript).foo = 2;
//找到名为"Hand"的子.
//调用附加到它上面的OtherScript脚本中的DoSomething.
transform.Find("Hand").GetComponent(OtherScript).DoSomething("Hello");
//找到名为"Hand"的子.
//然后应用一个力到附加在hand上的刚体.
transform.Find("Hand").rigidbody.AddForce(0, 10, 0);
你可以循环所有的子。
// 变换的所有子向上移动10个单位!
for (var child : Transform in transform)
{
child.Translate(0, 1, 0);
}
参考Transform类文档获取更多信息。
3.根据名称或标签定位.
你可以使用GameObject.FindWithTag和GameObject.FindGameObjectsWithTag搜索具有特定标签的游戏物体。使用GameObject.Find根据名称查找物体.
function Start ()
{
// 按照名称
var go = GameObject.Find("SomeGuy");
go.transform.Translate(0, 1, 0);
// 按照标签
var player = GameObject.FindWithTag("Player");
player.transform.Translate(0, 1, 0);
}
你可以在结果上使用GetComponent,在找到的游戏物体上得到任何脚本或组件。
function Start ()
{
// 按名称
var go = GameObject.Find("SomeGuy");
go.GetComponent(OtherScript).DoSomething();
//按标签
var player = GameObject.FindWithTag("Player");
player.GetComponent(OtherScript).DoSomething();
}
一些特殊的物体有快捷方式,如主相机使用Camera.main。
4. 作为参数传递.
一些事件消息在事件中包含详细信息。例如,触发器事件传递碰撞物体的Collider组件到处理函数。
OnTriggerStay给我们一个到碰撞器的引用。从这个碰撞器我们可以获取附加到其上的刚体。
function OnTriggerStay (other : Collider ) {
//如果另一个碰撞器也有一个刚体
// 应用一个力到它上面!
if (other.rigidbody) {
other.rigidbody.AddForce(0, 2, 0);
}
}
或者我们可以通过碰撞器获取附加在同一物体上的任何组件。
function OnTriggerStay (other : Collider ) {
// 如果另一个碰撞器附加了OtherScript
// 调用它上面的DoSomething.
// 大多数时候碰撞器不会附加脚本,
// 所以我们需要首先检查以避免null引用异常。
if (other.GetComponent(OtherScript)) {
other.GetComponent(OtherScript).DoSomething();
}
}
注意通过上述例子中的other变量,你可以访问碰撞物体中的任何组件。
5.一种类型的所有脚本
使用Object.FindObjectsOfType找到所有具有相同类或脚本名称的物体,或者使用Object.FindObjectOfType找到这个类型的第一个物体。
function Start ()
{
// 找到场景中附加了OtherScript的任意一个游戏物体.
var other : OtherScript = FindObjectOfType(OtherScript);
other.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。
// 'TheScriptName.js'中的一个静态变量
static var someGlobal = 5;
// 你可以在脚本内部像普通变量一样访问它
print(someGlobal);
someGlobal = 1;
为了从另一个脚本访问它,你需要使用这个脚本的名称加上一个点和全局变量名。
print(TheScriptName.someGlobal);
TheScriptName.someGlobal = 10;
实例化
实例化,复制一个物体。包含所有附加的脚本和整个层次。它以你期望的方式保持引用,到外部物体引用的克隆层次将保持完好,在克隆层次上到物体的引用将映射到克隆物体。
实例化是难以置信的快和非常有用的。因此最大化地使用它是必要的。
例如,这里是一个小的脚本,当附加到一个带有碰撞器的刚体上时将销毁它自己并实例化一个爆炸物体。
var explosion : Transform;
// 当碰撞发生时销毁我们自己
// 并生成一个爆炸预设
function OnCollisionEnter ()
{
Destroy (gameObject);
var theClonedExplosion : Transform;
theClonedExplosion = Instantiate(explosion,transform.position, transform.rotation);
}
实例化通常与预设一起使用。