Unity3D脚本中文教程.docx
《Unity3D脚本中文教程.docx》由会员分享,可在线阅读,更多相关《Unity3D脚本中文教程.docx(8页珍藏版)》请在冰豆网上搜索。
Unity3D脚本中文教程
Unity3D脚本中文教程 Part1一、脚本概览这是一个关于Unity部脚本如何工作的简单概览。
Unity部的脚本是通过附加自定义脚本对象到游戏物体组成的。
在脚本对象部不同志的函数被特定的事件调用。
最常用的列在下面Update这个函数在渲染一帧之前被调用这里是大部分游戏行为代码被执行的地方除了物理代码。
FixedUpdate这个函数在每个物理时间步被调用一次这是处理基于物理游戏的地方。
在任何函数之外的代码在任何函数之外的代码在物体被加载的时候运行这个可以用来初始化脚本状态。
注意文档的这个部份假设你是用Javascript参考用C编写获取如何使用C和Boo编写脚本的信息。
你也能定义事件句柄它们的名称都以On开始例如OnCollisionEnter为了查看完整的预定义事件的列表参考MonoBehaviour文档。
概览常用操作大多数游戏物体的操作是通过游戏物体的Transform或Rigidbody来做的在行为脚本部它们可以分别通过transform和rigidbody访问因此如果你想绕着Y轴每帧旋转5度你可以如下写functionUpdatetransform.Rotate050如果你想向前移动一个物体你应该如下写functionUpdatetransform.Translate002概览跟踪时间Time类包含了一个非常重要的类变量称为deltaTime这个变量包含从上一次调用Update或FixedUpdate根据你是在Update函数还是在FixedUpdate函数中到现在的时间量。
所以对于上面的例子修改它使这个物体以一个恒定的速度旋转而不依赖于帧率functionUpdatetransform.Rotate05Time.deltaTime0移动物体functionUpdatetransform.Translate002Time.deltaTime如果你加或是减一个每帧改变的值你应该将它与Time.deltaTime相乘。
当你乘以Time.deltaTime时你实际的表达我想以10米/秒移动这个物体不是10米/帧。
这不仅仅是因为你的游戏将独立于帧而运行同时也是因为运动的单位容易理解。
米/秒另一个例子如果你想随着时间增加光照的围。
下面的表达式以2单位/秒改变半径。
functionUpdatelight.range2.0Time.deltaTime当通过力处理刚体的时候你通常不必用Time.deltaTime因为引擎已经为你考虑到了这一点。
概览访问其他组件组件被附加到游戏物体附加Renderer到游戏物体使它在场景中渲染附加一个Camera使它变为相机物体所有的脚本都是组件因为它们能被附加到游戏物体。
最常用的组件可以作为简单成员变量访问Component可如下访问TransformtransformRigidbodyrigidbodyRendererrendererCameracameraonlyoncameraobjectsLightlightonlyonlightobjectsAnimationanimationCollidercollider…等等。
对于完整的预定义成员变量的列表。
查看ComponentBehaviour和MonnoBehaviour类文档。
如果游戏物体没有你想取的相同类型的组件上面的变量将被设置为null。
任何附加到一个游戏物体的组件或脚本都可以通过GetComponent访问。
transform.Translate030//等同于GetComponentTransform.Translate010注意transfom和Transform之间大小写的区别前者是变量小写后者是类或脚本名称大写。
大小写不同使你能够从类和脚本名中区分变量。
应用我们所学你可以使用GetComponent找到任何附加在同一游戏物体上的脚本和组件请注意要使用下面的例子能够工作你需要有一个名为OtherScript的脚本其中包含一个DoSomething函数。
OtherScript脚本必须与下面的脚本附加到相同的物体上。
//这个在同一游戏物体桑找到名为OtherScript的脚本//并调用它上加的DoSomethingfunctionUpdateotherScriptGetComponentOtherScriptotherScript.DoSomething概览访问其它游戏物体大多数高级的代码不仅需要操作一个物体Unity脚本接口有各种方法来找到并访问其他游戏物体和组件。
在下面我们假定有个一名为OtherScriptjs的脚本附加到场景的游戏物体上。
varfoo5functionDoSomethingparam:
Stringprintparamquotwithfoo:
quotfoo1.通过检视面板赋值引用你可以通过检视面板赋值变量到任何物体//变换拖动到target的物体vartarget:
TransformfunctionUpdatetarget.Translate010你也可以在检视面板中公开到其他物体的引用下面你可以拖动一个包含的游戏物体到检视面板中的target槽。
//设置在检视面板中赋值的target变量上的foo调用DoSomethingvartarget:
OtherScriptfunctionUpdate//设置target物体的foo变量target.foo2//调用target上的DoSomethingtarget.DoSomethingquotHelloquot2.通过物体层次定位对于一个已经存在的物体可以通过游戏物体的Transform组件来找到子和父物体//找到脚本所附加的//游戏物体的子―Hand‖transform.FindquotHandquot.Translate010一旦在层次视图中找到这个变换你可以使用GetComponent来获取其他脚本//找到名为―Hand‖的子//在附加到它上面的OtherScript中设置foo为2transform.FindquotHandquot.Translate010//找到名为―Hand‖的子//然后应用一个力到附加在hand上的刚体transform.FindquotHandquot.GetComponentOtherScript.DoSomethingquotHelloquot//找到名为―Hand‖的了//然后应用一个力到附加在hand上的刚体transform.FindquotHandquot.rigidbody.AddForce0100你可以循环所有的子//变换的所有子向上移动10个单位forvarchild:
Transformintransformchild.Translate010参考Transform类文档获取更多信息。
Part23.根据名称或标签定位.你可以使用GameObject.FindWithTag和GameObject.FindGameObjectsWithTag搜索具有特定标签的游戏物体使用GameObject.Find根据名称查找物体。
functionStart//按照名称vargoGameObject.FindquotSomeGuyquotgo.transform.Translate010//按照标签varplayerGameObject.FindWithTagquotPlayerquotplayer.transform.Translate010你可以在结果上使用GetComponent在找到的游戏物体上得到任何脚本或组件。
functionStart//按名称vargoGameObject.FindquotSomeGuyquotgo.GetComponentOtherScript.DoSomething//按标签varplayerGameObject.FindWithTagquotPlayerquotplayer.GetComponentOtherScript.DoSomething一些特殊的物体有快捷方式如主相机使用Camera.main。
4.作为参数传递一些事件消息在事件包含详细信息。
例如触发器事件传递碰撞物体的Collider组件到处理函数。
OnTriggerStay给我们一个到碰撞器的引用。
从这个碰撞器我们可以获取附加到其上的刚体。
functionOnTriggerStayother:
Collider//如果另一个碰撞器也有一个刚体//应用一个力到它上面ifother.rigidbodyother.rigidbody.AddForce020或者我们可以通过碰撞器获取附加在同一个物体上的任何组件。
functionOnTriggerStayother:
Collider//如果另一个碰撞器附加了OtherScript//调用它上面的DoSomething//大多数时候碰撞器不会附加脚本//所以我们需要首先检查以避免null引用异常ifother.GetComponentOtherScriptother.GetComponentOtherScript.DoSomething注意通过上述例子中的other变量你可以访问碰撞物体中的任何组件。
5.一种类型的所有脚本使用Object.FindObjectsOfType找到所有具有相同类或脚本名称的物体或者使用Object.FindObjectOfType.找到这个类型的第一个物体。
functionStart//找到场景中附加了OtherScript的任意一个游戏物体varother:
OtherScriptFindObjectOfTypeOtherScriptother.DoSomething概览向量Unity使用Vector3类同一表示全体3D向量3D向量的不同组件可以通过想xy和z成员变量访问。
varaPosition:
Vector3aPosition.x1aPosition.y1aPosition.z1你也能够使用Vector3构造函数来同时初始化所有组件。
varaPositionVector3111Vector3也定义了一些常用的变量值。
vardirectionVector3.up//与Vector3010相同单个向量上的操作可以使用下面的方式访问someVector.Normalize使用多个向量的操作可以使用Vector3类的数theDistanceVector3.DistanceoneVectorotherVector注意你必须在函数名之前写Vector3来告诉JavaScript在哪里找到这个函数这适用于所有类函数你也可以使用普通数学操作来操纵向量。
combinedvector1vector2查看Vector3类文档获取完整操纵和可用属性的列表。
概览成员变量amp全局变量变量定义在任何函数之外的变量是一个成员变量。
在Unity中这个变量可以通过检视面板来访问任何保存在成员变量中的值也可以自动随工程保存。
varmemberVariable0.0上面的变量将在检视面板中显示为名为quotMemberVariablequot的数值属性。
如果你设置变量的类型为一个组件类型例如TransformRigidbodyCollider任何脚本名称等等然后你可以在检视面板过拖动一个游戏物体来设置它们。
varenemy:
TransformfunctionUpdateifVector3.Distanceenemy.positiontransform.positionlt10printquotIsensetheenemyisnearquot你也可以创建私有成员变量。
私有成员变量可以用来存储那些在该脚本之外不可见的状态。
私有成员变量不会被保存到磁盘并且在检视面板中不能编辑。
当它被设置为调试模式时它们在检视面板中可见。
这允许你就像一个实时更新的调试器一样使用私有变量。
privatevarlastCollider:
ColliderfunctionOnCollisionEntercollisionInfo:
CollisionlastCollidercollisionInfo.other全局变量你也可以使用static关键字创建全局变量这创造了一个全局变量名为someGlobal//TheScriptName.js中的一个静态变量staticvarsomeGlobal5//你可以在脚本部像普通变量一样访问它printsomeGlobalsomeGlobal1为了从另一个脚本访问它你需要使用这个脚本的名称加上一个点和全局变量名。
printTheScriptName.someGlobalTheScriptName.someGlobal10Part3概览实例化实例化复制一个物体。
包含所有附加的脚本和整个层次。
它以你期望的方式保持引用。
到外部物体引用的克隆层次将保持完好在克隆层次上到物体的引用映射到克隆物体。
实例化是难以置信的快和非常有用的。
因为最大化地使用它是必要的。
例如这里是一个小的脚本当附加到一个带有碰撞器的刚体上时将销毁它自己并实例化一个爆炸物体。
varexplosion:
Transform//当碰撞发生时销毁我们自己//并生成给一个爆炸预设functionOnCollisionEnterDestroygameObjectvartheClonedExplosion:
TransformtheClonedExplosionInstantiateexplosiontransform.positiontransform.rotation实例化通常与预设一起使用概览CoroutinesampYield在编写游戏代码的时候常常需要处理一系列事件。
这可能导致像下面的代码。
privatevarstate0functionUpdateifstate0//做步骤0state1returnifstate1//做步骤1state2return//…更方便的是使用yield语句。
yield语句是一个特殊类型的返回这个确保在下次调用时该函数继续从该yield语句之后执行。
whiletrue//做步骤0yield//等待一帧//做步骤1yield//等待一帧//...你也可以传递特定值给yield语句来延迟Update函数的执行直到一个特定的事件发生。
//做一些事情yieldWaitForSeconds5.0//等待5秒//做更多事情…可以叠加和连接coroutines。
这个例子执行Do在调用之后立即继续。
DoprintquotThisisprintedimmediatelyquotfunctionDoprintquotDonowquotyieldWaitForSeconds2printquotDo2secondslaterquot这个例子将执行Do并等待直到它完成才继续执行自己。
//coroutineyieldStartCoroutinequotDoquotprintquotAlsoafter2secondsquotprintquotThisisaftertheDocoroutinehasfinishedexecutionquotfunctionDoprintquotDonowquotyieldWaitForSeconds2printquotDo2secondslaterquot任何事件处理句柄都可以是一个coroutine注意你不能在Update或FixedUpdate使用yield但是你可以使用StartCoroutine来开始一个函数。
参考YieldInstructionWaitForSecondsWaitForFixedUpdateCoroutineandMonoBehaviour.StartCoroutine获取更多使用yield的信息。
概览用C编写脚本除了语法使用C或者Boo编写脚本还有一些不同。
最需要注意的是1.从MonoBehaviour继承所有的行为脚本必须从MonoBehaviour继承直接或间接。
在Javascript中这自动完成但是必须在C或Boo脚本中显示申明。
如果你在Unity部使用Asset-gtCreate-gtCSharp/BooScript菜单创建脚本创建模板已经包含了必需的定义。
publicclassNewBehaviourScript:
MonoBehaviour...//CclassNewBehaviourScriptMonoBehaviour:
...Boo2.使用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使用yieldreturn…而不是yield…usingSystem.CollectionsusingUnityEnginepublicclassNewBehaviourScript:
MonoBehaviour//CcoroutineIEnumeratorSomeCoroutine//等一帧yieldreturn0//等两秒yieldreturnnewWaitForSeconds25不要使用命名空间目前Unity还不支持将代码放置在一个命名空间中这个需要将会出在未来的版本中。
6.只有序列化的成员变量会显示在检视面板中私有和保护成员变量只在专家模式中显示属性不被序列化或显示在检视面板中。
7避免使用构造函数不要在构造函数中初始化任何变量使用Awake或Start实现这个目的。
即使是在编辑模式中Unity也自动调用构造函数这通常发生在一个脚本被编译之后因为需要调用构造函数来取向一个脚本的默认值。
构造函数不仅会在无法预料的时刻被调用它也会为预设或未激活的游戏物体调用。
单件模式使用构造函数可能会导致严重的后果带来类似随机null引用异常。
因此如果你想实现如一个单件模式不要使用构造函数而是使用Awake。
其实上没有理由一定要在继续自MononBehaviour类的构造函数中写任何代码。
概览最重要的类Javascript中可访问的全局函数或C中的基类移动/旋转物体动画系统刚体FPS或第二人称角色控制器概览性能优化1使用静态类型在使用Javascript时最重要的优化是使用静态类型而不是动态类型Unity使用一种叫做类型推理的技术来自自动转换Javascript为静态类型编码而不需要你做任何工作。
varfoo5在上面的例子里foo会自动被推断为一个整型值。
因此Unity可能使用大量的编译时间来优化。
而不使用耗时的动态名称变量查找等。
这就是为什么Unity比其他在JavaScript的实现平均快20倍的原因之一。
唯一的问题是有时并非一切都可以做类型推断。
Unity将会为这些变量重新使用动态类型。
通过回到动态类型编写JavaScript代码很简单。
但是这也使得代码运行速度较慢。
让我们看一些例子functionStartvarfooGetComponentMyScriptfoo.DoSomething这里foo将是动态类型因此调用DoSomething函数将使用较长时间因为foo的类型是未知的它必须找出它是否支持DoSomething函数如果支持调用它。
functionStartvarfoo:
MyScriptGetComponentMyScriptfoo.DoSomething这里我们强制foo为指定类型你将获得更好的性能。
2使用pragmastrict当然现在问题是你通常没有意识到你在使用动态类型。
pragmastrict解决了这个简单的在脚本顶部添加pragmastrict。
然后unity将在脚本中禁用动态类型强制使用静态类型如果一个类型未知。
Unity将报告编译错误。
那么在这种情况下foo将在编译时产生一个错误pragmastrictfunctionStartvarfooGetComponentMyScriptfoo.DoSomething3.缓存组件查找另一个优化是组件缓存。
不幸的是该优化需要一点编码并且不一定是值得的但是如果你的脚本是真的用了很长时间了你需要把最后一点表现出来这是一个很好的优化。
当你访问一个组件通过GetComponent或访问变量Unity会通过游戏对象找到正确的组件。
这一次可以很容易地通过缓存保存在一个私有变量里引用该组件。
简单地把这个functionUpdatetransform.Translate005变成privatevarmyTransform:
TransformfunctionAwakemyTransformtransformfunctionUpdatemyTransform.Translate005后者的代码将运行快得多因为Unity没有找到变换在每一帧游戏组件中的对象。
这同样适用于脚本组件在你使用GetComponent代替变换或者其它的东西。
4.使用置数组置数组的速度非常快所以请使用它们。
而整列或者数组类更容易使用因为你可以很容易地添加元素他们几乎没有相同的速度。
置数组有一个固定的尺寸但大多数时候你事先知道了最大的大小在可以只填写了以后。
关于置数组最好的事情是他们直接嵌入在一个结构紧凑的缓冲区的数据类型没有任何额外的类型信息或其他开销。
因此遍历是非常容易的作为一切缓存在存中的线性关系。
privatevarpositions:
Vector3functionAwakepositionsnewVector3100forvari0ilt100ipositionsiVector3.zero5.如果你不需要就不要调用函数最简单的和所有优化最好的是少工作量的执行。
例如当一个敌人很远最完美的时间就是敌人入睡时可以接受。
直到玩家靠近时什么都没有做。
这是种缓慢的处理方式的情况functionUpdate//早期进行如果玩家实在是太遥远。
ifVector3.Distancetransform.positiontarget.positiongt100returnperformrealworkwork...这不是一个好主意因为Unity必须调用更新功能而你正在执行工作的每一个帧。
一个比较好的解决办法是禁用行为直到玩家靠近。
有3种方法来做到这一点1.使用OnBecameVisible和