变量的作用域
我们即将结束对变量的讨论,但还有一个更重要的主题需要介绍:作用域。类似于访问修饰符确定哪些外部类可以获取变量的信息,变量的作用域用于描述给定变量的可用区域。
C#中的变量有三种级别的作用域,如图3-7 所示。
- 全局作用域是指变量可以在整个程序中(在本例中是游戏)访问。C#不直接支持全局变量,但这个概念在某些情况下很有用,我们将在第 10 章介绍。
- 类或成员作用域是指变量在所属类的任何位置都能访问。
- 局部作用域是指变量只能在特定代码块内访问。
注意:我们所说的代码捷是指任何一对花括号包含的区域。这些括号在编程中作种视觉上的层级结构;右缩进越深,它们在类中的嵌套就越深。
下面我们解释一下图中的变量。
- characterClass 变量声明在类的顶部,这意味着我们可以在 LeamingCurve类的任何位置通过名称对它进行引用。你可能听说过变量的可见性这一概念这是一种很好的思考方式
- characterHealth 变量声明在 Stat 方法内部,这意味着这个变量仅在 Start 方内部可见。我们仍然可以在 Stat 方法中访问characterClass 变量,但是,如尝试从Stat 方法以外的任何位置访问 characterHealth 变量,则会报错。
- characterName 和 characterHealth 变量是一样的,前者只能在 CreateCharacta方法内部访问。这里只是为了说明类中可以有多个甚至嵌套的局部作用域。
如果经常与程序员打交道,就会听到关于变量的最佳声明位置的讨论或争论。答案比你想象的要简单: 声明变量时应牢记其用途。如果有需要在整个类中访问的变量就将其声明为类变量。如果仅在代码的特定部分需要变量,就将其声明为局部变量。
运算符
编程语言中的运算符表示类型可以执行的算术、赋值、关系和逻辑运算等。算运算符表示基本的数学运算,而赋值运算符则表示对给定的值可以同时执行数学和值运算。关系运算符和逻辑运算符用于计算多个值之间的条件,如大于、小于或等于此时,有必要先介绍算术运算符和赋值运算符,我们会在第4 章介绍关系运算符和辑运算符。
算术和赋值
算术运算符如下:
- +,表示加法
- -,表示减法
- /,表示除法
- *,表示乘法
- C#运算符遵循常规的运算顺序,先计算括号内的值,再进行指数、乘法、除法加法和减法运算。
例如,以下算式即使包含相同的值和运算符,结果也会有所不同:
1. 5+4-3/2*1=8 2. 5+(4-3) /2*1=5
通过把算术和等号结合在一起使用,赋值运算符可用于任何数学运算的简写替换例如,如果想乘以某个变量,以下两种方式会产生相同的结果
1. int currentAge= 32; 2. currentAge = currentAge * 2;
另一种方式如下:
1. int currentAge = 32; 2. currentAge*= 2;
注意:
在 C#中,等号被视为赋值运算符。以下赋值运算符跟之前的示例遵循相同的语法:+-、=和/分别用于加法赋值、减法赋值、除法赋值。字符串对于运算荐来说是一种特殊情况,因为它们可使用加号来拼接文本,如下所示
string fullName ="Joe"+"Smith";
提示:
这种方法产生的代码往往十分笨拙,大多数情况下,字符串插值是拼接文本的首选方法。
实践一-执行错误的类型操作
我们已经学习了类型的一些规则,用于控制如何进行操作和交互,但还没实践过.下面尝试把浮点数和布尔值相加,就像我们之前执行的数字运算一样:
// Use this for initialization void Start () { Debug,Log(firstName + allGood); }
刚刚发生了什么
控制台中会出现一条错误消息,指示我们不能把浮点数和布尔相加。当看到这种类型的错误时,请返回并检查变量类型是否兼容。
定义方法
前面一章,我们简要介绍了方法在程序中扮演的角色一 一存储和执行指令,就变量存储值一样。现在,我们需要理解方法的声明语法,以及它们如何在类中驱动作和行为。
基本语法
同变量一样,方法的声明也有一些基本要求:
- 需要返回数据类型。
- 必须具有以大写字母开头的唯一名称。
- 方法名的后面需要有一对括号。
- 需要使用一对花括号标记方法体(指令的存储位置)。
把以上所有这些要求放在一起,就可以得到如下简单的方法蓝图
1. returnType UniqueName () 2. { 3. method body 4. }
下面分析 LearningCurve 脚本中默认的 Start 方法。
Start 方法以 void 关键字开始,如果方法不返回任何数据,那么可以使用 void兰键字作为返回(数据)类型
Start 方法有唯一的名称。
名称后有一对括号,用于保存任何可能的参数。
方法体由一对花括号定义。
.修饰符和参数
与变量和输入参数一样,方法也有四个可用的访问修饰符。参数是变量占位符,可以传递到方法中并在方法内部访问。输入参数在数量上没有限制,但每个参数都要用逗号进行分隔,并且都有自己的数据类型和唯一的名称。
更新后的方法蓝图如下所示:
1. accessModifier returnType UniqueName (parameterType parameterName) 2. { 3. method body 4. }
提示:
如果没有显式的访问修饰符,则方法默认为私有的。与私有变量一样,利方法不能从其他脚本中调用。
为了调用方法(意味着运行或执行指今),我们需要输入方法名,后跟一对带或带参数的括号,并以分号结束:
// Without parameters UniqueName(); // With parameters UniqueName(parameterVariable);
实践一一定义一个简单的方法
我们曾一味地把AddNumbers 方法复制到 LearingCurve脚本中,而没有深入了解方法的细节。这一次,我们将有目的地创建方法。
// Use this for initialization void Start () { GenerateCharacter(); } public void GenerateCharacter() { Debug,Log("Character: Spikd'); }
- 定义一个public 方法,返回类型为void,名称为GenerateCharacter
- 调用简单的 Debug.Log 方法,打印喜欢的游戏或电影中的角色名
- 在Start方法内部调用 GenerateCharacter 方法并单击 Play 按钮
刚刚发生了什么
当游戏启动时,Unity会自动调用Stat 方法,而Start 方法又会调用GenerateCharacter 方法并打印角色信息到控制台。
2.命名约定
与变量一样,方法也需要唯一的、有意义的名称以便在代码中区分它们。方法用来驱动操作,所以最好在命名时记住这一点。例如,GenerateCharacter 听起来像是命令,在脚本中调用时也很好理解,而像 Summary 这样的名称就很乏味,因为无法清晰地描绘出方法的作用。
方法名总是以大写字母开头,并且后续任何单词的首字母也需要大写。这种命名风格又称为帕斯卡大小写(PascalCase)。
3.方法是逻辑的绕行道
我们已经看到,代码行是按照编写的顺序执行的,但是引入方法会带来一种特殊情况。调用方法相当于告诉程序绕道进入方法的指令,逐个运行它们,然后在调用方法的地方按顺序继续执行。
看看自己是否能弄清楚调试日志会以什么顺序打印到控制台。
(1)程序将首先打印“Choose acharacter.”,因为这是代码的第一行。
(2)等到调用 GenerateCharacter 方法时,程序跳到第 23 行,打印 Character:Spike,然后回到第 17 行继续执行。
(3)在 GenerateCharacter 方法的所有代码运行结束后,打印“Afine choice.”
指定参数
所有方法不可能都像GenrateCharater 方法那样简单。为了传递额外的信息,们需要定义方法可以接收和使用的参数。方法的每个参数都是一条指令,需要满足以下要求:
- 拥有显式的类型。
- 拥有唯一的名称
- 是不是很熟悉?方法的参数在本质上是经过简化的变量声明,它们执行相同功能。每个参数的作用类似于局部变量,只能在特定方法的内部访问。
实参赋值
如果形参是方法可以接收的值的类型的蓝图,那么实参就是值本身。
- 传入方法的实参总是需要匹配形参的类型,就像变量的类型与值一样。
- 实参可以是字面值(如数字 2)或在类中其他位置声明的变量。
实践一添加方法参数
下面更改GenerateCharacter 方法以接收两个参数。
(I)添加两个参数:一个是用于角色名称的字符串类型,另一个是用于角色等级的整数类型
(2) 更改 Debug.Log 以使用新参数。
(3)使用自己的实参字面值或声明的变量)更改 Start 方法中的 GenerateCharacter方法调用。
// Use this for initialization void Start () { int characterLevel = 32; GenerateCharacter("Spike",characterLevel);//实参 } public void GenerateCharacter(string name,int level)//形参 { Debug.LogFormat("Character: [0] - Level: (1],name, level); }
刚刚发生了什么
name(字符串)和 level(整型),并在 GenerateCharacter 方我们定义了两个参数一法中像局部变量那样使用它们。当我们在 Start 方法中调用 GenerateCharacter方法时,我们为相应类型的每个形参添加了实参值。在引号中使用字符串与使用characterLevel 变量可以产生相同的结果,
指定返回值
除了接收参数,方法还可以返回任何 C#类型的值。前面的所有示例都使用了 void类型,这表示不返回任何数据,但方法的真正优点在于能够编写指令并返回计算结果。
根据方法的蓝图,方法的返回类型可在访问修饰符之后指定。除了类型之外,方法还需要包含 retumn 关键字和返回值。返回值可以是变量、字面值,甚至是表达式,只要与声明的返回类型匹配即可。
提示:返回类型为 void 的方法仍可以使用不带任何值或赋值表达式的 retumn 关键字。一旦到达带有 return 关键字的代码行,方法就会停止执行。这在想要避免某业行为或修止程序崩清的情况下非常有用.
1添加返回类型
下面更改GencrateCharacter 方法以返回整数
- 蒋方法声明中的返回类型从 void 改为int。
- 使用 rcum 关键字将返回值设置为 level +5,
public int GenerateCharacter(string name, int level) { Debug.LogFormat(“character: (0) - Level: (1y", name, level); return level + 5; }
刚刚发生了什么
现在,GenerateCharacter 方法会返回一个整数,这个整数是通过对 level 参数加!计算得到的。我们没有指定如何或是否使用这个返回值,这意味着脚本现在不会做何新的事情。
2.使用返回值
返回值的使用方式有以下两种:
- 创建局部变量来存储返回值
- 将调用方法本身用作返回值,从而像变量那样使用。调用方法是触发指令的实际代码行,GenerateCharacter("Spike",characterLevel)。如果需可以将调用方法作为参数传递给另一个方法。
3.实践捕获返回值
可通过两条简单的调试日志来捕获和使用返回值,
(1)创建一个名为 nextSkilLevel 的整型局部变量,并将已经准备好的GenerateCharacter 方法调用的返回值赋给这个变量。
(2)添加两条调试日志:第一条输出 nextSkilllevel: 第二条输出一个新的调用方法,参数值由你选择。
(3)用两个反斜杠(/注释掉 GenerateCharncter 方法中的调试日志,使控制台输出不那么杂乱。
(4)保存脚本并在 Unity 中单击 Pay 按钮。
// Use th1s for 1n1t1o11zation vold Start () { int characterLevel m 32: int nextSkillLevel m GenerateCharacter("Spike",charactertLevel); Debug.Log(nextSk111Level); Debug.Log(GenerateCharacter("Faye",characterLevel)): } public int GenerateCharacter(string name, int level) { //Debug.LogPormat("Characteri (0) - Levet: (1)": hame, tevel); return level + 5; }
刚刚发生了什么
对于编译器来说,nextSkillLevel变量和 GenerateCharacter 方法调用都表示相同的信息,这就是两条日志均显示数字37的原因。
常见的Unity方法
现在,我们实际讨论 Uniy C#脚本中最常见的两个默认方法: Start Update与自定义方法不同,Unity引擎会根据它们各自的规则自动调用属于 MonoBeha类的方法。在大多数情况下,脚本中至少要有二个 MonoBehaviour 方法来启动自代码,这很重要。
Start方法
Uniy在启用脚本的第一调用的就是 Start 方法。MonoBehavior 脚本几乎总是附加到场景中的游戏对象上,当你单击 Play 按钮时,它们的附加脚本在加载的同时将被启用。在我们的项目中,LeamingCurve 脚本被附加到 Main Camera游戏对象这意味着当 Main Camera 被加载到场景中时,Start 方法就会运行。Stat 方法主要用在Update 方法运行之前第一次设置变量或者执行需要发生的逻辑。
注意:到目前为止,所有示例都使用了 Start 方法,即使它们没有执行设置操作但这通常不是使用 Start 方法的最佳方式。然而,Start 方法只需要触发一次这使其成为在控制台中显示一次性信息的绝佳工具。
Update方法
如果有足够多的时间查看参考脚本中的示例代码,就会注意到绝大多数代码是用 Update 方法执行的。。当游戏运行时,场景秒会刷新很多次,这被称为倾率或每传输帧数(Frames Per Second,FPS)。
在显示每一帧之后,Unity 会调用 Update 方法,Update 方法是游戏中执行次数多的方法之一,这使其非常适合用来检测鼠标和键盘输入或运行游戏逻辑。
如果对计算机的FPS 感到好奇,请在 Unity 中单击 Play 按钮,然后打开 Game视图中右上角的 Stats 选项卡。
总结
本章从编程的基本理论及构成要素快速过渡到了实际代码和C#语法层面。我们已经看到了代码格式的不同写法,学习了如何将信息调试到 Unity 控制台,并创建了第一个变量。对 C#类型、访问修饰符和变量作用域的介绍紧随其后,因为我们需要在Inspector面板中使用成员变量并开始涉足方法和操作。
方法有助于我们理解代码中的指令,但更重要的是掌握如何正确地运用它们的功能来实现一些有用的行为。输入参数、返回类型和方法签名都是重要的主题。你已经掌握了编程的两个基本模块,从现在开始,你要做的几乎所有事情就是对这两个基本模块进行扩展或应用。
在第4章,我们将介绍一种称为集合的 C#类型的特殊子集,集合可以用于存储组相关的数据,此外,我们还将介绍如何编写基于决策的代码。