安卓 Unity 游戏开发入门手册(一)
一、编程概念
编程就是解决一个问题并为其定义一个解决方案。每一个细节都是精心制作的,试图将解决方案传达给计算机。对于一些,特定的指令被给予计算机系统,以执行导致期望的解决方案的任务。
幸运的是,有高级编程语言来帮助我们用更接近英语而不是 0 和 1 的语言编写这些解决方案。确切地说,编程语言是一套规则,它提供了一种指示计算机执行什么操作的方法。
让我们考虑一个类比。(参见图 1-1 。)假设我们有几个水果,必须用它们做沙拉。第一步是分析和定义问题。具体来说,必须识别输入数据(水果)并将其转化为预期输出数据(沙拉)。
第二步是规划。程序员使用的一种技术是流程图,它是一个问题的逐步解决方案的图形表示。它们帮助我们关注程序逻辑,而不是我们将要使用的编程语言的适当语法。
第三步是实际编写程序。第二步中的逻辑现在必须转换成计算机可以理解的东西。存在各种集成开发环境(ide)来帮助程序员用他们选择的编程语言编码。IDE 就像一个文本编辑器,但是有几个附加的特性来帮助程序的开发,比如自动完成或者调试器。自动完成是一种功能,通过这种功能,句子会自动用 IDE 下一步期望的关键字来完成,同时调试器会帮助运行程序并可能找到错误。
第四步也是最后一步是测试程序。可能存在一些错误,为了检测它们,必须输入不同类型的测试数据,并且输出必须与预期的结果一致。调试是指检测、定位和纠正错误。这些错误可能是语法错误或逻辑错误,例如,前者是计算机无法理解的拼写错误的指令,后者就像告诉计算机重复操作,但不告诉它如何停止重复。
本章将引导你了解许多流行的编程概念,但是为了简单起见,很多内容将被搁置。
图 1-1
做沙拉
Note
您还不能运行下面的代码片段,但是不要担心。在我们进入后续章节的 Unity 编码之前,它们只是给你一些基本的理论。
1.1 变量、常数和类型
在编程中,数据可以有许多不同的类型。由于我们将使用 Unity 游戏引擎,代码片段(部分)将用 C#来表达。在这一章中,我们将只考虑四种类型的数据:整型、浮点型、布尔型和字符串型。
1.1.1 整数数据类型
整数(没有小数部分)的数值可以用整数形式表示。整数值也可以是负值。
10, 2667, -50, 0
1.1.2 浮点数类型
有小数部分的数值可以用浮点形式表示。浮点值也可以是负值。
3.9874, 1.245, -112.245, 0.0932
布尔数据类型
布尔值具有值true或false。
1.1.4 字符串数据类型
字符集可以用字符串形式表示。
"Unity", "192.168.100.0", "The big brown fox"
变量
可以对各种数据执行不同的操作。从长远来看,利用变量可能是个好主意。变量是一种保存特定类型数据的特殊容器。打个比方,一个变量可能是一个衣柜,用来装衣服,而不是其他东西——比如说,不是厨房用具。在 C#中,声明变量的经典方式如下:
<variableType> <variableName> = <value>;
在 C#中,特定数据类型的变量不能保存另一种数据类型的值。为了清楚起见,要声明上述数据类型的变量并为其赋值,必须设置类似于以下内容的内容:
int myInteger = 10;
float myFloat = 3.9874f;
bool myBool = false;
string myString = "Unity";
注意,对于浮点变量,f必须附加在 float 值的末尾,以确保它被解释为 float(而不是 double 数据类型)。在 double 数据类型中,值以 64 位(最多 16 位)存储,这对于我们将要处理的值的类型来说不是很有用。使用 float 数据类型还可以提供更高的整体性能,因为数据是以 32 位(7 位数)存储的。
如果变量在初始化时没有值,例如bool condition,它们所使用的数据类型的默认值将被赋给它们,在本例中为false。对于整数和浮点数,默认值将分别等于0和0.0f。对于字符串,这将是""(一个空白和空字符串)。
如果一个变量已经包含一个值,并且给它赋了一个新值,那么这个变量的内容将被覆盖,并且它将包含新赋的值,直到程序结束,除非给它赋了另一个值。
int pin = 1234;
pin = 4321;
// pin now holds a value of 4321.
注意,如果一个变量已经被声明了,就没有必要用它的数据类型来引用它。这将在 1.7 节中详细讨论。
常数
常数就像变量一样,只是在声明后不能修改,并且将保持它们初始化时的值。声明常量的过程与声明变量的过程类似,除了关键字const必须放在数据类型字段之前。
const <variableType> <variableName> = <value>;
评论
注释是脚本中程序员可读的注释或解释,通常使用户更容易理解代码的某个部分是做什么的。注释通常会被编译器/解释器忽略。在 C#中,注释可以用单行或多行的方式编写。
// This is a single-line comment.
/* This is a
multiline comment.*/
1.2 阵列
数组是一种类似于变量的数据存储结构形式,因为它被声明为保存特定的数据类型。然而,与变量不同,数组可以保存多个数据值。创建数组时,会为其设置一个预定义的大小。因此,数组将保存与其大小相等的数据值的数量。
与变量一样,数组中索引(位置)处的数据值可以被相同类型的其他数据值读取、修改或替换。数组的索引从 0(第一个数据值)开始,最后一个索引将等于数组的大小减 1,因为第一个索引不是 1。
1.2.1 声明和创建数组
如果声明的数组没有等号后面的部分,那么它的大小将为零。以后可以修改数组的大小,但是存储在每个索引中的数据值将被重置为数组使用的数据类型的默认值。
<arrayType>[] <arrayName> = new <arrayType>[<size>];
// create an integer array of a size of 5 named firstArray
int[] firstArray = new int[5];
// create a string array with a size of 0 named secondArray
string[] secondArray;
// creating a new string array with a size of 10 and assigning it to secondArray
secondArray = new string[10];
声明数组的另一种方式是在开始时用值初始化它们。
<arrayType>[] <arrayName> = {<value0>, <value1>};
int[] firstArray = {5, 10, 20, 35, 45};
string[] secondArray;
secondArray = {"abc", "def", "ghi"};
要获得数组的长度,即它可以存储多少个值,可以调用Length方法。
int[] firstArray = {5, 10, 20, 35, 45};
int arraySize = firstArray.Length; // 5
1.2.2 设置、获取和修改数组中的值
在这一节中,我们将看看如何设置和修改数组中的索引值。
<arrayName>[<index>] = <value>;
<variable> = <arrayName>[<index>];
在下面的例子中,值13将被存储在正在使用的整数数组(firstArray)的第三个位置(索引 2)。通过将存储在索引 0 ( 7)和索引 4 ( 6)的数据值相加来获得值13,其中大小- 1指的是索引 4。
int[] firstArray;
int size = 5;
firstArray = new int[size];
firstArray[0] = 7;
firstArray[4] = 6;
firstArray[2] = firstArray[0] + firstArray[size - 1];
1.3 算术运算符
在编程中执行算术运算是很常见的。基本的算术运算是加、减、乘和除。可以使用这些运算符来处理数值。这也适用于保存数值的变量和常量(int、float)。算术运算符必须有两个操作数,并且它们的倍数可以链接成一个等式。操作数是与操作符一起使用的任何东西。例如,加法语句中的数字是操作数,加号是运算符。遵循运算顺序:首先计算括号中的运算,然后是除法和乘法运算,最后是加法和减法运算。
1.3.1 加减乘除
这些可以像你在数学课上学到的一样使用。
1 + 1; // 2
9 -6; // 3
4 * 2; // 8
60 / 12; // 5
6 * 2 + 3; // 15
5 * (3 - 1) + 1; // 11
变量和常数
如前所述,算术运算可以通过使用保存数值的变量或常数来完成。
int number = 1;
number + 1; // 2
number + 8 - 6; // 3
算术运算的结果也可以存储在数字类型的变量中。
int number = 2;
number = number * 2; // 4
number * 4; // 16
number = number + 56; // 60
number / 12; // 5
模量
这是另一个有用的算术运算符。它返回整数除法的余数。正如前面的操作符一样,它也可以用于变量和常量。
11 % 4; // 3
复合赋值运算符
有一种简单的方法可以将一个操作的值赋给该操作中涉及的变量。
|例子
|
相等的
| | --- | --- | | 绵羊+= 5; | 羊=羊+5; | | 绵羊-= 5; | 羊=羊-5; | | 绵羊*-= 5; | 羊=羊* 5; | | 绵羊/= 5; | 羊=羊/5; | | 绵羊% = 5; | 绵羊=绵羊% 5; |
快速递增和递减
变量可以快速递增或递减 1。
int count = 5;
count++; // 6
count++; // 7
count--; // 6
一元运算
一元运算是只有一个操作数的运算。由于一元运算只有一个操作数,因此它们在包含它们的其他运算之前被求值。这通常适用于正运算符和负运算符。例如,+8 + -2 和+2 - 3 分别相当于 8 - 2 和 2 + 5。
铸造
例如,当执行算术运算时,可能会获得不期望类型的输出。例如,如果程序员将一个变量声明为一个整数,他们将在其中存储 5 * 1.61f 的结果,将获得一个不能存储在该变量中的浮点结果。
这是选角出现的情况之一。基本上,强制转换将特定数据类型的值转换为相同的值,但属于指定的数据类型。将浮点数转换为整数不会对值进行舍入。只返回整数部分。
int perfectInt;
float badFloat;
badFloat = 5 * 1.61f; // 7.55f
perfectInt = (int)badFloat; // 7
在前面的例子中,我们将变量badFloat的值转换为perfectInt。然而,我们可以直接执行算术运算,并在perfectInt变量中直接将其转换为整数。对于需要浮点结果的算术运算,不需要将涉及的整数(无论是变量还是原始值)转换为浮点数据类型
铸造在某些情况下也可能不适用。例如,由字母组成的 stringvalue 不能转换为整数或浮点数。
1.4 逻辑运算符
C#中的逻辑运算符是用于连接表达式的符号,因此生成的复合表达式的值取决于原始表达式的值以及所用逻辑运算符的含义。
简单的布尔表达式
布尔表达式总是产生true或false。例如,问一个问题,比如 5 大于 2 吗?会导致yes。在编程中,这将被写成 5 > 2,返回的结果布尔值将是true。
标志
|
解释
| | --- | --- | | == | 等于 | | != | 不等于 | | > | 大于 | | < | 小于 | | >= | 大于或等于 | | <= | 小于或等于 |
4 > 5 // false
180 < 450 // true
60 >= 70 // false
567 <= 550 // false
330 != 80 // true
45 > 45 // false
40 < 40.1f // true
90 >= 90 // true
1 == 2 // false
"Cat" == "Dog" // false
"cat" == "Cat" // false
"Shoes" == "Shoes" // true
"Car" != "Plane" // true
1.4.2 和(&&)
AND逻辑运算符有两个操作数。如果操作数的结果是true,它将返回一个值true。
(1 == 1) && (5 > 4) // true
(3 > 4) && (80 >= 79) && (50 > 40) // false
1.4.3 或(||)
OR逻辑运算符有两个操作数。如果它的操作数中至少有一个导致true,它将返回一个值true。
(3 < 4) || ( 255 == 256) // true
(4 > 5) || (40 < 50) || ("abc" == "def") // false
1.4.4 不是(!)
NOT逻辑运算符只接受一个操作数。它将一个布尔值转换成它的对应物。即如果其操作数的布尔值为true,则返回false。
!(1 == 2) // true
!((4 <= 8) && (6.1 >= 6)) // false
1.5 选择
通常,脚本由许多行代码组成。指令序列被一个接一个地执行。有时,我们希望只根据特定的条件运行特定的指令。这就是选择的来源。通过问一些问题,我们可以让计算机系统根据答案做一些事情。控制结构帮助我们改变编程中的流程。每个控件结构都需要缩进放在其中的代码。缩进实际上只是空格或制表符。
1.5.1 如果是,则控制结构
在这个选择控制结构中,将在代码运行的正常流程中检查所有指定子句的条件,直到找到导致true的第一个条件,或者如果所有子句都导致false。如果某个子句的条件导致true,那么它的代码块将会运行,而所有其他子句将会被跳过,因为只有一个指定的子句可以运行。
使用一个非常简单的if then else,一些代码只有在指定条件导致true时才能执行。
if (condition) {
// Do something
}
string apple = "fruit";
int numberOfFruits = 0;
if (apple == "fruit") {
numberOfFruits++;
}
如果if子句中的条件导致false,也可以指定else关键字来运行另一个代码块。
if (condition) {
// Do something
} else {
// Do something else instead
}
string apple = "fruit";
int numberOfFruits = 0;
int numberOfVegetables = 0;
if (apple == "fruit") {
numberOfFruits++;
} else {
numberOfVegetables++;
}
此外,if和else子句可以一起使用,仅在一个块中创建多个条件。注意最后一个else从句可以省略。
if (condition) {
// Do something
} else if (another condition) {
// Do something
} else {
// Do something else instead
}
string gender = "M";
int males = 0;
int females = 0;
int undefined = 0;
if (gender == "M") {
males++;
} else if (gender == "F") {
females++;
} else {
undefined++;
}
最后,if then else控制结构可以“嵌套”到其他if then else控制结构中。
if (condition) {
if (condition) {
// Do something
}
}
string species = "human";
string furColor = "";
int brownFur = 0;
int blackFur = 0;
// != is interpreted as "is not equal to"
if (species != "human") {
if (furColor == "brown") {
brownFur++;
} else if (furColor == "black") {
blackFur++;
}
}
1.5.2 案例控制结构
一个case选择控制结构与一个变量一起工作,根据它包含的值,可以运行特定的代码。将验证所有的case子句,直到其中一个子句的条件导致true;否则,将运行最后一个子句中的代码default。如果其中一个case子句有一个导致true的条件,那么相应的代码块将会运行,所有剩余的子句都不会被验证,因此会被跳过。每个子句中都需要break关键字。这种形式的控制结构在必须执行许多检查的情况下很有用,因为它提供了比链接许多if - else语句更优雅的解决方案。
switch(variable) {
case value:
// Do something
break;
default:
// Do something else then
break;
}
string apple = "fruit";
int numberOfFruits = 0;
int numberOfVegetables = 0;
int numberOfExceptions = 0;
switch (apple) {
case "fruit":
numberOfFruits++;
break;
case "vegetable":
numberOfVegetables++;
break;
default:
numberOfExceptions++;
break;
}
1.6 迭代
通常,脚本的某些部分可能需要重复多次。为了使程序更容易阅读、修改和调试,并且在某些情况下,为了使算法更有效,可以使用循环控制结构,而不是复制代码 x 次。基于预定义的条件,位于循环控制结构子句的花括号之间的代码将保持运行。然而,如果一个条件总是保持true,循环将无限运行并导致程序崩溃。
while 循环
本质上,在一个while循环中的代码不断重复自己while一些条件是true。
while (condition) {
// Do something
}
在下面的例子中,while循环运行了五次。
int number = 1;
while (number < 6) {
number++;
}
用于循环
编写一个for循环比编写一个while循环要复杂一些。与后者不同,它不只是有一个条件。相反,它包括声明一个变量,设置一个条件,如果条件不为真,则导致循环停止运行,最后,设置变量每次增加/减少的量。
for (<declareVariable>; <condition>; <variableIncrement>)
{
// Do something
}
例如,for循环可用于执行从 2 到 10 的所有偶数的求和。基本上,变量i被声明为一个整数,最初被赋予值 2,每次循环运行时,它的值将增加 2,并添加到变量 sum,直到i的值大于 10。
int evenSum = 0;
for (int i = 2; i <= 10; i += 2) {
evenSum += i;
}
1.6.3 foreach 循环
这是对for循环稍加修改的版本,更常用于数组之类的结构。与其编写一个for循环来遍历一个数组的所有值,不如编写一个foreach循环,这样可以更快地完成工作。不像常规的for循环,其中数组的大小必须为条件部分写入,这对于foreach循环是不必要的。
foreach (<dataType> <newVariableName> in <arrayName>) {
// Do something
}
例如,假设我们有一个由float值组成的数组,并希望得到该数组中所有元素的总和。foreach循环将遍历数组值的每个元素,并且每次自动将当前元素分配给临时float变量temp,该变量本身将被添加并存储在变量sum中。
float[] values = {66.3f, 346.21f, 45.8f, 890.8f, 556.99f};
float sum = 0.0f;
foreach (float temp in values) {
sum += temp;
}
作为参考,这里是一个普通的for循环中的等价函数。注意values.Length返回值 5,但是循环不能等于这个,因为数组的最后一个索引会是 4。
float[] values = {66.3f, 346.21f, 45.8f, 890.8f, 556.99f};
float sum = 0.0f;
for (int i = 0; i < values.Length; i++) {
float temp = values[i];
sum += temp;
}
1.6.4 继续和中断
关键字continue告诉循环跳过任何剩余的代码,直接跳到循环的下一次迭代。一个简单的例子是遍历一个食物数组,并简单地跳转到下一个迭代实例,以避免在当前位置的数组元素不是水果时增加一个integer变量。
string[] food = {"fruit", "human", "fruit", "vegetable", "shoes"};
int fruits;
foreach (string current in food) {
if (current != "fruit") {
continue;
}
fruits++;
}
关键字break立即结束循环的执行,并跳转到循环之后和循环之外的代码行。例如,一旦在数组中找到一个特定的值,就可以用它来结束循环的执行,因为继续检查该数组的其余元素是徒劳的。
string[] alphabets = {"a", "b", "c", "d", "e", "f"};
string alphabetToSearchFor = "c";
int alphabetRealPosition = 0;
for (int i = 0; i < alphabets.Length; i++) {
if (alphabets[i] == alphabetToSearchFor) {
alphabetRealPosition = i + 1;
break;
}
}
1.7 功能
您可能拥有数百或数千行代码的脚本。虽然您可以在一个连续的流程中编写所有内容,但是将代码包装在一个函数中做一件特定的事情会使代码看起来更整洁,也更容易管理。在前面的“迭代”部分(1.6),我们学习了如何通过循环来减少代码重复。但是如果一些代码必须在程序的不同部分再次运行多次呢?这就是函数的用武之地。
基础知识
函数包含代码,在脚本中需要的部分,可以“调用”它们来执行编写它们要做的操作。
void <functionName> () {
// Do something
}
注意,不返回值的函数,也就是说,脚本不期望从它们那里得到值,具有类型void。一个void函数完成它的任务,然后将控制返回给调用者。
一个简单的函数可以用来向用户显示一些东西。当然,功能不会自己运行。这就是为什么在我们代码运行的主流程中,我们必须调用它。Debug.Log函数将输出我们传递给它的string的日志消息。
PrintHelloWorld();
void PrintHelloWorld() {
Debug.Log(“Hello World”);
}
参数
有时,我们可能还希望将值传递给函数,以便它们使用这些值执行操作,例如,如果我们希望将该函数用于类似的操作,但使用不同的值。我们传递给函数的值被称为参数,它们必须与实际参数具有相同的数据类型。
void <functionName> (<parameterType> <parameterName>) {
// Do something
}
修改前面的示例,我们现在希望函数打印作为参数传递的三个数字的总和。传递的值由函数中的局部名称标识,以便可以引用它们。求和的结果是 11,并将在日志消息中输出。
int a = 5;
int b = 4;
PrintSum(a, b, 2);
void PrintSum(int first, int second, int third) {
Debug.Log(first + second + third);
}
返回一个值
在前面的例子中,我们的函数只执行一个操作。它们没有真正意义上的“返回”一个值。未声明为void的函数可以返回一个值,该值可以分配给变量或用于控制流程。例如,我们可以直接调用if子句中的函数,而不是让boolean变量保存一个在if else条件下使用的函数返回的boolean值。注意,当一个函数返回一个值时,该值前面必须有return关键字,任何代码,如果留在该函数中,都不会运行。
<functionType> <functionName> (<parameterType> <parameterName>) {
// Do something
return <value>;
}
再拿前面的例子来说,这次我们把函数的返回值赋给c。
int a = 5;
int b = 4;
int c = 0;
c = Add3Numbers(a, b, 2);
int Add3Numbers(int first, int second, int third) {
return first + second + third;
}
让我们考虑另一个例子,这次用一个boolean值。如果变量eat不包含值fruit,函数将返回false。
string food = "fruit";
int numberFruits = 0;
if (itsAFruit(food)) {
numberFruits++;
}
bool itsAFruit(string eat) {
if (eat == "fruit") {
return true;
}
return false;
}
全球和本地
基本上,在脚本的所有函数外部声明的变量被称为global,而在函数内部声明的变量被称为local。global变量可以从脚本中的任何地方访问,而local变量只能在声明它们的function中访问。
在下面的例子中,a是一个global变量,因为它和它的值可以在脚本中的任何地方被访问、读取和修改。即使一个global变量作为一个parameter被传递给一个function,那个function引用被传递参数的名字只有它自己知道,因此b和c都是local参数,只能在函数temporaryFunction()内部被访问、读取和修改。
int a = 100;
void temporaryFunction(string b) {
int c;
}
1.8 协程
协程与函数非常相似,除了当你调用一个函数时,它在返回之前运行完成。函数中发生的任何动作都不是随时间推移而发生的,因为函数中的代码是在一次帧更新中运行的。虽然可以使用循环和检查来实现这一点,但使用协程通常更有用。与每次执行都必须手动调用的函数不同,协程可以设置为在指定的延迟自动运行。协程的返回值是强制的,不能赋给任何变量或直接使用。
1.8.1 定义和调用协程
一个协程只能被调用一次,其中的代码将在一些特定的关键字定义的帧数下运行。
StartCoroutine("<coroutineName>"); // or StartCoroutine(<coroutineName>());
IEnumerator <coroutineName>(<parameters>) {
// Do something
yield return <returnValue>;
}
协程总是被定义为IEnumerator,并且必须总是有那个yield return <returnValue>;行。该yield return <returnValue>;行之后的任何代码都将在指定为returnValue的帧数之后运行。
例如,为了在游戏中每运行一帧就将integer变量numberFrames的值增加 1,可以使用下面的代码。yield return null;相当于yield return 1;,在我们的例子中,它导致coroutine的最后一行每帧运行一次。
int numberFrames = 0;
StartCoroutine("CountFrames");
IEnumerator CountFrames() {
yield return null;
numberFrames++;
}
1.8.2 用秒代替帧
coroutines可以等待若干秒,而不是等待帧。
IEnumerator <coroutineName>(<parameters>) {
// Do something
yield return new WaitForSeconds(<numberOfSeconds>);
}
演示这一点的一个很好的例子是在特定的时间间隔检查一些条件,而不是在每一帧检查。下面指定的for子句无限运行。if子句将以作为参数传递的值为间隔运行。
string weather = "rainy";
bool happy = false;
float numberSeconds = 0.5f;
StartCoroutine(CheckWeather(numberSeconds));
IEnumerator CheckWeather(float amount) {
for ( ; ; ) {
yield return new WaitForSeconds(amount);
if (weather == "sunny") {
happy = true;
} else {
happy = false;
}
}
}
二、Unity 简介
正如你已经知道的,我们将使用 Unity 游戏引擎来开发游戏。Unity 已经用 C++ 编程语言编写,但是它的脚本应用编程接口(API 我们实际用来编码游戏的东西)是用 C#写的。Unity 不仅仅可以用来做游戏。它甚至可以用于为电影、建筑或汽车制造创建可视化效果。
使用 Unity 而不是从头开始编写游戏的好处是,Unity 已经提供了许多现成的工具来帮助我们制作游戏,例如物理或照明。与其他游戏引擎相比,Unity 更受欢迎,这使得解决 bug 或学习如何做一些事情更容易,因为很可能一些东西已经出现在互联网上。我们希望在 Unity 中开发的任何游戏都是一个项目。
2.1 创建 Unity 帐户
为了使用 Unity,您必须在 https://id.unity.com 设置一个帐户。同样,你可以使用你的谷歌或脸书账户创建一个账户(图 2-1 )。
图 2-1
创建 Unity 帐户
2.2 下载 Unity 和附加软件
Unity 有四个许可证:个人版、高级版、专业版和企业版。个人拥有其他营业执照提供的大部分功能,并且是免费的(图 2-2 )。想要更多功能而不是个人优惠的个人可以购买 Plus。如果从你的游戏开发公司或组织获得的收入超过特定的阈值,必须购买专业或企业许可证。使用个人许可证的最大好处是能够定制游戏的闪屏(当游戏开始时),使用深色编辑器 UI,更好的支持,优质资源的可用性(尽管不是绝对必需的),以及更多的诊断/分析。更多信息请访问 https://store.unity.com/ 。
图 2-2
统一计划
2.2.1 统一集线器
Unity Hub 是一个应用,可用于下载多个版本的 Unity,以及它们各自的模块(如果需要的话),并且包含您正在处理或已经创建的项目(云或本地)的列表(图 2-3 )。
图 2-3
Unity Hub 项目
对于 MacOS 或 Windows 用户,可以从 https://unity3d.com/get-unity/download/ 下载 Unity Hub,对于 Linux 用户,可以从 https://forum.unity.com/threads/unity-hub-v2-0-0-release.677485/ 的论坛获取一个可执行文件。下载完成后安装或运行即可。
接下来,你必须下载一个版本的 Unity 编辑器(实际的游戏引擎)。对于本书中的示例,您必须使用任何 2019.3.x 版本。首先使用您之前创建帐户时使用的凭据登录 Unity Hub。然后,点击您的个人资料图片或姓名首字母(右上角)。转到管理许可证并激活新的 Unity 个人许可证。最后,转到 Installs,点击 Add,选择 Unity 的 2019.3.x 版本,并选择您希望安装的模块。因为我们将开发一款手机游戏,所以至少要选择 Android 和/或 iOS 构建支持模块。注意,如果你没有苹果电脑,你将无法制作 iOS 游戏。对于 Android 构建支持,也可以查看 Android SDK & NDK 工具和 OpenJDK(图 2-4 )。文档和其他东西都是可选的。
图 2-4
从中心下载 Unity 编辑器
您还需要一个集成开发环境(IDE)来编写脚本。Visual Studio 代码是一个轻量级的优秀选择。可以从 https://code.visualstudio.com/ 下载。我们稍后会将它链接到 Unity 编辑器。目前,只需安装它。
2.2.2 创建空项目
在 Unity Hub 中,点按蓝色的“新建”按钮,给您要创建的项目命名,然后选取一个存储位置。选择 3D 作为模板。点击创建,稍等片刻,项目应该打开(图 2-5 )。
图 2-5
创建新项目
首先,去编辑➤首选项➤外部工具,并确保您使用内置的 JDK,SDK 和 NDK。应勾选相应的复选框。您还必须在外部脚本编辑器选项卡中指定 vscode 选项,以便在适当的 IDE 中编写和修改脚本。该选项位于外部工具标题的正下方(图 2-6 )。
图 2-6
检查首选项
2.3 基本窗口
Unity 提供了几个具有特殊功能的窗口来帮助开发者开发游戏。你已经知道项目在 Unity 中意味着什么。游戏项目包含场景。例如,把场景想象成游戏的关卡。例如,当很多东西目前不需要的时候,加载所有的东西是一个坏主意。在场景中,你可以设计你的关卡并使它们可玩。
现在,场景中出现的所有东西都被称为游戏对象(在本书的第三章会有更多的介绍)。当你制作游戏时,你可能会需要图像、声音、3D 模型等。您在项目中导入的所有内容都是一种资源,无论您是否在场景中使用过它。
我将浏览 Unity 中一些最常用的窗口。您的空项目看起来应该是这样的(图 2-7 ):
图 2-7
空旷的场景
您可以通过点击 Unity 编辑器右上角的小布局按钮来更改这些窗口的布局(图 2-8 )。您也可以通过拖动它们的边缘来调整它们的大小。
图 2-8
布局
2.3.1 项目窗口
如果您使用的是默认布局,通常可以在编辑器的底部找到它。项目窗口(图 2-9 )是您已经创建或导入到 Unity 项目中的资源和目录的集合。您也可以使用搜索栏搜索整个项目,按名称或类型查找资源。
图 2-9
项目窗口
层次结构窗口
层级窗口(图 2-10 )基本上包含了当前在编辑器中打开的场景中出现的所有游戏对象的列表。默认情况下,它包含一个摄像头和一个光源,你会在第三章中了解更多。它位于编辑器的左上角,在默认布局中。
图 2-10
默认情况下的“层次结构”窗口
尝试从层级窗口中创建一些基本的游戏对象,通过左键单击小加号图标或右键单击窗口中的任意位置,弹出一个菜单供选择。新创建的游戏对象将立即出现在层级窗口中(图 2-11 )。
图 2-11
创建 3D 游戏对象
2.3.3 场景窗口
场景窗口主要用在关卡设计中,必须放置游戏对象来创建游戏区域。可以使用鼠标在场景中导航,并在场景窗口中直接更改场景中的对象。注意红色代表 x 轴,绿色代表 y 轴,蓝色代表 z 轴(图 2-12 )。
图 2-12
带立方体的场景窗口
可以通过在场景窗口中左键单击对象来选择对象。也可以尝试右键单击并拖动来旋转,或者使用滚轮来放大和缩小。最后,您可以点击并按住滚轮,移动鼠标进行平移。如果在“检查器”窗口中左键单击并选择对象,它们在“场景”窗口中也会显示为选中状态。
有七个工具(图 2-13 )可以帮助你在场景视图中执行操作。首先是手工具。选中时,当您单击它们时,不会选择任何对象。相反,鼠标左键将用于在场景中平移,就像前面描述的按住滚轮一样。
图 2-13
七个场景工具
第二个场景工具是移动工具(图 2-14 )。当在场景窗口中选择一个对象时,可以拖动箭头将其向特定轴的方向移动。物体也可以被两个小方块拖动,同时沿两个轴移动。
图 2-14
移动工具
第三个场景工具是旋转工具,其工作方式与移动工具相同,只是它用于旋转游戏对象。通过沿着彩色圆圈拖动,你可以在三个轴中的一个轴上旋转游戏对象,或者沿着灰色圆圈,同时在两个轴上旋转(图 2-15 )。
图 2-15
旋转工具
第四个工具是缩放工具(图 2-16 ),它允许我们缩小或放大选定的游戏对象。通过沿着彩色方块拖动,你可以沿着相应的轴放大或缩小游戏对象,通过从游戏对象中心的小方块拖动,你可以同时沿着所有轴均匀地放大或缩小游戏对象。
图 2-16
缩放工具
第五个场景工具是矩形工具,用于移动和缩放 2D 对象。当我们需要移动或缩放图像、按钮和 2D UI 元素时,我们将会用到它。
第六个工具结合了移动、旋转和缩放工具的功能。我们将跳过它做什么,现在使用第七个也是最后一个(多重)工具(图 2-17 )。
图 2-17
多功能工具
在场景视图中,也可以单击窗口右上角的小圆锥体,使视角垂直于轴。例如,尝试单击红色圆锥体,使视角垂直于 x 轴,本质上,您看到的内容现在将以二维形式出现,以 z 轴和 y 轴为界(图 2-18 )。
图 2-18
沿 x 轴方向观察
你也可以点击小圆锥下面的文字,在透视或正交视图之间切换,这取决于你正在制作的游戏类型。
场景视图左上角的第一个按钮允许你以另一种形式查看游戏对象。转向线框的一个很好的例子是调整汽车的车轮(图 2-19 )。
图 2-19
场景窗口中的线框着色
窗口顶部的小 2D 按钮使所有东西都出现在 2D,这对于处理 2D UI 元素或 2D 游戏很有用。其他按钮主要用于打开或关闭诸如灯光、音频或效果之类的东西。
最后,默认情况下还有两个按钮,标记为“中心”和“全局”。单击前者时,将显示“中心”或“轴心”。当使用移动或旋转等变换工具时,箭头将被放置在所选游戏对象的中心或枢轴点。现在在编辑器中尝试一下,以便更好地使用它们。尝试移动、旋转和缩放 3D 对象,并练习到目前为止所学的一切。
例如,如果场景中有两个立方体并且都被选中,如果第一个按钮被设置为中心,当前工具将被放置在与两个立方体等距的位置(图 2-20 )。
图 2-20
两个游戏对象的中心点
如果使用 Pivot,该工具将被放置在两个立方体之一的中心,因此称为轴心点。在这种情况下,轴心点将由首先选择两个立方体中的哪一个来确定。如果首先选择左边的,则其中心将是支点(图 2-21 )。
图 2-21
两个游戏对象的枢轴点
现在转到全局和局部模式。基本上,当第一个按钮的模式设置为全局时,所有工具相对于世界的定位都是相同的。红色箭头会一直指向右边(x 轴);绿色箭头会一直指向上(y 轴);蓝色箭头总是指向前方(z 轴)。这些将不取决于所选游戏对象的位置、旋转或比例。
然而,对于本地模式,该工具将始终取决于所选游戏对象的位置、旋转或缩放。在某种程度上,这就像说蓝色箭头“是游戏对象的 z 轴”,这意味着蓝色箭头将指向游戏对象的向前移动,而不是法线,它跨越了 Global 定义的世界空间。在下面的例子中,立方体被轻微旋转,因此它的“向前”指向上方一点(图 2-22 )。
图 2-22
选择游戏对象的本地模式
在全局/局部按钮旁边还有一个小磁铁图标。这可用于打开/关闭网格捕捉。当刀具定位设置为全局时,可使用此选项。网格捕捉允许您在所有轴上以 1 为单位移动对象,从而将游戏对象的位置捕捉到场景中最接近的整数位置。
游戏视图
如果使用默认布局,可以在场景窗口旁边找到它。它主要用于在导出之前测试游戏项目,因为它向第三方用户展示了实际导出游戏的表示(图 2-23 )。
图 2-23
游戏视图
图 2-23 显示了如果有人现在尝试玩你的空游戏会是什么样子。显示 1 允许您在不同的显示之间切换,以查看特定地点的外观。
自由外观选项卡允许您设置分辨率/比率,这样您就可以更容易地看到游戏如何在具有特定分辨率/比率的显示器上呈现。自由视角占据了游戏窗口的整个尺寸。您也可以创建新的分辨率或比率(图 2-24 )。
图 2-24
在游戏视图中设置新的分辨率/比率
使用缩放滑块,您可以放大或缩小游戏窗口,尽管如果您没有实现类似的东西,这在实际游戏中是不可用的。播放时最大化可以设置为开或关。如果被激活,它会显得更白,这样你就可以全屏测试你的游戏了。请注意,在这一点上,如果您打开或关闭它,什么都不会发生。
统计数据允许你预览一些关于游戏的信息,比如 CPU 使用率或者当前顶点的数量(图 2-25 )。
图 2-25
在游戏视图中显示统计数据
Gizmos 允许你预览更多显示给最终用户的东西,比如碰撞器这样的组件,你将在接下来的章节中了解更多。最后,进入游戏模式。
当你想测试你的游戏而不导出它的版本时,你可以在编辑器中完成。游戏窗口的顶部有三个图标。第一个(最左边的)是播放按钮。当你点击那个按钮时,编辑器变暗,按钮变成蓝色,你就进入了所谓的播放模式。在游戏模式下,你可以像普通用户一样玩游戏,如果他们有那个游戏的版本的话。要退出播放模式,只需再次单击播放按钮。
在游戏模式下(图 2-26 ,可以点击旁边的按钮——暂停按钮,暂时暂停游戏。再次点击暂停按钮将恢复暂停。最后一个(最右边的)按钮是步进按钮,每当你点击它,它会自动播放一帧并暂停游戏,如果它还没有暂停的话。你仍然可以点击暂停按钮继续游戏。如果我们想知道每一帧发生了什么导致了一个 bug,那么 Step 按钮是很有用的。
图 2-26
进入播放模式
请注意,您在播放模式下所做的任何更改都是暂时的。也就是说,例如,如果您在播放模式中移动对象,当您返回正常编辑模式时,更改将恢复到进入播放模式之前的状态。
2.3.5 检查员窗口
如果您使用的是默认布局,检查器就是屏幕右侧的大垂直三角形。它包含了许多关于所选游戏对象的信息,基于附加到这个(这些)游戏对象的组件。默认情况下,所有游戏对象都有一个变换组件(图 2-27 和 2-28 )。大多数组件的字段或属性的值都可以在此窗口中调整。
图 2-28
多维数据集的典型默认检查器窗口:第二部分
图 2-27
多维数据集的典型默认检查器窗口:第一部分
我们作为例子的立方体,或者我们创建的一个新的立方体,默认情况下应该有这些组件。标签和层的用途将在最后几章中解释,你看到的组件的使用将在第三章中记录。当静态旁边的复选框被选中时,无论我们做什么,我们的游戏对象都不会在运行时移动或物理改变。这有性能增益。建议您尝试更改一些组件的属性,看看会有什么不同。
2.3.6 统一素材商店
如果您还记得在您学习项目窗口的章节中提到的素材定义,有几种方法可以将素材导入 Unity。其中最常见的是通过打开.unitypackage文件。Unity 包,或以扩展名.unitypackage结尾的文件,是一个包含多个素材的压缩文件。
对于本节,我们将主要关注从素材商店下载和导入素材,而不是从第三方非官方来源。素材商店是一种市场,你可以浏览和下载游戏的素材。把它想象成 Unity 的 Google Play for Assets。
要访问素材存储,您必须转到窗口并单击素材存储选项卡。或者直接按 Ctrl+9。您可以调整“素材存储”窗口的大小或将其放置在某个位置。它应该看起来有点像全屏显示的图 2-29 。
图 2-29
素材商店
您可以使用搜索栏搜索素材,并使用价格和类别等过滤器。出于本教程的考虑,我们将下载并导入一个名为 Simple Input 的包,它碰巧是免费的。使用定价过滤器仅针对免费素材(图 2-30 )。
图 2-30
搜索素材
然后单击弹出的第一个结果(它应该看起来像前面截图中的第一个结果)。在素材页面上,您可以阅读描述、预览将导入的文件,以及查看屏幕截图或评论。准备好了就点击下载。当它完成下载时,你现在应该在那个按钮上看到 Import 而不是 Download(图 2-31 )。点击它。
图 2-31
简单输入系统素材
Unity 现在将解压软件包。最后,点击导入。一旦所有内容都被导入,素材将出现在您的项目窗口中(图 2-32 )。
图 2-32
导入素材——我们项目中的简单输入系统
控制台窗口
控制台是一个只读窗口,可以显示日志、警告或错误(图 2-33 )。在测试游戏项目的代码时,您通常会将值输出到控制台,以确保一切按预期运行。这叫做调试。Unity 也可能警告你一些事情,这些事情可能不会马上真正影响你的项目,但从长远来看会引起问题。至于错误,你必须修正它们;否则,您将无法运行或构建您的游戏。
图 2-33
控制台中记录、警告和错误消息的示例
当您单击控制台窗口中的日志/警告/错误时,更多详细信息将出现在屏幕底部,主要指示与相关消息相关的语句和行号。如果您在控制台中双击一条消息,负责的脚本将在 Unity 已分配使用的默认文本/代码编辑器中打开。
清除按钮将删除控制台窗口中的所有消息,除了那些必须完全修复才能继续开发游戏项目的消息(图 2-34 )。只要启用了“播放时清除”,每次进入播放模式时,它都会自动执行“清除”按钮的操作。类似地,每次 Unity 成功构建游戏的可执行文件时,Clear on Build 就会运行。
图 2-34
控制台窗口中按类型和流派显示的消息数量
Collapse 选项卡将把相同的控制台消息编辑在一条消息中。实例的数量将显示在控制台窗口最右侧的小圆圈中。错误暂停将在收到错误时暂停游戏模式,最后一个下拉按钮编辑器允许您选择调试消息的来源,例如,可能来自连接到您计算机的设备。
搜索栏允许您搜索特定的消息,最后三个彩色小按钮允许您选择想要的调试消息类型。例如,单击黄色按钮将停止控制台窗口中弹出的所有警告消息。但是,自您上次点击清除后收到的每种类型的日志消息的数量仍将继续显示在它们各自的按钮旁边。
构建设置
这可以从文件➤构建设置中访问。在构建设置窗口中,您可以切换到您想要导出游戏的平台,并调整更多选项,这些选项将在以后的章节中更全面地介绍(图 2-35 )。您当前所在的平台将在其标签前显示一个小的 Unity 徽标。对于未来的项目,切换到 Android 平台。您将很快看到如何配置和构建。在“构件设置”窗口中拖放您想要的场景。第一个是玩家打开游戏时看到的。最后,保存您的场景和项目是一个很好的做法。从“文件”选项卡执行此操作。
图 2-35
“构件设置”窗口
三、游戏对象、预设、材质和组件
如第二章所述,用 Unity 搭建的游戏通常都是场景布置。这些场景通常包含许多对象来增强它们,并使游戏具有交互性和趣味性。目标是有一个具体的游戏,有坚实的机制,良好的游戏性,和良好的图形。
3.1 游戏对象和预设
在 Unity 中,场景中的物体被称为“游戏物体”创建新场景时,它包含主摄影机和平行光。这些是游戏对象。你在层级窗口中找到的每个对象都是一个游戏对象。如果你创建一个立方体,它也是一个游戏对象。
现在,如果你有多个场景,并且在所有的场景中使用一个公共的游戏对象,如果你可以在所有的场景中拖放那个对象,而不是为每个场景从头开始配置,那就太好了,对吗?这就是预制构件的用武之地。基本上可以保存一个游戏对象的版本,拖放到其他场景中(图 3-1 )。你只需要在项目窗口中拖放一个游戏对象,它就会变成一个预置。
图 3-1
制作预制品
你刚刚变成预设的游戏对象现在在层级标签中会有一点蓝色。至于你的预设,你可以加载另一个场景,并将其拖入新打开场景的层次或场景标签中。
你也可以直接改变一个预置。你只需要在项目窗口中双击它。场景窗口现在将有一个蓝色的背景,你现在将能够看到该预设的子对象(如果它有任何子对象的话),并对其属性以及子对象的属性进行更改。
在做了想要的改变之后,你可以通过点击层级窗口左上角预设名称旁边的小箭头或者使用快捷键比如 Ctrl 来保存它们;前一个动作会自动保存对预设所做的更改,并返回到之前打开的场景。然后,该预设的所有实例将在发现它们的所有场景中用这些变化进行更新(图 3-2 )。
图 3-2
打开预设
如果你对你在场景中变成预设的游戏对象进行了更改,在检查器窗口中有一个覆盖按钮,你可以点击它使你所做的更改应用到预设,从而应用到该预设在其他场景中的每个实例(图 3-3 )。你也可以将属性还原为预设版本的属性。这不是一个必要的步骤,你也可以在一个场景中有一个游戏对象,它来自一个预置,但是没有完全相同的属性。
图 3-3
覆盖预设属性
如果你制作了一个预置,把它放在一个场景中,并且不希望它被将来对预置的修改所更新或覆盖,你可以通过在检查器中右键点击它,或者点击“解包预置”或者“完全解包预置”来使预置实例成为一个独立的游戏对象。例如,如果你在一个场景中放置了一个预置,并且它的属性与原始预置中的属性完全不同,你可能希望那个版本是完全独立的。
你可以制作预置的预置,例如,通过创建一个游戏对象,它有多个预置作为孩子,并且它自己变成一个预置。前一个选项将只移除第一层嵌套预设,而后一个选项将移除所有嵌套预设。还需要注意的是,删除一个预设并不会删除场景中该预设的实例。这些预设实例将成为独立的游戏对象,并在层级中沿着它们的名称带有红色(图 3-4 )。嵌套预设在游戏中非常有用,在游戏中所有的敌人都以相似的方式行动,并且共享相同的基础特征。
图 3-4
删除预设
3.2 组件
每个游戏对象都有组件。组件基本上是一个可以附加到游戏对象上的模块。除了空游戏对象已经提供的属性和特性之外,组件还提供了几个新的属性和特性。有些组件需要游戏对象上的其他组件才能正常工作,而有些组件是强制性的,即使你想创建一个空的 3D 对象。当您选择游戏对象时,您将在检查器窗口中看到所有附加到它们的通用组件,也可以通过单击它们的名称来收缩或扩展(图 3-5 )。
图 3-5
多个组件
在检查器窗口中,您可以更改组件的值。您还可以单击组件右上角的三个点,以调出更多选项,例如 Reset,它将为组件分配默认值,就像您刚刚添加它一样。
也可以通过单击“复制组件”将组件值同时复制并粘贴到两个组件中。在选择了包含您想要覆盖其值的组件的游戏对象后,单击粘贴组件值。你也可以复制组件并直接粘贴到另一个游戏对象上。
还应该注意到,一个游戏对象可以有一个以上的特定组件的实例,尽管也有一些例外。你也可以通过上下拖动或者使用三个点并点击上移或下移来重新排列游戏对象上的组件。
在三点图标旁边,还有一个按钮。单击它允许您使用相应组件的预设。
要获取组件的手册或文档,只需单击左侧预设按钮旁边的问号图标。将打开一个浏览器选项卡,显示合适的相关信息(图 3-6 )。
图 3-6
查看文档
最后,你可以通过点击添加组件按钮并浏览或搜索你想要添加的组件,或者通过使用编辑器菜单(图 3-7 )来给游戏对象添加新的组件。
图 3-7
添加组件
3.2.1 转换
如果你按照第二章中的学习,你已经从场景窗口中与变换组件进行了交互,并且可能对它有一个坚实的概念。每个游戏对象都需要转换组件。与整数或浮点数据类型不同,转换组件由三组Vector3值组成。您可以将Vector3视为由三个值组成的浮点数组。从第一个索引开始,或者从左边开始,这三个值分别由“x”、“y”和“z”字符表示。变换组件的三组Vector3值是所选游戏对象的位置、旋转和缩放(图 3-8 )。
图 3-8
变换组件
x 轴用红色表示,水平方向从左(-)到右(+)。y 轴用绿色表示,垂直方向从下(-)到上(+),z 轴用蓝色表示,从后(-)到前(+)。所有的轴都互相垂直。
位置Vector3用X、Y和Z值表示游戏对象在世界中的位置。您可以更改这些值,方法是将它们直接输入到特定轴名称旁边的文本框中,或者在“检查器”标签本身中将光标从轴名称向右(+)或向左(-)。
旋转Vector3以x、y和z值表示游戏对象在世界中的旋转,比例Vector3以x、y和z值表示游戏对象在世界中的位置。
但是,如果选定的游戏对象是另一个游戏对象的子对象,那么它的位置、旋转和缩放都是相对于其父对象的。例如,下面是两个不同立方体的变换组件(图 3-9 和 3-10 ):
图 3-10
立方 2 的变换组件
图 3-9
立方 1 的变换组件
现在,如果我们必须使立方体 2 成为立方体 1 的子对象,通过在层次窗口中将它的游戏对象拖动到后者的游戏对象上,下面是它的变换看起来的样子(图 3-11 ):
图 3-11
Cube2 的转换组件(如果它是 Cube1 的子元素)
因为它与立方体 1 处于相同的位置,相对于其父体,立方体 2 的Vector3位置在所有轴上都将为 0。因为它的旋转在任何地方都是 0,为了保持这一点,立方体 2 必须在其相对的 y 轴上减去 90 度,以保持该值为 0,因为立方体 1 在其自身的 y 轴上旋转了 90 度。至于比例,这是不言自明的:立方 2 比立方 1 大两倍。
相机
在电子游戏中,相机相当于我们的眼睛。你在游戏中感知到的一切都被一个叫做摄像头的组件“看到”。通常,在一个特定的时间,你在一个单人游戏中只能启用一个主摄像头,以向玩家展示他们所能看到的。如果你在玩第三人称游戏,摄像机将会在你所控制的主要角色的后面,因此,会产生另一个实体正在监视和跟踪后者的印象。在第一人称游戏中,相机充当你所控制的角色的眼睛。
在一个空场景中,通常应该有一个相机组件已经连接到一个游戏对象(主相机),如果你没有对它做任何修改的话(图 3-12 )。
图 3-12
相机组件
清除标志选项允许您从预定义的列表中选择应该显示在摄像机空白区域的内容。
-
默认情况下,它被设置为天空盒,正如您稍后将了解到的,它由总共六幅图像组成,这些图像通常相互补充,形成一种围绕场景的立方体。
-
此外,您可以选择纯色选项,并在下面的背景属性中选择一种颜色。
-
如果选择“仅深度”,空白区域中不会显示任何内容,如果没有被任何内容覆盖,“不清除”将持续显示上一帧中存在的内容。
剔除遮罩是一种机制,它基于已分配给组(称为层)的图形元素来控制渲染到该相机的内容。
接下来,您可以选择是让相机使用透视视图还是正交视图。如果您选择正交(默认情况下,这是透视),相机看到的一切都将在某种程度上 2D 视图。
-
使用正交相机,您可以调整相机的大小,即它在特定时刻或帧可以“看到”的区域。
-
使用透视相机,您可以选择它的视野,从字面上看,这是调整它从中心“看到”的“角度”的过程。
您还可以调整视野是沿着水平(x)轴(从上到下)还是沿着垂直(y)轴(从左到右)。
尽管如此,对于透视视图,您可以通过勾选物理摄像机来选择使摄像机更具可配置性(图 3-13 )。这将允许您调整更多的设置,如相机的焦距和传感器大小。顺便说一句,要改变相机的位置和旋转/方向,你必须修改游戏对象的转换组件中相应的值,该组件连接了相机组件。
图 3-13
透视照相机
现在是裁剪平面。Unity 的 1 个单位相当于现实世界中的 1 米。对于相机,“剪裁平面”设置有一个近值和一个远值。这两个值代表游戏对象到摄像机的最小和最大距离,以便后者渲染它。如果相机比游戏对象的近距离更近,比方说,相机位于游戏对象的正中心,并且近距离值大约为 0.5,它不会被渲染。如果现在另一个游戏对象离相机很远,距离大于“远”值,它也不会被渲染。
至于 Viewport Rect 设置,由两组Vector2值组成。因为屏幕上显示的一切都是 2D 形式,所以没有第三个值来代表 z 轴。
-
X和Y值允许您分别水平和垂直调整相机渲染的位置。 -
W和H值分别代表用于水平和垂直渲染摄像机的屏幕部分。值 1 表示使用屏幕的整个宽度或高度,值 0 表示不使用。
例如,如果您正在为两个玩家制作一个具有分屏功能的游戏机赛车游戏,您可以有两个摄像机,每个摄像机占据一半的屏幕,跟随两个玩家中的一个。两个相机的W值为 0.5,H值为 0,Y值为 0,但是X值不同,以匹配屏幕左半部分和右半部分的位置。
如果在一个场景中使用多个摄影机,可以为它们设定不同的深度值。例如,如果您正在制作一个游戏,其中您可以在第三人称和第一人称视图之间切换,您可以对任何一个视图使用两个相机,但是由于它们都占用 100%的屏幕空间,深度值决定了哪个相机将被渲染给玩家。在该示例中,玩家将看到具有最高深度值的相机正在“看”什么
其余的设置将有一个非常简短的描述,对于简单或小型项目,你可能不会去弄乱那些,如果你这样做,最好是编辑项目本身的设置,而不是相机的个别设置。渲染路径设置允许你选择游戏对象如何被一个相机渲染。
目标纹理允许您将 2D 渲染纹理指定给相机组件。该纹理将随摄像机在场景中看到的任何东西而更新。例如,当你想创建一个鸟瞰图形式的小地图时,这是很有用的。可以设置一个摄像头,从上面跟着玩家往下看(90,0,0)。然后它可以输出到一个 2D 纹理,该纹理可以被分配到一个“矩形”中,该矩形将总是显示在屏幕的右上角。
遮挡剔除是一种流行的技术,可以显著提高某些类型游戏的性能。如果您正在使用该技术,并且特别希望相机从中受益,请勾选相应的复选框。为了遮挡剔除正常工作,你的场景必须为这个特定的特征“烘焙”;否则,勾选复选框不会导致任何变化。
至于高动态范围(HDR)和多采样抗锯齿(MSAA)渲染,它们可以让你的游戏看起来更好,但特别是使用 MSAA,有重要的性能成本。
最后,勾选允许动态分辨率将允许相机缩放渲染纹理,如果你构建游戏的平台支持的话。
照明
灯光在电子游戏中非常重要。主灯光的位置和旋转决定了游戏对象可见的部分,相对于它们与光源投射光线方向的角度,从而决定了阴影投射的方向和大小。
默认情况下,Unity 中的一个空场景会有一个被称为平行光的游戏对象,其中有一个灯光组件(图 3-14 )在一个方向上提供均匀的光线,模拟类似太阳的东西。如果场景中没有光源,世界将会完全黑暗。
图 3-14
轻组分
在“层次”窗口中,可以从四种默认类型的光源中创建,即平行光、点光源、聚光灯和区域光源。平行光可以用来照亮整个场景,基本上就像太阳一样。点光源用于更特殊的场景,例如中世纪村庄中的火把。例如,聚光灯可用于在黑暗的房子里模拟恐怖游戏中的手电筒,区域光可用于均匀照亮指定的区域。
为了保持这一部分的简单,我将只基于定向光源的解释。用其他类型的光源获得的附加设置是不言自明的。
首先,您可以设置光源发出的光的颜色。然后,可以将灯光模式设置为实时、混合或烘焙。
-
如果你使用的是实时光照模式,那么在你玩游戏的时候,游戏物体会被加阴影。
-
使用“烘焙”,可以“烘焙”场景以生成一种将自动指定给该光源的照明数据资源。
-
如果你使用混合,你将有一个烘焙和实时照明的组合。
如果你制作的游戏需要一个移动的光源,或者游戏对象是渐进或随机产生的,最好使用实时,因为照明数据会更准确。但是,如果您想在照明方面节省一些性能,并且如果对象在场景中几乎是静态的,以及光源,您可能会考虑过早烘焙场景并使用烘焙。
如果你使用混合或实时作为照明模式,你可以为将要产生的阴影类型做一些额外的设置。你可以选择从没有阴影到有软阴影或硬阴影。软阴影比硬阴影看起来更平滑,但需要更多的处理能力。然后你可以修改将要产生的阴影的值,比如它们的强度,它们的分辨率(也可以在项目的设置中设置),以及它们与各自游戏对象的距离偏差(图 3-15 )。
图 3-15
立方体上正常强度的光
您也可以增加或减少光源的强度和间接乘数。间接光是被一个游戏对象反射到另一个游戏对象上的光。增加这两个值中的任何一个都会使场景看起来更亮。如果光源的强度被提升到其原始值的两倍(图 3-16 ),上图中的场景看起来会是这样:
图 3-16
立方体上的高强度光
cookie 属性允许您将 2D 纹理分配给光源。纹理将被用作遮罩。你可以把它想象成一个放在光源(灯泡)前的厚纸板形状(可能是恐龙的形状)。这将定义光源投射时获得的阴影、轮廓或图案。您也可以在它下面的选项中编辑 cookie 掩码的大小。
勾选绘制光晕将在光源周围创建一个模糊的球体,其半径等于其范围(如果使用点光源或聚光灯光源,此属性可用),并且颜色与光源投射的光相同。
“光斑”可用于允许场景中的光源渲染光斑,如果您正在制作电影,这可能会很有用。如果使用某种形式的正向渲染,可以更改渲染模式以反映场景中灯光的重要性。最后,以类似于相机的方式,您可以选择将受光源在其剔除遮罩属性中影响的游戏对象层。没有被选中的层的游戏对象不会受到光源的任何影响。在 Unity 的文档中,您可以找到不同灯光样本的更多细节。
渲染器
我现在要讨论的是让 3D 游戏对象可见的两个关键要素。任何缺少这些的游戏对象都将是透明的和不可见的。第一个组件是网格过滤器,它从您的素材中提取一个网格,并将其传递给第二个组件,即用于在屏幕上渲染的网格渲染器(图 3-17 )。
图 3-17
渲染器组件
在接下来的主题中,你将会学到材质到底是什么,但是现在,假设它们是颜色。
渲染器可以利用多种材质,具体取决于如何设置网格过滤器组件中的网格。如果你想自定义一个游戏对象如何接收或投射阴影,可以调整灯光属性。
在制作游戏时,你可能不需要弄乱渲染器的其他属性,但你可以随时访问 Unity 的文档或手册来了解更多信息。
碰撞器
游戏中的物理依赖于刚体和对撞机。碰撞器是一组允许碰撞发生的组件。碰撞器被用作触发器也很常见。例如,在游戏中离非玩家角色(NPC)足够近可能会触发 NPC 和你的角色之间的对话。
在 Unity 中,有六种类型的碰撞器,主要是
-
盒子(图 3-18
-
范围
-
胶囊
-
网状物
-
车轮
-
地带
在本书中,我将介绍前四个对撞机。车轮碰撞器用于制作陆地车辆的车轮,以及其他类似的对象,而地形碰撞器用于地形,这是另一种形式的原生 3D 对象,但具有更多可配置的选项。这本书不会涉及地形。
图 3-18
盒子碰撞器组件
默认情况下,在编辑器中创建的立方体带有一个盒子碰撞器(图 3-19 )。如果你点击编辑碰撞器按钮,你将能够在你的场景视图中缩放碰撞器。你只需要向你想要缩放碰撞器的方向拖动出现的小方块。
图 3-19
修改场景中长方体碰撞器的边界
在检查器窗口中,改变Vector3的x、y或z值,即所谓的“中心”,将使碰撞器向各自的方向移动。改变大小值会使碰撞器变大或变小,这取决于你在哪个轴上修改它的值。应该注意的是,增加或减少游戏对象的缩放变换将使其碰撞器的大小以相似的比例减少。例如,如果您创建一个立方体,并使其所有的比例Vector3值等于 2,即使其碰撞器的大小在所有轴上都是 1,假设您没有手动修改任何东西,碰撞器仍然会环绕立方体的整个体积或大小。
上面的物理材质标签可以用来让碰撞器模拟一种特殊的真实材质。例如,一些物理材质可以使对撞机在你走在上面时感觉更滑,例如冰,而其他材质可以使它感觉像似乎有更多摩擦的东西,例如沥青。
如果我们想在两个物理对象碰撞时触发一个事件,但不让它们相互弹开,就使用触发碰撞器。所以,如果你想在地板上移动时打开灯,这些会有帮助。如果你在碰撞器组件上勾选了 IsTrigger,与之相关的游戏对象将会看起来缺乏参与碰撞的能力。换句话说,你将能够穿越游戏对象,即使当它的 IsTrigger 框被选中时,它有一个碰撞器组件。这在您想要创建触发区域时非常有用,例如,在游戏中,在一个区域中行走会触发某些事情的发生,如播放过场动画。
球体对撞机(图 3-20 )的性质与箱式对撞机非常相似。唯一的区别是它们有一个半径属性,而不是一个Vector3大小的属性。您可以在场景窗口中使用该属性的第一个按钮来修改碰撞器,但是拖动一个点将沿所有轴均匀地增加半径。
图 3-20
球体碰撞器组件
胶囊由两个半球组成,一个在胶囊的顶部,一个在底部。这两个半球之间有一段距离,称为高度。胶囊碰撞器同样具有这些属性(图 3-21 )。
图 3-21
胶囊碰撞器组件
最后说一下网格碰撞器(图 3-22 )。例如,如果你有一个不规则形状的游戏对象,你可以给它添加一个网格碰撞器组件。默认情况下,网格碰撞器会将碰撞添加到游戏对象的整个表面。
图 3-22
网格碰撞器组件
要制作网格碰撞器,请使用 IsTrigger。必须先标记为“凸”。滴答滴答将使碰撞器使用相当数量的 3D 规则形状来覆盖游戏对象的整个表面区域和体积。使用 Convex 还允许使用网格碰撞器在其他游戏对象之间进行碰撞。然而,建议您尽可能使用之前讨论的其他碰撞器,而不是这些,因为它们提供了性能提升,即使网格碰撞器被标记为凸面。
3.2.6 刚体
刚体组件(图 3-23 )通常与碰撞器组件一起使用,它是一个神奇的组件,可以将游戏对象变成反映现实世界中对象属性的对象。它可以使物理引擎允许对象从其他对象反弹,并模拟重力之类的东西。如果你正在制作一个与物理有关的游戏,你很可能不得不使用刚体。
图 3-23
刚体组件
Mass 属性允许您设置对象的质量。一个单位相当于 1 公斤。更大的值会让游戏对象感觉更重。如果使用重力,增加质量会使游戏对象对外力(例如爆炸)的反应更弱,下落更快。
阻力相当于空气阻力,当游戏物体从高处落下时,可以看到这个数值的差异。角阻力几乎是相同的,但它可以由多少空气阻力将影响游戏对象的旋转扭矩来定义。这些值中的任何一个值为 0 都可以解释为“这个游戏对象不受空气阻力的影响。”
不勾选“使用重力”将会阻止游戏对象自动下落,如果它被放置在离地面一定高度的地方,下面没有任何东西支撑它。
如果运动学被勾选,游戏对象将不会受到普通物理的影响。为了使游戏对象移动或影响其位置或旋转,您必须操纵其各自的变换值。这对制作移动平台很有用。
例如,如果游戏中玩家角色的运动不稳定,插值可以用来使游戏对象感觉更平滑。将“插值”设定为“插值”将基于前一帧的变换平滑变换,而在“外推”中,将基于估计的下一个运动平滑变换。当我们开始编码时,你会学到更多关于插值的知识。
碰撞检测系统可以从离散模式改变为连续模式,如果游戏对象移动得如此之快,以至于它能够通过其他碰撞器,因为碰撞检测系统检测它的速度不够快。使用离散模式以外的模式会有性能成本。
最后,你可以为游戏对象设置约束。勾选任一复选框都不允许游戏对象在各自的轴上移动或旋转。这并不意味着它不会通过代码或脚本。这只是意味着应用于刚体的普通物理(例如,碰撞)不会对其产生任何影响。
3.2.7 音频源和听众
音频监听器组件通常在主相机游戏对象上。它实现了一个类似麦克风的设备。它会记录周围的声音,并通过播放器的扬声器播放出来。一个场景中只能有一个侦听器。例如,如果在场景中的某一点,您有一辆汽车发动机正在运行并产生声音,当摄像机靠近它时,声音会以更高的音量播放。
相反,音频源组件定义了播放什么声音以及如何播放。声音来源的位置将由音源组件所连接的游戏对象的位置决定(图 3-24 )。
图 3-24
音频源组件
音频源组件的第一个属性是 AudioClip。这通常是在 Unity 中作为资源导入的音频文件。混音器组是另一个组件或资源,您可以使用它来进一步个性化将要产生的声音质量。
勾选静音会禁止音频收听者拾取其音频源产生的音频。稍后,您可以勾选旁路,以防止音频源产生的声音受到其他效果的影响,无论是听众效果还是其他类型组件产生的效果,混响区域都会相应地应用到它。
“唤醒时播放”将使音频源在场景加载后立即播放指定的音频剪辑,而“循环”将使音频源从头开始重放音频剪辑,每次它都自动完成播放。
如果场景中有多个音频源,可以使用优先级滑块为每个音频源设定不同的优先级。例如,如果音频收听者与两个音频源的距离相等,则两个音频源中具有最低优先级滑块的那个音频源的声音会比另一个音频源的声音大。
音量滑块的作用非常明显。音量为 1 的音频源将以扬声器设定的最大输出音量播放分配给它的音频片段。请注意,音量为 0 的音频源产生的声音是听不到的。
音高滑块用于设定音频源产生的声音的频率。它也可以用来加快或减慢声音。
立体声声相滑块设定发送到混响区的输出信号量。该数量在(0–1)范围内是线性的,但允许在(1–1.1)范围内放大 10 dB,这对于实现近场和远场声音的效果很有用。
空间混合、混响区域混合和 3D 声音设置超出了本书的范围。
粒子系统
粒子系统是一个看起来在特定位置和旋转发射某种粒子或形状的组件(图 3-25 )。例如,使用粒子系统可以帮助你模拟火,雪,或者只是汽车尾气中的烟雾。
图 3-25
场景中的粒子系统
在你的场景窗口中,默认情况下,一个粒子系统看起来就像前面的截图。这三个按钮将分别从左边,暂停,重启,或停止粒子系统。停止和暂停的区别在于,停止会重新启动系统,但会立即暂停。
-
可以更改播放速度,以查看以指定速度以外的速度运行的粒子系统。
-
播放时间包含一个值,表示自粒子系统开始运行以来经过的秒数。
-
粒子是该粒子系统当前活动的已生成粒子数,速度范围是这些粒子速度的最小-最大值。
-
模拟层允许你在层上模拟粒子系统,而不是在游戏对象的层上。
-
勾选重新模拟将使应用到粒子系统的更改立即显示。
-
“显示边界”将使 3D 体积出现在场景窗口中,这将指示粒子在该系统中可以行进的最大距离。
-
最后,如果勾选了最后一个复选框,只显示选中的,将隐藏当前效果中所有未选中的粒子系统。
持续时间(见图 3-26 )定义了粒子系统发射的时间。这意味着,在该秒数过去后,将不再创建更多的粒子或形状。当然,如果勾选了循环,这个值就没有任何重要性了,因为系统会一直发出信号。
图 3-26
粒子系统组件中的第一个设置
开始延迟是系统开始发射前等待的时间。“开始寿命”定义粒子发射后多少秒后将被自动销毁。开始速度是粒子发射时最初行进的速度。
起始尺寸或 3D 起始尺寸可用于定义粒子的尺寸。开始旋转或 3D 开始旋转定义其旋转。翻转旋转可用于翻转粒子的旋转。“开始颜色”在创建粒子时更改粒子的颜色。
重力修改器可以创建受重力影响的粒子。高于 0 的值将使粒子下落得更快。低于 0 的值将根据前面的陈述起作用,但是粒子将向上而不是向下,0 将使粒子完全不受重力影响。
模拟空间可以设置为本地、世界或自定义。
-
如果设置为局部,粒子将相对于它们的粒子系统所附着的游戏对象的变换移动。
-
在世界中,它们相对于世界或场景移动。
-
在“自定义”中,可以指定另一个变换,使系统相对于该变换。
模拟速度是粒子系统播放的乘数。如果使用“未缩放”选项,更改增量时间模式对于暂停时播放效果非常有用。
缩放模式用于相对于整个层次、局部粒子节点调整粒子大小,或者仅将缩放应用于形状。
唤醒时勾选播放将使粒子系统自动开始运行。
发射器速度允许你改变模式为变形或刚体,这取决于系统移动时如何计算速度。
最大粒子数定义了某一特定时刻系统中粒子的最大数量。如果达到该数量,将不会发射更多的粒子,直到粒子数量少于定义的数量。
勾选“自动随机种子”会使每次播放效果时的模拟不同。
在“停止动作”中,您可以定义在粒子系统停止且所有粒子都已被销毁的情况下要做的事情。例如,您可以选择禁用粒子系统组件或销毁以后者为组件的游戏对象。
剔除模式定义了当系统不在屏幕上时会发生什么,即当粒子在屏幕上不可见时。
-
追赶模式会暂停屏幕外模拟,但当它们变得可见时,会执行一个大的模拟步骤,给人一种从未暂停的感觉。
-
自动对循环系统使用暂停模式,否则始终模拟。
-
AlwaysSimulate 永远不会暂停模拟,即使在屏幕外。
当“环形缓冲区模式”设定为“启用”时,粒子将保持活动状态,直到“最大粒子缓冲区”填满,此时新粒子将替换最旧的粒子,而不是在粒子寿命结束时死亡。
我将详细讨论发射(图 3-27 )和形状,但对于其余的大多数属性,我将只陈述它们的用途。粒子系统不必利用检查器中所有可用的属性。
图 3-27
粒子系统组件的发射属性
“随时间变化的速率”属性中的值表示每秒发射的粒子数量。速率随距离的变化与速率随时间的变化是一样的,但作用于单位秒。
“爆发”阵列允许您在特定的时间帧发射粒子。其时间属性允许您选择何时发射粒子爆发,计数指定要发射的粒子数量。此外,通过在下拉列表中选择值以外的其他设置,可以设置曲线或创建要发射的粒子数范围。
周期值允许指定重复脉冲的次数。通过在访问其下拉菜单时选择该属性,可以将其设置为无穷大。
Interval 允许您每 x 秒重复一次脉冲,概率是 0 到 1 之间的一个值。如果 Probability 设置为 0,则永远不会发生猝发,如果设置为 1,则总是会发生。值为 0.5 将使突发发生 50%的时间,或者不发生 50%的时间。
您可以通过单击下面的加号图标添加更多组值,或者通过单击减号图标删除组值。
形状代表发射器的 3D 体积(图 3-28 )。例如,球形会导致粒子向各个方向发射。我们将看到这部分的圆锥形状。其他形状提供的不同选项也很容易理解。
图 3-28
粒子系统组件的形状属性
较高的角度值将使发射的粒子向更多方向运动,增加半径将增加粒子可以覆盖的体积。半径厚度的值可以从 0 到 1。值为 0 将使发射的粒子看起来更密集。
“弧”中的值表示从发射器中心可以产生粒子的最大角度。值为 0 将使粒子仅从发射器的中心发射,而值为 360 允许粒子在发射器底部区域的任何点发射。例如,当我们使用圆锥体时,值为 360 会使粒子在形成发射器底部的小圆上的任意点繁殖。
“模式”允许您选择如何在圆弧周围产生粒子。“随机”模式使它们在相对于原始 Arc 值的任何位置繁殖,而“扩散”允许您选择在特定角度繁殖粒子。值为 0 表示禁用此行为。
“从使用发射”的值可以更改,以指定希望粒子从何处发射,从基础还是从体积本身发射。使用纹理 2D 资源,可以修改粒子采样颜色的位置。
Vector3位置允许你从游戏对象的变形位置移动发射器体积。旋转和缩放起着类似的作用。
勾选“对齐到方向”将根据粒子的初始行进方向自动对齐粒子。
随机化方向取 0 到 1 的值。值 1 将用随机方向覆盖粒子的初始行进方向。
类似地,球形化方向用从形状变换中心向外投射粒子的方向来覆盖初始行进方向。
最后,“随机化位置”将起始位置移动一个随机量,直到它包含的最大值。
转到 Shape 属性底部的四个按钮,单击第一个按钮可以打开或关闭形状 gizmo 编辑模式。这与碰撞器的“修改碰撞器”按钮的作用相同,允许您在场景窗口中调整发射器体积的边界或形状。
其他三个按钮分别允许您使用箭头等导向移动、旋转或缩放发射器体积,当您切换它们时,这些导向将出现在场景窗口中。
可以调整“一生中的速度”和“一生中的极限速度”的值,分别使粒子随时间增加或减少速度。
“继承速度”允许您控制粒子从发射器本身继承的速度。
“力随寿命变化”和“颜色随寿命变化”包含可以修改的属性,以分别使粒子获得/失去力并显示颜色随时间的变化。
“颜色按速度”的工作方式类似于“颜色随寿命”的工作方式,但它是根据粒子的速度而不是时间来工作的。大小和旋转随寿命或速度的变化类似。
例如,外力可以被修改以使粒子受到风的影响。“噪波”允许您将湍流添加到粒子的运动中,“碰撞”允许您指定粒子可以碰撞的多个碰撞平面。
触发器允许您根据粒子是在碰撞形状内部还是外部来执行脚本代码。子发射器允许每个粒子在另一个系统中发射粒子。纹理片动画允许您指定纹理片资源,并对每个粒子进行动画/随机化。灯光用于控制附加到粒子上的光源,轨迹用于将轨迹附加到粒子上(在下一节“轨迹渲染器”中会详细介绍)。自定义日期非常复杂,并且允许粒子与脚本或着色器进行交互。
至于渲染器的属性,你可以定义如何渲染粒子,例如,控制它们的颜色,轨迹,渲染模式,排序模式,最小/最大尺寸,相对于相机的对齐,沿轴翻转/旋转,以及它们如何与阴影/灯光交互。
轨迹渲染器
轨迹是粒子系统的另一种形式,它画出了游戏对象的位置(图 3-29 )。把它们想象成尾巴。例如,如果你有一架飞机,你可能希望它的机翼上有轨迹,以模拟在空中飞行的效果。
图 3-29
来自场景中游戏对象上的轨迹渲染器组件的轨迹
轨迹渲染器(图 3-30 )组件的第一件事是某种宽度(y 轴)对时间(x 轴)的图表。你可以在图上添加更多的点,让轨迹随着时间变大或变小。
图 3-30
轨迹渲染器组件
-
时间轴(x 轴)的值实际上对应于轨迹被设置为渲染的总时间的百分比,由 Time 属性定义(在我们的示例中为 5)。这个时间值 5 可以被解释为踪迹将持续的最大时间。下面是一个例子。如果该值设置为 10,并且汽车持续行驶,将会持续产生多段踪迹,看起来好像踪迹将达到最大长度值 10。轨迹将保持这么长(看起来像一个长矩形),因为新的片段正在产生,以取代汽车上的前一个片段,最大值为 10,直到汽车刹车。轨迹会变得越来越短,直到长度为 0。这需要 10 秒钟的时间,因为每一个棋子在产生后 10 秒钟就会被销毁,最远的棋子最先消失。
-
宽度轴(y 轴)的值将对应于在特定时间点形成的轨迹的宽度。
最小顶点距离是从上一个顶点开始在轨迹上产生一个新点的最小距离。
当没有踪迹时,勾选自动毁灭会自动毁灭以踪迹渲染器为组件的游戏对象。不点击发射将暂停轨迹生成。
“颜色”允许您为沿着轨迹的颜色设置渐变。拐角顶点是为每个拐角添加的顶点数量。端帽顶点是要添加到轨迹每一端的顶点数。
对齐允许您旋转轨迹以面向其变换组件或相机。如果选择使用 TransformZ 模式,线将沿变换的 XY 平面拉伸。
另一方面,纹理模式可以设置为另一种模式,这取决于您希望如何放置坐标。
如果选中,生成照明数据将为关联的着色器生成数据。
可以应用阴影偏置来防止自阴影伪像。值为 0.5 表示每段轨迹宽度的 50%。
您将在下一节了解材质,但是现在,假设它们定义了轨迹的颜色。
照明可以让您选择投射或接收阴影的方式。同样,探针也是关于照明和反射的。
3.3 材质
如前所述,材质可以改变装备了渲染器组件的游戏对象的外观。这包括对象的纹理、颜色和平滑度,以及其他几个属性。材质拥有的属性由该材质使用的着色器定义。着色器定义了对象的外观,材质可以被视为着色器的一个实例,就像数据类型和变量一样。在项目窗口中右键单击任意位置并指向创建➤材质,可以创建一个材质(图 3-31 )。您可以重命名刚刚创建的材质。
图 3-31
创建材质
要将一个材质应用到场景中的游戏对象,你可以简单地将它直接拖放到场景窗口中的对象上,层次窗口中游戏对象的名称上,或者检查器视图的底部,如果有问题的游戏对象被选中的话(图 3-32 )。
图 3-32
使用渲染器将材质应用到游戏对象
要编辑一个材质的属性,你可以简单地在任何一个应用了它的渲染器的游戏对象上展开它,或者在项目窗口中选择它,在检查器窗口中进行(图 3-33 )。
图 3-33
材质的特性
默认情况下,创建的材质将使用标准着色器。这是可以改变的,符合特定的要求。出现在属性旁边的透明小方块可以被赋予 2D 纹理,这样它们就不会像默认情况下那样显得单调。
反照率是材质的主色,可以挑别的颜色。使用该材质的对象的所有实例将实时显示和应用更改。
“金属色”和“平滑度”滑块将分别使材质的颜色看起来或多或少有点金属色和平滑度。也可以将源更改为反照率 Alpha 来模拟另一种效果。
如上所述,您可以为法线、高度、遮挡和细节遮罩设置 2D 纹理。勾选发射后,您可以使材质发射 HDR 颜色。
例如,如果您有一个使用栅格 2D 纹理的材质,平铺和偏移就很有用。更改这些值将使材质重复自身或沿轴移动。
我不会详细讨论二级地图和其他选项,因为这对于本书来说不是必需的。
3.4 标签和层
如前所述,标签可用于例如在射击游戏中识别与敌人相撞的物体。如果是球员,我们应该移除他的健康;否则,如果是子弹,损害应该由敌人承担。
图层也可以应用于游戏对象。在编辑器中,你可以定义一层 GameObject 是否可以和另一层 GameObject 发生碰撞。默认情况下,当您创建一个层并将其指定给游戏对象时,如果您不修改任何内容,它会使用现有层与所有对象发生冲突。层也可以用于定义哪些对象由相机渲染或受特定光源影响。
在编辑➤项目设置➤物理,你可以设置许多默认的物理属性,如重力和摩擦力设置值。我们将保持目前的一切,但你可以随时改变价值观,以更好地了解自己的变化。向下滚动,你会发现层碰撞矩阵,在这里你可以设置游戏对象的层是否可以与其他游戏对象的层发生碰撞(图 3-34 )。请注意,取消勾选或禁用两层之间的碰撞将使两个游戏对象之间不可能发生碰撞。这两层中的每一层,包括 IsTrigger,都调用不再有效的内容。
图 3-34
层碰撞矩阵的一个例子
要创建新的标签或层,前往编辑➤项目设置➤标签和层,并展开各自的选项卡。对于标记,单击加号图标,输入名称,保存并关闭窗口。对于层,输入你看到的第一个空矩形命名用户层并关闭窗口(图 3-35 )。
图 3-35
标签和图层
要将标签或/和层应用到游戏对象,在场景或层次窗口中选择它,并选择要应用到它的相应标签或/和层(图 3-36 )。
图 3-36
将标签和层应用到游戏对象
3.5 脚本
脚本可以分为两类,一类是组件,另一类是独立的素材。脚本只能用 UnityScript 编写(像 C#一样,只是做了一些修改),因为其他所有以前的语言都已被弃用。当你选择游戏对象并输入你想要添加的脚本名称时,你可以通过使用检查器窗口底部的添加组件菜单来创建脚本,或者在项目窗口中右击并创建一个脚本(图 3-37 )。请注意,当您使用前一个选项,并且您键入的脚本名称不存在时,您可以直接创建一个。
图 3-37
创建脚本
创建脚本后,您还可以在项目窗口中看到它。选择一个脚本会在检查器中显示其内容的预览(图 3-38 )。
图 3-38
在检查器中查看脚本
如果您双击该脚本,它将在您在“编辑➤”首选项中设置的代码编辑器中打开。创建的任何新脚本的前三行用来引用包含类的名称空间。这些将允许您编写代码,利用流行和重要的数据类型,如列表和数组。通过您的脚本,using UnityEngine;行将让您轻松地与引擎中的其他组件进行交互。
您在脚本中编写的代码通常放在它的开始和结束花括号之间。: MonoBehaviour部分是让你的脚本实际上表现得像 Unity 中的一个组件。
接下来,如果你想声明全局变量,你可以在任何函数之外,在类的花括号内这样做。
当您进入播放模式时,void Start() {}函数中编写的代码将运行一次。写入void Update() {}的指令将每帧执行一次。如果你的游戏运行速度是 60 FPS,更新函数中的代码一秒钟就会运行 60 次。
图 3-39 显示了一个声明一个全局整型变量并使其在 Unity 编辑器和其他脚本中公开可见的例子。请注意,如果您省略了public部分,变量将默认标记为private,其他脚本将无法直接访问这些变量,也无法在编辑器中看到这些变量。
图 3-39
一个脚本如何默认+一个全局整数变量:myInt
保存你的脚本后,在 Unity 中自动编译后,你应该可以在游戏对象上看到一个新的区域,这个区域有这个脚本作为组件。在编辑器中编辑该字段的值会直接改变myInt变量保存的值(图 3-40 )。在后面的章节中,你会学到更多关于 Unity 脚本的知识。
图 3-40
从检查器中查看和修改脚本变量
四、用户界面
通常,用户界面(UI)是指用户与计算机系统交互的方式。在我们的例子中,这个术语主要指游戏中的 UI 元素,比如按钮和操纵杆,它们允许玩家与我们的游戏进行交互。此外,本章还将讨论其他类型的 UI 元素,如文本、滑块和图像,播放器不能与之交互,但可以用来显示重要信息或提供更好的用户体验。对于本章,建议您使用 2D 视图。你可以点击场景窗口顶部标有 2D 的小按钮(图 4-1 )。此外,我们将使用 Rect 工具(图 4-2 )。
图 4-1 和 4-2
2D 视图按钮(左)和矩形工具(右)
4.1 画布
在 Unity 中,2D UI 元素必须是一个叫做 Canvas 的游戏对象的子元素。默认情况下,当你在编辑器中创建一个 UI 元素时,Unity 会创建一个画布,如果还没有创建画布的话,它会使元素成为画布的子元素。现在,让我们看看一个空画布游戏对象上的多个组件。这可以通过在层次窗口中右键单击并从 UI 中选择 Canvas 来创建。您可能还注意到,在场景中创建了另一个名为 EventSystem 的游戏对象。我将首先分析 Canvas GameObject 上的不同组件。如果在场景视图中缩小,画布应该看起来像下面的截图。请注意,你也可以双击 UI 元素,或者任何游戏对象,将它们完全显示在视图中(图 4-3 )。
图 4-3
在 2D 视图的“场景”窗口中显示的画布
如前所述,UI 元素需要是 Canvas GameObject 的子元素,以便可见和/或可交互。但是,例如,如果一个画布有四个与子元素大小相同的 UI 元素,并且位于相同的确切位置,则第一个元素将首先在屏幕上绘制,最后一个元素将在它们之上绘制(在 4.3 节“文本”中演示)。
画布组件
画布组件中有三种呈现元素的模式(图 4-4 )。
图 4-4
画布组件
在第一种默认模式“屏幕空间-覆盖”中,画布将与屏幕大小相同,因此与屏幕分辨率相匹配。如果勾选了像素完美,则渲染 UI 时不会消除锯齿以提高精确度。如果在场景中使用了具有相同模式的多个画布组件,则排序顺序值最高的组件将被渲染。更改目标显示允许您在编辑器中测试多个画布或视图。我不会真的去详细说明这个选项或者附加的着色器通道。
如果模式设置为屏幕空间-相机(图 4-5 ),画布及其子元素将根据指定的相机游戏对象进行渲染。
图 4-5
画布组件设置为屏幕空间—相机渲染模式
另一种说法是,画布可以被认为是一个平面。您可以在“平面距离”字段中设置它与相机的距离。虽然您不会看到画布大小的变化,例如,如果您正在使用 3D 游戏对象,那么画布与相机的渲染距离将会有明显的差异。图 4-6 展示了这一点。
图 4-6
带有相机屏幕空间模式和 3D 对象的画布示例
第三个也是最后一个模式,世界画布,使画布在游戏中表现为 3D 平面。使用这种模式可以让你在一个场景中拥有尽可能多的画布游戏对象,它们都将由指定的摄像机渲染。例如,在一个游戏中,这种模式可以用来制作漂浮在敌人身上的生命值条。因此,画布将与其子滑块所代表的敌人处于相同的 3D 位置和旋转。图 4-7 举例说明。
图 4-7
世界空间中带有健康栏的画布
监视器的屏幕也使用了一个世界空间画布游戏对象。
画布缩放器
画布缩放器(图 4-8 )是一个非常有用的组件,它允许我们指定画布应该如何根据不同的屏幕尺寸进行渲染。它仅适用于前一组件的屏幕空间模式。
图 4-8
画布缩放组件
在第一种模式下,画布将始终保持相同的大小。例如,如果你为一个 720p 屏幕制作一个游戏,并相应地调整你的元素的大小以匹配它,当分辨率加倍时,所有的元素将仍然是相同的物理大小,因此,看起来小两倍,假设屏幕大小对于 720p 和 1440p 屏幕是恒定的(图 4-9 )。增加比例因子将使画布按比例变大。最后一个选项适用于精灵,我们不会使用它,因为我们更关心 2D 游戏。
图 4-9
画布缩放组件设置为随屏幕大小缩放
您可以设置一个参考分辨率,以此作为开发游戏的目标。然后,您可以选择让画布匹配屏幕的宽度或高度,或者相应地收缩或扩展。对于前者,您可以将画布设置为仅匹配屏幕的宽度或高度。例如,将滑块一直拉到最左边,无论屏幕尺寸的像素高度是否增加,画布都不会发生变化,而宽度保持不变。但是,在这种情况下,如果以像素为单位的屏幕宽度发生变化,画布将按相等的比例缩放。
最后,在最后一种模式中,恒定物理大小,您可以根据厘米、毫米、英寸、磅或十二点活字来设置画布的实际物理大小。这就是你需要知道的全部。
由于这本书旨在帮助你开发手机游戏,特别是 Android 平台,如果我们在创建一个风景游戏,画布缩放器通常设置为匹配高度。例如,如果我们有一部分辨率为 1920 × 1080 的手机,并且游戏将以横向(水平)模式运行,则最终的屏幕高度将对应于值 1080。通常,屏幕在垂直方向比水平方向变得更大。我们的声明试图传达这样的信息,例如,如果我们的手机长宽比为 16:9、18:9 或 21:9,我们的按钮和其他元素将保持相同的大小,除非屏幕实际上更大而不是更长。因此,我们的游戏将能够在这些设备上全屏播放,而不会影响我们的元素的布局,例如它们的大小。现在,如果我们的游戏是在一个更大屏幕的设备上玩,而不仅仅是一个更长的屏幕,它将缩放并仍然正常显示。根据你想做的游戏,这并不总是你想做的,你可能会追求另一个选择。
图形光线投射仪
该组件(图 4-10 )主要用于确定画布上的元素是否被点击。
图 4-10
图形光线投射器组件
我不会详细讨论这些属性,因为您很少与这个组件交互。如你所知,它们用于调整什么物体应该能够阻挡光线投射。
4.2 直肠变换
这是一个你会在所有 UI 类型的游戏对象上看到的组件(图 4-11 )。它相当于 3D 游戏对象上的转换组件,但是对于 UI 元素,有一些额外的选项和属性。对于画布游戏对象,该组件中的值可能被锁定。
图 4-11
矩形转换组件
位置 X、Y 和 Z 对应于变换组件的典型位置Vector3。相同的逻辑适用于旋转和缩放字段。改变Width或Height值是不言自明的。请注意,如果您更改 Pos Z 中保存的值,这实际上不会有什么不同,除非两个或更多元素重叠,在这种情况下,具有最高 Pos Z 值的元素将是可见的元素。例如,如果有许多空的 2D 方块(图像),每个都具有相同的大小,并且正好位于相同的位置,但是每个都具有不同的颜色,则该组中具有最高位置 Z 的那个将是可见的。然而,这条规则也有例外。还要注意,根据所选的锚类型,字段的名称和数量会有所不同。
锚点指定 UI 元素的位置相对于其父元素的矩形变换和位置的点。就 X 和 Y 坐标轴而言,每个最小值和最大值可以在 0 到 1 的范围内。值 0 对应于画布的最左侧位置,而值 1 对应于最右侧位置。类似地,对于Y值,该范围从画布的最高位置到最低位置。通常Vector2的最小值和最大值是相同的。
虽然定位点与元素相对于其父元素本身的定位有关,但支点则是相对于元素本身移动 UI 元素的中心。同样,X和Y的值从 0 到 1,但是这一次,它们表示基于自身宽度和高度的 UI 元素枢轴的位置。例如,如果X枢轴值设置为 0,并且您试图增加元素的宽度,枢轴将在最左端,并且如果该值为 0.5,元素将仅从其右侧缩放,而不像从其中心在两个水平方向缩放,即在元素的中心。
你也可以通过点击 Rect Transform 组件左上角看起来像网格的东西来改变元素的锚点和枢轴(图 4-12 )。
图 4-12
锚点预设
单击这些预设中的任何一个都会自动将其锚点值分配给元素。此外,如果在执行此操作的同时还按住 Shift 按钮,则 pivot 值也会以类似的方式进行修改。如果在这个过程中按住 Alt 键,不管是否按住 Shift 键,元素也将移动到锚位置,因此有一个(0,0)的Vector2位置。在了解了其他 Canvas UI 元素之后,您将能够测试所有这些元素。
4.3 文本
要创建 UI 元素,只需右键单击层次结构窗口中的空白区域,转到 UI,然后选择所需的元素。在本节中,我们将关注名为 Text 的 UI 元素。确保它是画布游戏对象的子对象。双击该元素将使其可见并位于场景窗口的中心(图 4-13 )。
图 4-13
文本用户界面元素
当然,您可以沿着文本元素的边缘拖动,使其占据更大的区域,或者手动修改其 Rect Transform 组件的值(图 4-14 )。
图 4-14
文本组件
修改文本组件上默认写入“新文本”的字段会直接改变元素上实际写入的内容。
接下来,如果在项目中导入了字体文件,您可以指定要使用的字体。您可以为文本元素选择普通、粗体和/或斜体字体样式,并指定字体大小和行距。例如,较大的字体大小值会使文本看起来更大,而较小的行距值会减少段落中各行之间的距离。勾选富文本允许您在文本中设置特定的单词,例如将它们放在类似 HTML 的标签之间。
接下来,您可以将文本左对齐、居中对齐或右对齐,以及在其 Rect Transform 组件占据的区域的顶部、中间或底部对齐。勾选“按几何图形对齐”将执行一些小的更改,使文本更多地反映您之前的选择,就其实际几何图形而言。
对于水平溢出,您可以选择在一行的字数超过 Rect 转换的宽度时让文本在新的一行上继续(换行),或者忽略 Rect 转换的宽度并在同一行上继续(溢出)。
对于垂直溢出,您可以选择截断或溢出。以类似的方式,在后一种情况下,文本将继续在 Rect 变换高度的边缘之前,并且只占用它所需要的空间,而在前一种情况下,在达到 Rect 变换可以容纳的最大行数之后,其余的文本或行将被丢弃,并且不可见。
勾选最佳匹配允许您设置文本字体大小的最小和最大值。这些值将覆盖以前的字体大小字段。只要 Rect 变换具有足够容纳所有文本的区域,文本的字体大小就不会小于最小值,并且倾向于尽可能接近最大值。如果 Rect 转换可以轻松容纳最小值的所有文本,字体大小将在内部增加到一个更高的值,但小于或等于最大值设置,而 Rect 转换可以容纳全部文本。
请注意,如果在不使用溢出模式的情况下,指定大于 Rect 转换所能容纳的字体大小或最小字体大小,则文本中的部分或全部字符可能不可见。
接下来,如果您希望文本具有某种效果,如水平渐变,您可以更改文本的颜色并指定一种材质。
勾选 Raycast 目标允许您稍后通过脚本添加事件,例如,当您希望在单击文本 UI 元素时发生一些事情。
4.4 图像
请记住,位于画布子元素列表底部的 UI 元素会呈现在列表中位于它们上方的元素之上。如果我们创建一个 UI 图像元素,并将其放在画布子元素列表中的文本元素之前,会发生什么情况(图 4-15 ):
图 4-15
在文本 UI 元素上呈现图像
然而,如果文本和图像元素交换位置,图像将位于文本元素的前面或顶部(图 4-16 )。
图 4-16
在图像 UI 元素上呈现文本
该组件可用于向用户显示非交互式图像。您可以将此用于装饰或图标等元素(图 4-17 )。
图 4-17
图像组件
图像可以设置为显示实际的图片图形。请注意,图像将被缩放以匹配 Rect 变换的尺寸。您可以将图像素材标记为 Sprite 2D 类型,这样您就可以将它分配给 UI 图像组件的 Source Image 属性,以便它显示该图像。要将导入的图像标记为精灵 2D 类型,您只需在项目窗口中选择它,并在检查器中将其纹理类型更改为精灵(2D 和用户界面)(图 4-18 )。在第六章中,我们将为暂停按钮使用雪碧 2D 纹理。
图 4-18
标记为 2D 雪碧的纹理
在图像组件上设置另一种颜色可以看作是在图像上放置一个颜色过滤器。Material 属性可用于在要渲染的最终图像上应用其他效果。勾选光线投射目标让 Unity 考虑光线投射的图像。
4.5 原始图像
与图像不同,只有纹理类型的图像才能在原始图像组件上渲染(图 4-19 )。
图 4-19
原始图像组件
颜色和材质属性的工作方式与图像组件中的类似。至于Vector2值X和Y以及W和H,它们分别对应于所分配纹理的位置和大小,相对于矩形变换组件。修改X和Y值将使纹理从矩形变换的中心偏移X和Y的量,并且修改W和H值将相应地改变纹理相对于矩形变换的实际宽度和高度的宽度和高度。
4.6 滑块
Slider UI 元素在各种情况下都很有用。它们可以用来轻松地为敌人和/或玩家制作生命条,或者是可交互的,以便改变一些游戏内的值,例如音量(图 4-20 和 4-21 )。
图 4-21
滑块组件
图 4-20
滑块
如果互动保持勾选,你将可以在游戏进行的时候拖动和调整滑块的旋钮。过渡允许您根据滑块的值或旋钮的状态设定视觉反馈。
例如,使用色调过渡允许您为旋钮设定定义的颜色,这取决于旋钮是被停用还是被按下。以类似的方式,子画面交换导致正在使用的子画面发生定义的变化,并且动画将相应地触发设置动画。当然,如果转换被设置为无,则不会有任何变化。
导航中的选项允许您通过键盘按键控制滑块。如果不希望滑块的值发生变化,请使用键盘按键将该属性设置为 None。Fill Rect 和 Handle Rect 是必须指定的 Rect 变换组件,以便滑块知道它的填充和手柄在哪里。
方向直观地定义了滑块的最小值和最大值所在的位置。如果设置为从右到左,滑块可以容纳的最小值将在滑块的最右端可见。
在 Direction 属性的正下方,有两个字段允许您设置滑块可以容纳的最小值和最大值。如果旋钮位于滑块的任意一端,它将代表我刚才讨论的字段中设置的最小值或最大值,这取决于方向属性。
勾选整数将确保滑块代表的值仅为整数。当此选项被勾选时,带有小数/分数部分的数字不会出现。
通过调整值滑块所做的更改也将通过场景和游戏窗口中的滑块反映出来。此值滑块从最小值(左)设置一直到最大值(右)设置。
最后,您可以设置滑块在它的值改变时做一些事情。例如,当滑块的值改变时,你可以让滑块调用游戏对象的某个脚本中的函数。我们稍后将利用这一点。
接下来,让我们快速看一下默认情况下组成 Slider GameObject 一部分的子对象(图 4-22 )。
图 4-22
典型的 Slider UI 元素的子游戏对象
背景有一个图像组件,是一个完整大小的滑块游戏对象。更改该组件的属性将直接改变滑块的背景。
填充区域只是一个空的游戏对象,但是它是滑块填充正常工作所必需的。接下来,填充游戏对象的工作方式类似于背景游戏对象。由于滑块的值更接近其最大值,填充将占据更大的区域并覆盖背景。
手柄填充区域表示滑块手柄可以移动的区域。最后,句柄只是一个图像组件(带有 Rect 转换),默认情况下使用一个普通的白色圆形精灵。如果你决定改变游戏中滑块的值,手柄就是你要与之交互的可视组件。
4.7 按钮
按钮可以被认为是在游戏中为了做某些事情而与之交互点击组件。按钮的一个很好的例子是看起来像箭头的东西,当你按下它时,它会让你的角色跳起来。默认情况下,按钮在创建时看起来如下(图 4-23 ):
图 4-23
一个按钮
要改变按钮的大小,只需在游戏对象的 Rect Transform 组件中编辑适当的值。要更改其默认外观,根据需要编辑其游戏对象上的图像组件(图 4-24 )。
图 4-24
按钮组件
前几个属性的作用与 Slider 组件的相同。如果未勾选可交互,您将无法与按钮交互。转换基本上定义了按钮的外观,这取决于按钮被按下或禁用时的状态。
渐隐持续时间值越小,反映的变化越快。例如,如果按钮被按下,并且正在使用色彩过渡,则如果“淡化持续时间”值设置为 0(秒),按钮将立即过渡到“按下的颜色”中指定的颜色。
同样,可以将按钮设置为执行某些操作,如该组件底部的 OnClick()列表中所定义的。
默认情况下,按钮也有一个文本游戏对象作为子对象。然而,如果你不需要它,你可以安全地删除或销毁它。
4.8 输入字段
输入字段实际上只是一个文本框。点击它将调出移动设备上的默认键盘,并允许您键入一些内容。作为子元素,它有两个文本 UI 元素。第一个称为占位符,它将包含一些虚拟文本,而没有输入任何内容。另一个文本 UI 元素将包含您键入的文本。您可以修改这些文本 UI 元素的属性,以获得您想要的样式(图 4-25 )。
图 4-25
输入字段
在输入字段组件上,您将再次发现交互、转换和导航属性。这些将做与按钮和滑块相同的事情。
与文本相关的属性分别表示用于显示键入内容的文本 UI 元素、直接映射到该文本 UI 元素的文本属性的字段以及该输入字段可以容纳的最大字符数。请注意,占位符文本将会消失,除非在 text 属性中没有设置任何内容。如果不为空,用户输入的数据前面将会有我们设置的文本。
为要输入的数据类型选择适当的内容类型有助于定义如何显示输入的数据(图 4-26 )。例如,如果设置为 Password,当用户在输入字段中输入内容时,所有字符将自动替换为星号。请访问 InputField 的文档,了解许多内容类型选项之间的区别。
图 4-26
输入字段组件
根据您在“内容类型”中设置的值,您可能还有一个“行类型”属性,该属性允许您选择是否只能在一行中设置数据格式(单行),是否可以跨多行设置数据格式,或者用户是否可以通过按 Enter/Return 键(多行换行)或不按下 Enter/Return 键(多行提交)来跨越一个新行。使用后一个选项,文本将在需要时自动跨多行。
默认情况下,Placeholder 属性只引用输入字段的第一个子字段,作为包含占位符文本的文本 UI 元素。
脱字符号闪烁速率中的值定义脱字符号每秒闪烁的次数。插入符号宽度中包含的较高值将使插入符号更宽,您也可以通过勾选自定义插入符号颜色并选择一种颜色来为插入符号选择自定义颜色。
选择颜色是字符被选中时的突出显示颜色。勾选隐藏移动输入将在 iOS 设备上隐藏屏幕键盘上的原生输入栏。
如果勾选了只读复选框,将无法在输入字段中输入更多字符。
最后,您可以向 OnValueChanged()和 OnEndEdit()列表添加操作。只要输入字段中保存的值发生变化,就会执行前一个选项中的操作。例如,如果您进入播放模式并输入三个字符,OnValueChanged()将被调用三次,任何操作集将被执行三次。至于 OnEndEdit(),每次用户完成编辑文本内容时,都会执行在那里分配的操作,无论是提交内容还是单击某处将焦点从输入字段移开。
4.9 切换
Toggle 与 Button 非常相似,工作方式基本相同。唯一的基本区别是 Toggle 在特定时间有一个True或False值(图 4-27 )。每次单击或触摸切换按钮时,它都会在这两个值之间交替变化。如果它在被点击/触摸之前是True,它将有一个False值;否则,它将有一个True值。
图 4-27
一个开关
在一个 Toggle 组件上(图 4-28 ,IsOn 表示 Toggle UI 元素的True / False状态。勾选此项时,切换处于真实状态。
切换过渡属性允许您设置用户与切换 UI 元素交互时的视觉效果。
图 4-28
肘节组件
图形被设置为具有图像组件的游戏对象。这个图形将代表切换 UI 元素的True / False状态。
至于组属性,你可以在这里分配一个带有切换组组件的游戏对象。切换组基本上是切换 UI 元素的集合。例如,一次只有一个 toggle 处于True状态,有时会很有用。这就是为什么切换组可能会派上用场,但它们超出了本书的范围。
和前面介绍的大多数 UI 元素一样,当 toggle 的值改变时,可以执行一些动作。
默认情况下,一个切换游戏对象有两个子对象(图 4-29 )。
图 4-29
典型切换 UI 元素的子游戏对象
第一个子元素是 Background,默认情况下它有一个图像组件。这个游戏对象本身有另一个图像 UI 元素作为子元素,名为 Checkmark。这个对号游戏对象激活或去激活,取决于开关的True / False状态。
两个子元素中的另一个名为 Label,它只是切换 UI 元素的背景/复选标记旁边的文本。它不是切换 UI 元素的基本部分,可以安全地删除。
4.10 下拉菜单
想象一下,创建一个定义好的选项列表,用户可以从中选择一个。这也正是下拉 UI 元素存在于 Unity 中的原因(图 4-30 )。
图 4-30
下拉菜单
当触摸/单击下拉菜单时,会显示一个包含所有预定义选项的扩展菜单。如果选项的数量要求显示比已经为下拉菜单定义的区域更大的区域,滚动条将显示在选项列表的右侧。选择一个选项后,下拉列表将折叠回其初始状态,显示当前选择的选项。
下拉 UI 元素有一个很长的子元素列表,但是由于它们的精确名称,通过使用 Rect 工具,你可以很容易地理解它们的用途(图 4-31 )。在本节中,我们将只研究 Dropdown 组件中的新属性。
图 4-31
下拉组件
模板、标题文本和标题图像分别是对矩形转换、文本和图像组件的引用。默认情况下,Template 是包含许多子对象的 GameObject,这些子对象构成了当单击/触摸下拉菜单时出现的列表的一部分。标题文本表示将写入当前所选选项名称的文本组件。Caption Image 不是必需的,但它代表的是代表当前所选选项的图像的图像组件。
项目文本和项目图像的工作方式与标题文本和标题图像完全相同,但这一次通常表示下拉列表中的选项。
Value 是选项列表中当前选定选项的索引。它从 0 到列表中的项目数减 1。
Alpha 渐变速度是转换到完全不透明的下拉列表或完全透明的下拉列表所需的秒数。
最后,您拥有预定义选项的实际列表,您可以从中添加或删除选项。对于每个选项,您可以更改名称,指定一个精灵来代表它,并在选项列表中对它进行重新排序。
4.11 滚动条
滚动条(图 4-32 )非常类似于滑块。主要的区别是滚动条为它们的句柄提供了更多的调整,但是没有可以设置的最小值或最大值,它们只能有一个从 0 到 1 的值。
图 4-32
滚动条
如果滚动条的值为 0,滚动条的手柄将位于其左边缘附近,如果值为 1,则位于右边缘附近。
你可以参考关于滑块的部分来理解滚动条组件提供的大多数属性(图 4-33 )。仅有的两个新属性是大小和步数。
图 4-33
滚动条组件
大小滑块的值总是介于 0 和 1 之间。值为 1 将使控制柄的宽度和高度等于 Rect Transform 组件的宽度和高度。步数将定义滚动条在其手柄被拖动时可能停止的次数。例如,如果该属性的值设置为 4,当您进入播放模式并拖动滑块时,在滚动条的整个宽度上,一次只能有四个位置可以放置手柄。默认值 0 允许控制柄沿该宽度自由定位。
4.12 Scrollview
Scrollview UI 元素有一个 viewport(图 4-34 )。您可以在其中放置几个 UI 元素,比如文本、图像或按钮。默认情况下,它还带有水平和垂直滚动条。您可以使用它们在放置在其视口中的元素之间滚动。
图 4-34
卷轴检视
使用 scrollview 是一种在移动游戏的定义区域放置多个元素的优雅解决方案,因为与通常在比手机屏幕更大的屏幕上玩的 PC 或主机游戏不同,移动游戏开发人员通常必须显示多个元素,这些元素通常不能太小,因为最终用户会发现很难与所显示的内容进行交互,或者无法正确感知所显示的内容。scrollview 也可以用来滚动一个大的图像或文本元素。
Content 表示 scrollview 的子视图的 Rect 变换,它将包含您放置在其视口中的所有元素(图 4-35 )。
图 4-35
Scrollview 组件
不勾选水平或垂直将在运行时禁用相应的滚动条。移动类型可以设置为无限制、弹性或夹紧。使用最后两个选项将使内容保持在滚动矩形的范围内。但是,当内容到达滚动矩形的边缘时,Elastic 会反弹内容。后者的反弹量将由弹性属性决定。
如果勾选了惯性,拖动后释放手柄,内容将继续移动。否则,内容只会在拖动时移动。如果设置了惯性,减速率将决定内容停止移动的速度。速率为 0 将立即停止运动,而速率为 1 将使运动永不减速。
滚动敏感度是使用滚轮和触控板事件进行滚动的敏感度。其他一些属性只是用来将滚动条指向视口和滚动条。滚动条有一个可见性属性。将该属性设置为 Permanent 将防止滚动条被隐藏,而 Auto Hide 和 Expand Viewport 将在不需要时隐藏内容。发生这种情况时,后一种情况也会导致视口扩展。间距是滚动条和滚动条右下角之间的距离。
4.13 面板
面板只是一个具有一定透明度的图像,它的矩形变换沿着其父对象的整个宽度和高度伸展。也就是说,如果您使一个面板 UI 元素成为设置为“屏幕空间-覆盖”的画布元素的子元素,它将在播放模式下占据您屏幕的整个可视区域(图 4-36 )。
图 4-36
面板组件
4.14 事件系统
事件系统是控制与 UI 系统的所有交互的组件,接收来自键盘、鼠标、触摸屏等的输入。,并将它们转换成与底层 UI 元素的交互(图 4-37 )。
图 4-37
事件系统组件
它计算出用户与哪个画布和控件进行了交互,并相应地激活它。如果没有事件系统,UI 将仅仅在屏幕上绘制,什么也不做。
此外,事件系统允许 UI 控件(支持事件,如复选框和按钮)在用户与它们交互时通知 Unity 项目。例如,一个用户点击一个按钮,然后按钮激活或停用另一个游戏对象。如果使用正确,这可能是一个非常强大的系统。
第一次选择对应于运行时第一次选择的游戏对象。勾选发送导航事件允许 EventSystem 发送导航事件,如移动、提交和取消。拖动阈值对应于拖动的软区域,以像素为单位。
独立输入模块(图 4-38 )是实际检测输入(键盘按键、鼠标指针点击、触摸)并向游戏发送相应事件的组件。没有这个组件,你将无法与你的游戏互动。
图 4-38
独立输入模块组件
带有String值的属性正好对应于代表游戏中一些常见输入的轴。每秒输入操作中保存的值表示每秒允许的输入数,重复延迟是每秒输入操作重复率生效之前的延迟秒数。勾选强制模块激活将强制独立输入模块激活。
在我们将要制作的游戏中,我们不需要编辑事件系统中任何组件的任何属性。
4.15 输入轴介绍
由于我们将专注于制作手机游戏,对于许多类型的游戏来说,了解 axes 如何工作是很重要的。要与冒险 3D 手机游戏互动,您可能需要使用操纵杆来移动玩家。这个操纵杆很可能会使用一个或多个轴。
像下面这样的操纵杆(图 4-39 )可以用来在 3D 游戏中向各个方向移动玩家的角色。玩家可以握住游戏杆的手柄,向他们喜欢的方向移动。
图 4-39
操纵杆轴的典型图示
所示的操纵杆利用了两个轴:水平轴和垂直轴。当手柄未按下时,它位于操纵杆总面积的中间。在这个位置,它的两个轴的值都为 0。参考上图,如果手柄被推到左上角,它将有一个近似值-0.5 来表示其水平轴,另一个近似值+0.5 或 0.5 来表示其垂直轴。
你将很快学会在手机游戏中使用操纵杆,以及如何映射它们来触发角色的动作。操纵杆允许我们做的另一件有趣的事情是根据一个轴的值来修改角色的速度。例如,如果我们将角色的最大速度设置为 10 单位/秒,我们可以将它乘以操纵杆纵轴的当前值,这样我们就可以通过不完全向上推操纵杆手柄来使角色走得更慢。