首先还是要端正一下学习态度。我们在这里谈的游戏说到底是计算机软件的一种。你很难不通过编程来实现它。
不过对于“编程”的定义应该保持更开放的态度。写代码很可能是在编程,但是编程不一定是(传统意义上的、以文本示人的)写代码。编程无非是利用一种方法来表达我们想要计算机完成的工作。除了传统的编程语言以外,也有通过节点图来进行游戏编程的。Unreal的蓝图和Unity的Bolt都是这类的。
其实Godot 3.x中也有一个类似的可视化编程接口,但是在4.0之后移除了它。创始人Juan在4.0正式发布之前的一篇文章中解释了一些原因。他提到可视化脚本需求“很怪“,”它是很多计划用Godot但还没开始用的用户想要的功能“,而”Godot的大部分特性都是由实际在使用Godot的用户发起请求的,而非潜在用户“。不过当时的特性提议机制并非以实际用户的请求优先,所以最后3.0中加入了VisualScript。 然而VisualScript并没有满足期待,用的人并没有那么多,开发团队从没有使用VisualScript的用户那里得到的原因之一是:
很多想要这个功能的潜在用户,最后发现GDScript实际上很趁手,很可能最后还是更喜欢GDScript。由于当时流行的引擎并没有提供这样的高级(high level,不是advanced)脚本系统,他们并没有预料到GDScript是如此地易学易用。对于这部分用户中的很大一部人来说,Godot最后反而成了学习编程的工具。
这从侧面上证明了GDScript对于新手来说足够简单易学。所以我们还是要直面GDScript。
由于这一系列文章是面向所有人群的,对于有经验的开发者或者相关从业人员来说,有的地方可能略显罗嗦,对这部分读者略表歉意。
另外在任何的讲解、教学过程中大家可能很讨厌“这里先就这样记着,具体的后面再说”,但是由于很多地方展开之后“就算不够一本书那至少也是一学期的课”,为了整体的效果无法完全避免这样的做法。尽力!
GDScript的设计目的并非像C++、C#、Python等通用编程语言那样,它目前只为Godot引擎服务,因此目前也只能在Godot中使用。所以我们得在一个Godot项目中来学习它。你可以直接用前面的文章里创建的项目,不过可以新建一个场景然后新建一个脚本来学习。还记得怎么操作吗?不记得没关系,再回去复习一下。这里简单创建一个只有Node2D节点(其实其他2D节点也可以)的2D场景就行了,然后创建一个脚本,名字你可以随便取。
我们主要在前面提到的_ready函数中测试我们的代码,因为这样只要一启动游戏就能看到代码的效果了,比较方便。
之前我们都是一笔带过模板中的内容,这次我们就好好看一下模板里面的代码到底是啥意思。
打开你的新脚本,如果找不到可以直接点上方的Script图标切换到脚本编辑器。如果你的屏幕不是很大,脚本的某些行可能显示不全。此时可以点击脚本编辑器右上方的四个箭头按钮启动免打扰模式或者按快捷键Ctrl+Shift+F11展开编辑器:
第一行extends Node2D表示该脚本继承了Node2D节点。还记得在创建脚本的对话框中选择要继承哪个节点吗?
一般来说我们在新建脚本时,Godot为自动为我们选择继承节点所属类型,你也可以自己选择要继承哪个类型,也可以选择自己的脚本中定义的类型。实际上,我们新的脚本至此就成为了一个新的类型(或者说一个新的”类“,这是面向对象编程中的术语),但它仍具有它所继承的类型的各种功能。这样我们就不用每次都重新编写代码实现一些基本功能了。
说白了,extends后面的类型,必然是所附着在的节点所属的类型,或者它的爹、它的爷或者它祖上某个长辈。
extends本意就是扩展的意思。extends在GDScript中是关键字(keyword),编程语言中的关键字就是说它有特殊含义,你不能随便乱用。顺带一提,Java和TypeScript也用extends表示类型的继承。
第三行代码是一行注释。编程语言中的注释(comment)是不影响程序含义的说明性文本。它的作用就是方便开发者留下一些笔记或者解释一些东西。GDScript的注释以#开头,在该行行内,井号后面的内容都会被视为注释(这一点和Python保持一致)。当然,如果你的注释有多行就需要每行开头都有井号了。注释可以是任何文字。可以试着在前后写写注释。
连续有多行注释的时候,左侧会出现一个箭头可以折叠这一段。
上一篇文章我提到,这是两个特殊的函数。一个是节点进入场景时运行,一个是每帧运行。不过函数怎么就成了函数呢?
函数的定义首先以关键字func(function的缩写,但是Python用的是def,define的缩写)开头,随后空格。这里的f是函数的名称。函数名称可以是任何的名称,汉字也可以!但是名称之间不能有空格或者其他特殊符号(比如你不能写个#吧)。
函数名后面有一对括号,注意!这里以及后面提到的各种符号都是英文符号!像(小/圆)括号、逗号、句号、冒号、分号、感叹号、问号这些符号中文输入法打出来的符号和英文是不一样的,在写代码时需要特别注意,这是很多刚接触编程的朋友很容易遇到的错误。
函数名称后面的括号有没有让你回想起数学课?小学课堂中老师一般会写到这样的式子:
y = f(x)
老师会称括号中的x为”自变量“。当然随着后续的数学学习,也可以叫它参数(parameter)。包括GDSCript在内的很多编程语言中,函数名后面的括号中的内容就是参数列表。上面写的这个最简单的函数中,f后面的括号里啥也没有,也就是说它不接受参数,它没有参数。同样可以看到,模板中的_ready函数也没有参数。
括号后面是冒号,到这里我们函数的头部就结束了。对于一个函数来说主要分为两部分,我们可以称之为头部(header)和主体(body)。当然这两个术语不是严格定义的,大家对此说法不一样,不过body的说法还是挺常见的。函数头部表明了函数的名称以及参数,我们还需要编写函数的主体来让函数实现具体的功能。
在冒号之后的内容就是函数的主体,这里就是函数具体的代码。模板和上面的f函数中出现的pass是什么意思呢?先来做个练习,试着在脚本文件开头(extends xxx下方)编写一个名为g的函数,无任何参数,且什么也不做的函数。
编辑器下方出现了错误信息:Expected indented block after function declaration.啥意思呢,就是说Godot期望在函数声明之后有缩进(indent)过的代码块。啥是缩进?简单来说就是你写文章时(尤其是现在用计算机软件处理文本时),每行空几格。
我在前文提到,冒号过后就是函数的主体。但是,我没说的是,到哪里才是函数主体的结束呢?在Python和GDScript中,代码块以缩进级别确定。函数主体是代码块的一种,意思是会将多行代码视为一个语法元素——这里也就是函数的主体。
函数g的冒号下方什么也没有,在还不清楚该函数何时结束的情况下它又遇到另一个另一个函数定义,于是发现了错误。在Godot的代码编辑器中,缩进用类似于>|的符号表示,因为制表符(tab)本身是不可见的字符。在定义函数g的冒号后面按下回车时,编辑器发现你在定义函数,它会自动为你缩进一级(也就相当于按下了一次tab键),在你刚才练习的过程中你可能把它用退格删掉了?没事,按tab加回来。
然而,在Python和GDScript这样的语言中,某个代码块”什么也不做“不能用空行表示,必须用特殊的关键字(也是语句)pass来表示。所以g应该是这样:
注意到pass前面的空白和>|符号了吗?这就表示缩进了一级。pass位于函数g的主体代码块中。但要是这样写的话:
前两个pass都属于g的主体部分,因为它们是相邻的,且都缩进了一级。实际上你可能注意到左侧的下箭头,这个按钮按下之后可以折叠展开代码块。你可以由此体会到哪些代码是同一个代码块。第二个pass之后,没有缩进,是单纯的空行(注意没有>|)。因此这里就表示函数g的定义结束。而后面的pass不属于函数g的范围内。同样下面的f的定义也是如此解析到的。作为练习可以看看模板中的_ready和_process的定义是从哪里到哪里。
在其他一些编程语言中由于是用花括号{}来表示代码块,并且用分号表示一行代码结束,所以不需要缩进和pass来界定函数(以及其他代码块)定义。
前面提到了参数,但是我们自己的函数还没有参数。我们先看看模板中的_process。
它有一个参数。这个函数参数由三个部分组成:参数名,冒号,类型。
delta是参数名称,和函数名一样,可以是任何内容,但仍然遵循不能有那些已经用到的特殊符号。冒号右边是参数类型(type)。在编程中,类型通常特指数据类型(data type),这里的float代表浮点数,也就是小数。这里它代表帧与帧之间的时间间隔(秒)。这里的数据类型可以省略,这篇文章中我们简单起见,先无视参数类型。
下面做个练习,写一个叫double的函数,有一个参数x,什么也不做:
要想函数有多个参数也很简单,只需要在参数之间加上逗号就可以了:
只要在参数列表中出现的参数名称,在函数的主体中就可以使用,比如:
在你敲下x的时候可能注意到Godot弹出来一个窗口。这里是Godot检测到输入时为你建议的输入项目,可以按上下浏览,按tab确定所选项让它自动补全。因为x很短,所以无所谓。有时候这些名字太长的话就可以节省很多时间了。
函数是一种复用代码的方式,这样我们就不用每次都重复函数主体中定义的内容。就好比我们说“正弦函数”时不用每次都解释它是怎么回事,做数学题一般也不用解释怎么算正弦,而是直接写下“sin(x)”。
上一篇文章中我们已经用到过一个print函数,它会往控制台输出内容。
结合上面所学,print是函数的名称,括号里面是它的参数。在这里,它的参数是一个字符串字面量(literal)。字符串就是一串文字。那字面量是啥?字面量指的是编程语言中可以直接用特殊语法产生的某些数据。数字也算是字面量,但是它太过简单直白,也很基础,很多时候不会想那么多。在Godot中,字符串需要用双引号或者单引号把内容围起来(Python同),这样Godot就知道你想要的是文本内容而不是其他的东西。比如要输出字母g你得写print("g"),而不是print(g)。如果你的脚本中定义了函数g,你可以在ready函数中试一下这两种写法,然后启动游戏。后者不会出错,而是输出了函数g相关的内容。
使用函数的过程用行话来说叫调用(call,日语更直白,叫呼び出す,部分语境直接叫呼出)。调用函数时,我们在参数列表中填入实际的参数(argument)以替代定义函数时的、形式上的参数(parameter)。回到刚才的double函数,我们把它改一下然后调用:
启动游戏,输出窗口中显示4。这里我们以2为实际参数调用了double。也就是说代码运行时,double的定义中的x就获得了我们传入(pass)的2。然后执行后续的代码,用print输出了2乘以2的结果。
如果我们试着用print函数输出double(2)(即print(double(2))),我们并不会在控制台中看到4,我们看到的是一行奇怪的文字:<null>。
null在编程语言中很常见,它的意思就是啥也没有。
换言之,double函数没有返回值(return value)。
返回值是由函数交给调用方的一个结果。也就是说,对于一个函数,我们给它一个输入,它针对输入进行某些操作,然后返回一个结果——实际上在某些情况下这才是函数更精确的定义。
那么怎么让函数返回一个值?答案是用return。return的意思就是返回的意思。
return后面就是我们想要返回的结果。再次测试print(double(2)),我们就可以看到4了。
一个正常的函数(比如它没陷入死循环)总是会返回,没有return的时候它也得返回,只不过它执行完函数主体的最后一行代码后它自己就结束并返回了。return后面可以没有任何东西,这样return的意思就是我只想返回,但是没有任何结果给你。
要不要最后那个return是没有影响的。而如果在函数最后一行之前就遇到了return,那么函数也会提前结束。而对于:
你是看不到Without you my friend的。
至此,我们对默认的脚本模板有了一个不错的理解,并且还学会了编写一些简单的函数!
下一篇文章我们将进一步学习GDScript的基础概念。
评论区
共 8 条评论热门最新