Unity3D脚本中文教程 Part1 一、 脚本概览 这是一个关于Unity内部脚本如何工作的简单概览。 Unity内部的脚本是通过附加自定义脚本对象到游戏物体组成的。在脚本对象内部不同志的函数被特定的事件调用。最常用的列在下面 Update 这个函数在渲染一帧之前被调用这里是大部分游戏行为代码被执行的地方除了物理代码。 FixedUpdate 这个函数在每个物理时间步被调用一次这是处理基于物理游戏的地方。 在任何函数之外的代码 在任何函数之外的代码在物体被加载的时候运行这个可以用来初始化脚本状态。 注意文档的这个部份假设你是用Javascript参考用C编写获取如何使用C和Boo编写脚本的信息。 你也能定义事件句柄它们的名称都以On开始例如OnCollisionEnter为了查看完整的预定义事件的列表参考MonoBehaviour 文档。 概览常用操作 大多数游戏物体的操作是通过游戏物体的Transform或Rigidbody来做的在行为脚本内部它们可以分别通过transform和rigidbody访问因此如果你想绕着Y轴每帧旋转5度你可以如下写 function Update transform.Rotate050 如果你想向前移动一个物体你应该如下写 function Update transform.Translate002 概览跟踪时间 Time类包含了一个非常重要的类变量称为deltaTime这个变量包含从上一次调用Update或FixedUpdate根据你是在Update函数还是在FixedUpdate函数中到现在的时间量。 所以对于上面的例子修改它使这个物体以一个恒定的速度旋转而不依赖于帧率 function Update transform.Rotate05Ti
me.deltaTime0 移动物体 function Update transform. Translate 0 02Time.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 …等等。 对于完整的预定义成员变量的列表。查看ComponentBehaviour和MonnoBehaviour类文档。如果游戏物体没有你想取的相同类型的组件上面的变量将被设置为null。 任何附加到一个游戏物体的组件或脚本都可以通过GetComponent访问。 transform.Translate030 //等同于 GetComponentTransform.Translate0 1 0 注意transfom和Transform之间大小写的
区别前者是变量小写后者是类或脚本名称大写。大小写不同使你能够从类和脚本名中区分变量。 应用我们所学你可以使用GetComponent到任何附加在同一游戏物体上的脚本和组件请注意要使用下面的例子能够工作你需要有一个名为OtherScript的脚本其中包含一个DoSomething函数。OtherScript脚本必须与下面的脚本附加到相同的物体上。 //这个在同一游戏物体桑到名为OtherScript的脚本 //并调用它上加的DoSomething function Update otherScript GetComponentOtherScript otherScript.DoSomething 概览访问其它游戏物体 大多数高级的代码不仅需要操作一个物体Unity脚本接口有各种方法来到并访问其他游戏物体和组件。在下面我们假定有个一名为OtherScriptjs的脚本附加到场景的游戏物体上。 var foo 5 function DoSomething param : String printparam quot with foo: quot foo 1.通过检视面板赋值引用 你可以通过检视面板赋值变量到任何物体 //变换拖动到target的物体 var target : Transform function Update target.Translate0 1 0 你也可以在检视面板中公开到其他物体的引用下面你可以拖动一个包含的游戏物体到检视面板中的target槽。 //设置在检视面板中赋值的target变量上的foo调用DoSomething var target : OtherScript function Update //设置target物体的foo变量 target.foo 2 // 调用target上的DoSomething target.DoSomethingquotHelloquot 2.通过物体层次定位 对于一个已经存在的物体可以通过
游戏物体的Transform组件来到子和父物体 //到脚本所附加的 //游戏物体的子―Hand‖ transform.FindquotHandquot.Translate0 1 0 一旦在层次视图中到这个变换你可以使用GetComponent来获取其他脚本 //到名为―Hand‖的子 //在附加到它上面的OtherScript中设置foo为2 transform.FindquotHandquot.Translate0 1 0 //到名为―Hand‖的子 //然后应用一个力到附加在hand上的刚体 transform.FindquotHandquot.GetComponentOtherScript.DoSomethingquotHelloquot // 到名为―Hand‖的了 // 然后应用一个力到附加在hand上的刚体 transform.FindquotHandquot.rigidbody.AddForce0 10 0 你可以循环所有的子 //变换的所有子向上移动10个单位 for var child : Transform in transform child.Translate0 1 0 参考Transform类文档获取更多信息。 Part 2 3.根据名称或标签定位. 你可以使用GameObject.FindWithTag和GameObject.FindGameObjectsWithTag搜索具有特定标签的游戏物体使用GameObject.Find根据名称查物体。 function Start // 按照名称 var go GameObject.ansform.Translate0 1 0 // 按照标签 var player GameObject.ansform.Translate0 1 0 你可以在结果上使用GetComponent在到的游戏物体上得到任何脚本或组件。 function Start // 按名称 va
r go GameObject.FindquotSomeGuyquot go.GetComponentOtherScript.DoSomething // 按标签 var player GameObject.FindWithTagquotPlayerquot player.GetComponentOtherScript.DoSomething 一些特殊的物体有快捷方式如主相机使用Camera.main。 4.作为参数传递 一些事件消息在事件包含详细信息。例如触发器事件传递碰撞物体的Collider组件到处理函数。 OnTriggerStay给我们一个到碰撞器的引用。从这个碰撞器我们可以获取附加到其上的刚体。 function OnTriggerStay other : Collider // 如果另一个碰撞器也有一个刚体 // 应用一个力到它上面 if other.rigidbody other.rigidbody.AddForce0 2 0 或者我们可以通过碰撞器获取附加在同一个物体上的任何组件。 function OnTriggerStay other : Collider // 如果另一个碰撞器附加了OtherScript // 调用它上面的DoSomething // 大多数时候碰撞器不会附加脚本 // 所以我们需要首先检查以避免null引用异常 if other.GetComponentOtherScript other.GetComponentOtherScript.DoSomething 注意通过上述例子中的other变量你可以访问碰撞物体中的任何组件。 5.一种类型的所有脚本 使用Object.FindObjectsOfType到所有具有相同类或脚本名称的物体或者使用Object.FindObjectOfType.到这个类型的第一个物体。 function Start // 到场景中附加了OtherScript的任意一个游戏物体 var other : Othe
rScript FindObjectOfTypeOtherScript other.DoSomething 概览向量 Unity使用Vector3类同一表示全体3D向量3D向量的不同组件可以通过想xy和z成员变量访问。 var aPosition : Vector3 aPosition.x 1 aPosition.y 1 aPosition.z 1 你也能够使用Vector3构造函数来同时初始化所有组件。 var aPosition Vector31 1 1 Vector3也定义了一些常用的变量值。 var direction Vector3.up // 与 Vector30 1 0相同 单个向量上的操作可以使用下面的方式访问 someVector.Normalize 使用多个向量的操作可以使用Vector3类的数 theDistance Vector3.DistanceoneVector otherVector 注意你必须在函数名之前写Vector3来告诉JavaScript在哪里到这个函数这适用于所有类函数 你也可以使用普通数学操作来操纵向量。 combined vector1 vector2 查看Vector3类文档获取完整操纵和可用属性的列表。 概览成员变量 amp 全局变量变量 定义在任何函数之外的变量是一个成员变量。在Unity中这个变量可以通过检视面板来访问任何保存在成员变量中的值也可以自动随工程保存。 var memberVariable 0.0 上面的变量将在检视面板中显示为名为quotMember Variablequot的数值属性。 如果你设置变量的类型为一个组件类型例如Transform Rigidbody Collider任何脚本名称等等然后你可以在检视面板中通过拖动一个游戏物体来设置它们。 var enemy : Transform function Update if Vector3.Distance enemy.position transform.position lt 10 prin
tquotI sense the enemy is nearquot 你也可以创建私有成员变量。私有成员变量可以用来存储那些在该脚本之外不可见的状态。私有成员变量不会被保存到磁盘并且在检视面板中不能编辑。当它被设置为调试模式时它们在检视面板中可见。这允许你就像一个实时更新的调试器一样使用私有变量。 private var lastCollider : Collider function OnCollisionEnter collisionInfo : Collision her 全局变量 你也可以使用static关键字创建全局变量 这创造了一个全局变量名为someGlobal // TheScriptName.js中的一个静态变量 static var someGlobal 5 // 你可以在脚本内部像普通变量一样访问它 printsomeGlobal someGlobal 1 为了从另一个脚本访问它你需要使用这个脚本的名称加上一个点和全局变量名。 printTheScriptName.someGlobal TheScriptName.someGlobal 10 Part3 概览实例化 实例化复制一个物体。包含所有附加的脚本和整个层次。它以你期望的方式保持引用。到外部物体引用的克隆层次将保持完好在克隆层次上到物体的引用映射到克隆物体。 实例化是难以置信的快和非常有用的。因为最大化地使用它是必要的。 例如 这里是一个小的脚本当附加到一个带有碰撞器的刚体上时将销毁它自己并实例化一个爆炸物体。 var explosion : Transform // 当碰撞发生时销毁我们自己 // 并生成给一个爆炸预设 function OnCollisionEnter Destroy gameObject var theClonedExplosion : Transform theClonedExp
losion Instantiateexplosion transform.ation 实例化通常与预设一起使用 概览Coroutines amp Yield 在编写游戏代码的时候常常需要处理一系列事件。这可能导致像下面的代码。 private var state 0 function Update if state 0 // 做步骤0 state 1 return if state 1 // 做步骤1 state 2 return // … 更方便的是使用yield语句。yield语句是一个特殊类型的返回这个确保在下次调用时该函数继续从该yield语句之后执行。 whiletrue // 做步骤0 yield //等待一帧 // 做步骤1 yield //等待一帧 // ... 你也可以传递特定值给yield语句来延迟Update函数的执行直到一个特定的事件发生。 // 做一些事情 yield WaitForSeconds5.0 //等待5秒 //做更多事情… 可以叠加和连接coroutines。 这个例子执行Do在调用之后立即继续。 Do print quotThis is printed immediatelyquot function Do printquotDo nowquot yield WaitForSeconds 2 printquotDo 2 seconds laterquot 这个例子将执行Do并等待直到它完成才继续执行自己。 //链接coroutine yield StartCoroutinequotDoquot printquotAlso after 2 secondsquot print quotThis is after the Do coroutine has finished executionquot function Do printquotDo nowquot yield WaitForSeconds 2 printquotDo 2 seconds laterquot 任何事件处理句柄都可以是一个coroutine 注意你不能在Update或FixedUpdate内使用yield但是你可以使用StartCoroutine来开始一个函数。 参考YieldInstruction WaitForSeconds WaitForF
ixedUpdate Coroutine and MonoBehaviour.StartCoroutine获取更多使用yield的信息。 概览用Cunity3d入门编写脚本 除了语法使用C或者Boo编写脚本还有一些不同。最需要注意的是 1.从MonoBehaviour继承 所有的行为脚本必须从MonoBehaviour继承直接或间接。在Javascript中这自动完成但是必须在C或Boo脚本中显示申明。如果你在Unity内部使用Asset -gt Create -gt 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或第二人称角控制器 概览性能优化 1使用静态类型 在使用Javascript时最重要的优化是使用静态类型而不是动态类型Unity使用一种叫做类型推理的技术来自自动转换Javascript为静态类型编码而不需要你做任何工作。 var foo5 在上面的例子里foo会自动被推断为一个整型值。因此Unity可能使用大量的编译时间来优化。而不使用耗时的动态名称变量查等。这就是为什么Unity比其他在JavaScript的实现平均快20倍的原因之一。 唯一的问题是有时并非一切都可以做类型推断。Unity将会为这些变量重新使用动态类型。通过
回到动态类型编写JavaScript代码很简单。但是这也使得代码运行速度较慢。 让我们看一些例子 function Start var foo GetComponentMyScript foo.DoSomething 这里foo将是动态类型因此调用DoSomething函数将使用较长时间因为foo的类型是未知的它必须出它是否支持DoSomething函数如果支持调用它。 function Start var foo : MyScript GetComponentMyScript foo.DoSomething 这里我们强制foo为指定类型你将获得更好的性能。 2使用pragma strict 当然现在问题是你通常没有意识到你在使用动态类型。pragma strict解决了这个简单的在脚本顶部添加pragma strict。然后unity将在脚本中禁用动态类型强制使用静态类型如果一个类型未知。Unity将报告编译错误。那么在这种情况下foo将在编译时产生一个错误 pragma strict function Start var foo GetComponentMyScript foo.DoSomething 3.缓存组件查 另一个优化是组件缓存。不幸的是该优化需要一点编码并且不一定是值得的但是如 果你的脚本是真的用了很长时间了你需要把最后一点表现出来这是一个很好的优化。 当你访问一个组件通过GetComponent或访问变量Unity会通过游戏对象到正确的组件。这一次可以很容易地通过缓存保存在一个私有变量里引用该组件。 简单地把这个 function Update transform.Translate0 0 5 变成 private var myTransform : Transform function Awake myTransform transform function Update myTransform.Translat
e0 0 5 后者的代码将运行快得多因为Unity没有到变换在每一帧游戏组件中的对象。这同样适用于脚本组件在你使用GetComponent代替变换或者其它的东西。 4.使用内置数组 内置数组的速度非常快所以请使用它们。 而整列或者数组类更容易使用因为你可以很容易地添加元素他们几乎没有相同的速度。内置数组有一个固定的尺寸但大多数时候你事先知道了最大的大小在可以只填写了以后。关于内置数组最好的事情是他们直接嵌入在一个结构紧凑的缓冲区的数据类型没有任何额外的类型信息或其他开销。因此遍历是非常容易的作为一切缓存在内存中的线性关系。 private var positions : Vector3 function Awake positions new Vector3100 for var i0ilt100i 5.如果你不需要就不要调用函数 最简单的和所有优化最好的是少工作量的执行。例如当一个敌人很远最完美的时间就是敌人入睡时可以接受。直到玩家靠近时什么都没有做。这是种缓慢的处理方式的情况 function Update // 早期进行如果玩家实在是太遥远。 if Vector3.Distancetransform.position target.position gt 100 return perform real 这不是一个好主意因为Unity必须调用更新功能而你正在执行工作的每一个帧。一个比较好的解决办法是禁用行为直到玩家靠近。有3种方法来做到这一点 1.使用OnBecameVisible和OnBecameInvisible。这些回调都是绑到渲染系统的。只要任何相机可以看到物体OnBecameVisible将被调用当没有相机看
到任何一个OnBecameInvisible将被调用。这种方法在很多情况下非常有用但通常在AI中并不是特别有用因为只要你把相机离开他们敌人将不 可用。 function OnBecameVisible enabled true function OnBecameInvisible enabled false 2.使用触发器。一个简单的球形触发器会工作的非常好。一旦你离开这个影响球你将得到OnTriggerEnter/Exit调用。 function OnTriggerEnter c : Collider if c.CompareTagquotPlayerquot enabled true function OnTriggerExit c : Collider if c.CompareTagquotPlayerquot enabled false 3.使用协同程序。Update调用的问题是它们每帧中都发生。很可能会只需要每5秒检检查一次到玩家的距离。这应该会节省大量的处理周期。 概览脚本编译高级 Unity编译所有的脚本为.NET dll文件.dll将在运行时编译执行。 这允许脚本以惊人的速度执行。这比传统的javascript快约20倍。比原始的C代码慢大约50。在保存的时候Unity将花费一点时间来编译所有脚本如果Unity还在编译。你可以在Unity主窗口的右下角看到一个小的旋转进度图标。 脚本编译在4个步骤中执行 1.所有在quotStandard Assetsquot quotPro Standard Assetsquot 或 quotPluginsquot的脚本被首先编译。 在这些文件夹之内的脚本不能直接访问这些文.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论