上一篇中我们实际上已经讲解了函数的很多重要内容。这一篇中我们来学习另一个重要的基础概念——变量。
单刀直入,变量(variable)是给数据取的名字。
每次需要用到1的时候,我可以直接写1。但是每次写114514的时候,我每次都要按6下,就显得麻烦了,更不用说圆周率这么长的东西了。所以我可以给它取个名字:
在GDScript中定义变量时,首先写下关键字var(variable的缩写),后面是变量名称,等号后面是它的初始值。
这句话我们读作“定义变量n,并将114514赋值(assign)给它”。这里的=是等于号没错,但是在这里它不是“等于”的意思,而是要从右往左看,是“将114514赋值给n”的意思。而在编程语言中表达“等于”含义的符号将在未来讲解。
这样在每次需要用到这一数字时,我都可以通过这个变量来获得。例如在之前的可以动的logo的脚本中,我们是让它固定每次移动5个单位。现在我们来定义一个叫speed的变量,用来代表它的移动速度:
对于计算机——准确地说是对于CPU来说,数据没有任何意义,它只需要完成它的工作就好。但是对于人类来说,数据在具体的问题中有具体的含义。将数据分为不同类型有助于我们防止错误地使用数据。比如我们一般不希望将一只熊猫除以数字2。
GDScript中可以为变量标注数据类型。除了上述提到的那样可以帮助我们防止错误,还能让编辑器为我们提供更准确的自动补全建议。例如,我们将speed标注为int类型:
int是integer的缩写,即整数。我们会不断遇到各种各样的数据类型,不同的类型有不同的操作可用。实际上,各节点也有自己的所属类型。
此外,除了整数,也有小数类型,在GDScript中,小数用float表示,float指的是浮点数(floating-point):
此前我们在输出Hello World时已经用到了字符串。字符串的类型名称为String(其本意就是“串”的意思,在编程中一般就指字符串,也是弦的意思):
前面提到,类型系统的作用之一就是防止对数据进行误操作。你可以试试看把数字2给这个标注为String的变量。
编辑器会报错:Cannot assign a value of type "int" as "String".即无法把类型为int的值作为String赋给变量。要想要一个内容为2的字符串我们要写"2"。
你可能已经注意到,整数和小数之间可以自动转换不会报错。但是字符串和数字没法,我们需要一些函数来实现数字到字符串的转换,例如:
这里用到了num函数,它可以把数字转换为String。
前面我们也看到在实现获得键盘输入时出现了Input.is_action_pressed这种写法,这里该怎么解读呢?这个点就相当于说后面这个东西是属于前面这个东西的,和前面这个东西是相关的。在你输入这个点时,你可以提供自动补全建议看到String的更多可用函数,可以先自己探索一下。
告诉你一个秘密。按住Ctrl键,然后移动鼠标,你会发现脚本编辑器中很多东西下方就会出现一条下划线:
变成小手的时候点一下,此时就来到了文档中对应的部分。
这就是各类开发工具必不可少的文档(Documentation,简称docs)。会介绍各种类型、函数等等。这里就提到了这个num函数有什么用,以及它的头部是如何定义的。
可以注意到左侧的方框中显示的是已打开的文件(包括脚本文件和文档),文档看够了点击自己要编辑的文件回去就好了。右上方的箭头符号也是和各种浏览器一样功能,可以前进后退。
同样,按住Ctrl点击类型名称可以直接进入某个类型的文档,在里面会介绍该类型的各种可用函数。也可以在在线文档中查询 由于各种类型的函数有很多,不可能有人会随时记得有哪些函数、各个函数怎么用。合格的开发者的基本技能之一就是要会查文档。
前面讲解函数的时候没有提到的是,函数本身也有类型,这里主要指的是它返回值的类型。上面提到的num函数的类型应该如何解读呢?
String num(number: float, decimals: int = -1) static
最开头的是返回值类型,后面和函数定义时类似,依次是函数名称和参数列表。这里有两处我们暂时不太清楚。
首先是最后的static,鼠标放到这个词上面会弹出提示:
它说,这个方法(暂时视作一类特殊函数)不需要实例来调用,可以直接用类名来调用。很抱歉,这里暂时要卖个关子。但是它就是说我们可以直接通过String类型的类型名String加上点来调用就行了,而不需要通过某个具体的String来调用。比如我们不需要写"haha".num(123)来将123转换为字符串(尽管可以这样做,但是纯粹多此一举)。
还有一处就是decimals: int后面的=-1,这是在之前将定义函数时没有提到的。这里就是说这个decimals参数是可选的,也就是说你可以不给它传递一个具体参数,没有给定的时候就采用默认值,所以我们前面只传递一个参数就可以调用num。从文档中可以得知,这个参数决定了转换为字符串时数字保留多少位小数。-1就是默认最多保留14位。
不过,在定义函数时,函数的返回值类型并不是像文档中这样标注的(写到前面)。我们可以看看模板中的_process函数:
参数列表的括号后面有一个箭头(实际上是->),然后后面有个void。void和float一样是绿色的,暗示它也是一个类型。但是void是啥意思呢?它也是啥也没有的意思,就是说这个函数没有返回值。
这样一来,就不怕有人乱用了!现在试试看正确使用double函数然后把结果保存到一个变量中吧!如果你给了一个字符串给它,编辑器是会报错的。
提醒一下,在函数中也可以定义变量。但是一定要记得缩进。
同样,我们可以自己给函数定义默认参数,这里的语法和文档中是一样的:
但是要注意的是,如果有带默认值的参数,这些参数必须放在最后面:
这样是不行的,因为有默认值的参数可以省略,但要是省略了第一个参数,传入两个参数,怎么确定你是传入的前两个,还是后两个呢?所以要这样:
和变量相对的,常量(constant)的值无法改变。对于一些在游戏运行过程中不会发生改变、且不应该改变的值,应当定义为常量。常量用const关键字定义:
尝试修改常量的值会直接报错。最为人熟知的数学常量π,在GDScript定义为常量PI。
首先看函数g的定义。前面讲到,定义变量要用var,这里没有var。所以这个x指的是函数外面这一级的这个x。要是你把x换成一个哪里都没出现过的变量(比如y),那么就会出现错误。变量需要先定义再使用。而函数f呢,它自己定义了一个x,在后面它要print(f)时,首先就会找到函数内部定义这个x,因此也就输出1。
这里的问题是变量作用域(scope)的问题。在编程中,作用域指的就是某个名字在哪个范围内可见。简单来说,Godot会从内向外逐级查找一个名字是否已经定义,如果找到了,那就是它,也不用再往外找了。如果一直找到顶级都没找到,那就报错!
此外可以注意到,在一个脚本文件级别上,变量和函数定义的先后顺序无关,它总能找到其定义:
交换定义顺序也没问题。但是你必须确保在使用某个名字的时候,能找到它的定义。
还记得讲Hello World那篇文章最后我们复制了好几个logo出来吗?如果我们更改脚本中speed的值,大家的移动速度都跟着变了。要是我们想让它们有不同的移动速度要怎么办呢?
你可能会想,新建一个脚本,然后设置speed为不同的值。就这个需求来说,可以,但是没有灵活性。如果需要十种不同的速度难道要复制十个差不多的脚本吗?
我们已经介绍过右侧检视面板,提到可以在其中修改节点的各属性。我们能不能把自己的变量也暴露在这里直接修改呢?当然有!这里用到@export标记(annotation):
现在在场景中选中你的某个有logo脚本的节点,或者是某个已经放到了场景中的logo场景,然后看右边:
看,这里出现了一个可以编辑的项目!就是我们自己的speed变量!如果没看到的话,可以按一下保存。此外,如果你的场景中和我一样有好几个logo,还可以分别给它们设置不同的speed:
Godot会根据变量类型,在检视面板中对不同类型的export变量显示不同的编辑控件。
实际上在一开始,我们在很多地方省略了类型标注。这在GDScript中是允许的。数字可以和数字做运算,这很自然,不需要多说。
然而要是试图将a和b相加,在编辑代码时并不会提示错误。
就是说不能把加号用到int和String上。但如果我们主动写上类型标注:
游戏还没运行,编辑器就报错了,你也不能就这样运行游戏。
不标注类型,Godot对代码就行的类型检查就被延后到了游戏运行时。开发者常说的“静态”和“动态”,在很多时候谈的就是“编译时”(处理源代码的软件处理人类编写的代码的时候)和“运行时”。大家说Python语言是动态类型语言,指的是它在运行代码之前不检查代码中和类型相关的约束,但是在运行时如果出现了类似于上面这种字符串和数字相加的情况就会报错;相应的,C#是静态类型语言,说的就是它会尽可能地在运行代码之前就会执行类型检查(除非开发者用到了某些故意延迟类型检查的东西比如dynamic),发现问题。
对于GDScript来说,如果没标注类型,Godot就不会提前检查,而是在运行时检查;反之,我们就会在运行游戏之前检查。
当然如你所见,如果处处写上类型,有时候会很麻烦,但是不写类型,我们就没法获得保障。所以GDScript给你足够的自由,如果你能确定你的代码很简单,或者不需要编辑器根据类型自动补全,那么你就可以省略类型标注。在后面我们会看到,省略类型标注还有一种重要的作用就是让函数接受不止一种类型的参数的同时保持函数正常工作。
前面提到了作用域的概念。不过似乎还是解释不通为什么print这样的函数随处可用?
如果按照前面讲的方法打开print的文档,我们可以在左侧看到,这是一个叫@GlobalScope的文档的一部分,说明print属于@GlobalScope。
Global Scope的字面意思就是全局作用域,简单来说这就相当于最外层的作用域,其中定义的各种东西在任何地方都能找到。举例来说,sin、cos这样的函数,以及PI这样的常量都定义在这里。试着探索一下里面还有哪些东西。
在这一篇文章中,我们对于编程中最重要的概念之一变量有了一个稳固的基础,我们也讨论了什么是类型,什么是类型系统,以及类型标注的重要性。下一篇文章我们要认识另一个重要的基础类型,以及和函数不同的一种代码块!
评论区
共 13 条评论热门最新