脚本
脚本基础
1. 在Unity中,脚本可以理解为附加在游戏对象上用于定义游戏对象行为的指令代码,脚本和组件的用法相同,必须绑定在游戏对象上才能开始它的生命周期。
2. 脚本之间可以相互访问和进行函数调用。在Unity中,为对象添加脚本,就像给对象添加其他组件一样,可以为对象带来交互性。在Unity中,使用脚本一般需要三步:
- 创建脚本。
- 将脚本添加到一个或者多个游戏对象上。
- 如果脚本需要,就用值或者其他游戏对象填充脚本的属性值。
3. 在Unity和C#中,文件名必须要和其中的类名相匹配。类名和文件名中不允许有空格。例如在脚本中执行:print("Hello World"),则挂在对象上运行游戏后,在Project视图的左下角和Console窗口中将会显示输出语句。
4. Unity中脚本常见的必然事件
C#语法
1. C#中数据类型:
2. 在Unity的C#脚本中有两种访问修饰符可以使用:private和public。一般来说,私有变量(使用关键字private修饰的变量)只能在创建它们的文件内使用,其他脚本和编辑器无法看到它们或者以任何方式修改它们。公共变量对于其他脚本甚至Unity编辑器都是可见的,这样你就可以很容易地在Unity内随意修改变量的值。如果没有为变量添加修饰符,那么默认情况下它是私有的。
3. 在C#中只能使用内建数组。例如:
public int[] array = new int[5];
4. C#中虽然不能使用Array数组,但是可以使用ArrayList,List等容器来达到同样的目的。
5. C#中关键字ref申明为引用传入参数可有效提升效率。注意:在函数调用时的实参函数前也需要添加ref。
6. 另外参数前可以使用关键字out实现返回多个数值。out和ref的区别在于使用ref时,必须先对值进行初始化。
输入
InputManager
1. Edit->ProjectSettings->Input可以查看和修改InputManager。
2. 默认有18个轴,可以自定义数目映射。
2. 每个轴对应的属性
3. 在脚本中Update函数里面可输入:
void Update()
{
float hVal = Input.GetAxis("Horizontal");
float vVal = Input.GetAxis("Vertical");
if (hVal != 0)
print("Horizontal movement selected: " + hVal);
if(vVal != 0)
print("Vertical movement selected: " + vVal);
}
4. 当移动键盘中箭头或者w,s,a,d键将会输出对应的不同内容。这段代码必须在Update函数中,因为它每一帧都要检查用户输入。
如果没有看到任何输出,请用鼠标点击Game窗口,用于激活窗口,让输入生效,然后再重复操作一遍。
特定键的输入
1. GetKey函数在按下时返回true,没有按下时返回false。另外GetKeyDown和GetKeyUp可判断某个键是否被按下或释放。
void Update()
{
if (Input.GetKey(KeyCode.M))
print("The M Key is pressed down");
if (Input.GetKeyDown(KeyCode.O))
print("The O Key was pressed");
if (Input.GetKeyUp(KeyCode.O))
print("The O Key was released");
}
鼠标输入
1. 判断某个鼠标是否被按下。
void Update()
{
bool isMouseButtonDown = false;
isMouseButtonDown = Input.GetMouseButtonDown(0);//left mouse button
isMouseButtonDown = Input.GetMouseButtonDown(1);//right mouse button
isMouseButtonDown = Input.GetMouseButtonDown(2);//center mouse button
}
2. 判断鼠标的移动距离。
void Update()
{
//返回对应的前后两帧的距离值
float mxVal = Input.GetAxis("Mouse X");
float myVal = Input.GetAxis("Mouse Y");
if (mxVal != 0)
print("Mouse X movement selected: " + mxVal);
if (myVal != 0)
print("Mouse Y movement selected:" + myVal);
}
访问对象和组件
在Unity场景中出现的所有物体都属于游戏对象(GameObject)。
访问局部组件
1. 对象由多个组件组成。每个对象都有一个transform组件,还有很多可选的组件,比如Render、Light和Camera。脚本也是一个组件,将这些组合在一起就可以为游戏对象提供行为。
2. 可以在运行时通过脚本与组件进行交互。首先,需要获得想要操作的对象的引用,为了提升效率需要在Start()函数中完成这个操作,然后将这个组件保存在一个变量中。
3. GetComponent<Type>()方法使用一对尖括号指明要查找的组件,比如Light、Camera,脚本名等。GetComponent()返回游戏对象上第一个指定类型的组件。
4. 访问Transform
(1) 最常用的组件非Transform莫属。修改这个组件的属性,可以让游戏对象在屏幕上移动。记住,对象的Transform由平移、旋转和缩放组成。虽然可以直接修改这些属性,但是最好使用游戏内置的方法,比如Translate()、Rotate()以及localScale变量。
(2) 因为每个对象都有一个Transform组件,所以不需要显式的查找操作,可以直接操作transform这个组件。它是唯一一个可以这样操作的组件,其他组件必须使用GetComponent方法。
(3) 因为Translate()和Rotate()都是方法,所以如果把它们放在Update()中,游戏对象就会持续累加效果,但是LocalScale是属性定义,不会一直累加。
访问其他对象
1. 很多情况下,都需要在脚本中查找其他对象并访问它们的组件。需要首先找到对象,然后调用合适的组件。
2. 查找其他对象最简单的方式就是使用编辑器。在GameObject类型的类级别创建一个公共变量,然后我们只需要将想要使用的对象拖动到Inspector视图中的对应属性上即可。
3. 另外一种查找游戏对象的方式是使用Find方法。一般来说,如果想让设计师将某些对象联系在一起,或者这些对象之间的关系是可选的,那么就使用Inspector让这些对象之间建立联系。但是,如果这些对象必须要建立联系,那么我们就使用Find方法。脚本中有三种主要的查找方式:使用名称,使用标签,或者使用类型。
(1) 使用名称查找:GameObject.Find("对象名称");对象的名称就是Hierarchy视图中显示的名称。
- 缺点:如果存在多个对象,它只会返回指定名称的第一个对象;如果不存在指定名称的游戏对象,则返回null。
- 注意Find()方法的速度特别慢,因为它会遍历场景中的所有对象,直到找到匹配的对象。在一些大型场景中,这个方法会显著降低游戏效率,导致游戏帧率降低。所以,如果可以,尽量避免使用这个方法。话虽这么说,但是这个方法还是有它的使用场景。比如在Start()中调用Find(),然后将查找结果保存下来,这样就可以显著降低Find()带来的副作用。
(2) 另外一种查找游戏对象的方式是通过tag。GameObject.FindWithTag("Tag名称")
- 如果场景中存在指定标签的游戏对象,那么返回该对象的引用,否则返回空值null;如果有多个游戏对象使用同一标签,那么返回第一个对象的引用。
- 如果游戏场景中有多个相同标签的游戏对象,可以通过FindGameObjectWithTag("Tag名称")方法获取游戏对象数组。
- 对象的tag与对象的层级很类似,唯一的区别就是它们的语义。layer(层级)一般指的是对象的大分类,而tag指的是对象的标识。
- 使用Tag Manager(Edit>Project Settings>Tags&Layers)或者“Add Tag”可以创建tag。
- 创建之后在对象的Insprctor视图中设置对应的Tag标记即可。
- 当对象添加了一个tag之后,就可以使用FindWithTag()方法查找对象。
修改其他对象组件
1. 一旦拥有了另外一个对象的引用,那么操纵被引用的对象的组件就跟操纵脚本所在的对象上的组件一样简单。唯一的区别就是:之前只需要直接使用组件的名称,但是现在要先写变量名然后再操作这个组件。
常用组件及其变量
1. 如果游戏对象上不存在某组件,则该组件对应变量的值将为null。
2. 如果要访问的组件不属于上表中的常用组件,或者访问的时游戏对象中的脚本(脚本属于自定义组件),可以通过如下函数来得到组件的引用。
事件响应
常用事件响应函数
常用脚本API
Transform组件
1. Transform组件控制游戏对象在Unity场景中的位置,旋转和大小比例,每个游戏对象都包含一个Transform组件。
2. Transform组件的成员变量。
3. Transform中成员函数
Time类
1. 在Unity中可以通过Time类来获取和时间有关的信息,可以用来计算帧速率,调整时间流逝速度等功能。
2. Time类成员变量如下表所示:
Random类
1. Random类可以用来生成随机数。
2. Random类成员变量:
3. Random类成员函数:
Mathf类
1. Unity中封装了常用的数学操作。
2. Mathf类的变量:
3. Mathf类的常用方法:
Coroutine协同程序
1. Coroutine被称为协程,协同程序可以和主程序并行运行,和多线程有些类似。
2. 与协同程序有关的函数:
3. 在C#中,协同函数的返回类型必须为IEnumerator,yield要用yield return来代替,并且启动协同程序用StartCoroutine函数。
脚本优化方案
由于脚本将消耗大量的开发时间,学习一些最佳实践将非常有益。脚本是一个非常宽泛的术语,因此尽量将内容限制在非常统一的情况下,集中在围绕着MonoBehaviours单行为、游戏对象和相关功能的问题上。
在脚本中进行优化主要是如何将性能增强应用于以下领域:
(1) 访问组件。
(2) 组件回调(Update()、Awake()等)
(3) Coroutines协同程序
(4) GameObject和Transform的使用。
(5) Object之间的交流。
(6) 数学计算。
(7) 反序列化,如场景和预制加载。
在每种情况下,我们将探索如何以及为什么会出现性能问题,出现问题的示例情况,以及解决问题的一个或多个解决方案。
一. 使用最快的方法获取组件。
1. GetComponent()方法有几个变体,每个变体都有不同的性能成本,因此调用这个方法的最快版本是比较明智的。可用的三种重载是GetComponent(string)、GetComponent<T>() 和GetComponent(typeof(T))。
2. 事实证明,最快的版本取决于我们运行的是Unity的哪个版本,因为多年来我们对这些方法进行了多次优化;然而,在Unity 5的所有后续版本和Unity 2017的最初版本中,最好使用GetComponent<T>()变体。
3. 某次测试的验证结果:
(1) 可以看到,GetComponent<T>()方法只比GetComponent(typeof(T))快一点点,而GetComponent(string)则比其他方法慢得多。因此,使用基于类型的GetComponent()版本是非常安全的,因为性能差异很小。
(2) 但是,我们应该确保从不使用GetComponent(string),因为结果是相同的,而且所产生的成本没有任何好处。
(3) 有一些非常罕见的例外,比如如果我们正在为Unity编写一个自定义调试控制台,它可能会解析一个用户输入的字符串来获取一个组件。在任何情况下,这些将只用于调试和诊断情况,在这些情况下,性能不是太重要。对于生产级应用程序,使用GetComponent(string)只是一种不必要的浪费。