用主谓宾的思想来看待Godot的GDScript?(如何正确操作对象)
本帖最后由 囿里有条小咸鱼 于 2024-7-28 22:55 编辑RT,本教程仅仅是从自然人类语言上的主谓宾角度出发,来讨论如何正确选择、操作对象。本篇教程以Godot的GDScript为例,基本上也适用于大部分面向对象编程语言。
在看本帖之前,建议先去学习godot相关的基本知识,包括节点、脚本等。
请注意:本贴中的术语不是借用就是自编,不一定具有专业性,仅供参考。
1L会不定期更新。
我们先来用通俗的语言解释下什么是主谓宾:
[*]主语:动作的发出者,话题的陈述者
[*]谓语:狭义上的谓语仅仅包括表动作和状态的语言结构;广义上的谓语也包括宾语在内
[*]宾语:谓语动作的对象
那么,我们就按照这个思路来分析下面的代码:
var ref := get_node("MyNode")
ref.queue_free()上面的代码中,我们用ref这个变量存储了对名叫MyNode的节点引用,然后让ref所指向的节点销毁。这里我们先不考虑ref为什么叫指向节点MyNode。我们先从第二行看起,这里我们把它翻译过来就是“ref执行销毁动作”,那么ref就是主语,而“销毁”这个动作,也就是调用queue_free()这个函数就是谓语。这样一来,我们通过<对象.调用函数>这一套模板实现了“某对象执行某动作”这一语句。
我们将调用函数的对象称为执行主语(Subject),被调用的函数叫做执行谓语(也叫动作或行为,Action 或 Behavior)。
那么,如果我们所调用的函数需要一个参数传递进去呢?
# 假设ref是Node2D类型的对象
var target := get_node("Target") # 假设这个节点也是Node2D类型的对象
ref.look_at(target.global_position)这里我们调用了ref的look_at()函数,该函数需要传入一个Vector2类型的参数,用于确定ref需要看向的位置,这里我们用target变量存储对节点Target的引用,然后在look_at()当中,我们将target传入,显然target并非Vector2类型的参数,因此会报错,而由于我们需要的是一个位置,因此我们需要获取target的位置属性,这里look_at()要求传入的位置是一个全局位置,因此我们需要获取target的global_position属性。这段代码的第三行我们便可以翻译为“ref执行看向动作,看向target的全局坐标”,这里target的全局坐标,也就是target.global_position就是整个动作的对象,我们可姑且将函数参数称为执行宾语(Object)。需要注意的是,这里的宾语实际上是target的global_position这个属性,而非target本身,但target是global_position这个属性的拥有者,相当于对global_position起到了修饰限定的作用,因此我们将target称作执行宾语的修饰语(或执行宾语定语,Attribute)。
实际上,有些情况下,在执行主语后面加上"."之后,也会让该执行主语转变成定语,如下面这个例子:
get_parent().get_node("MyChild").queue_free()我们先可以只看前面这部分:
get_parent().get_node("MyChild")我们还可以再往前推一下:
get_parent()这里get_parent()的执行主语为self,这里的self默认情况下可以省略,因此这一小段翻译过来就是“self执行获取父节点动作”,get_parent()在这里就变成了执行谓语,但是get_parent()会返回一个节点,此时如果我们再在后面加上一个get_node(xxx),也就相当于让父节点执行“获取节点”的操作,此时调用get_parent()函数实际上是要的返回结果,而这个返回结果就变成了执行主语,get_node(xxx)则变成了执行主语get_parent()的动作。因此,获取xx节点的操作也就发生在get_parent()身上而非self身上,因为此时self此时发生了语义转化(Semantic Transformation),从执行主语变成了执行主语的定语。而实际上的执行主语则因为语义偏移(Semantic Shift)而转到了“.”后面的语句上。
我们再回头看一下queue_free()那段代码,想要确定执行主语到底是谁很简单:从头看到尾,如果调用的函数会返回一个对象,且调用函数后面还接有一个函数调用,那么就接着往后看,直到看到有不返回值的函数为止。一般情况下,该不返回值函数就是执行谓语,其前面最近的那个对象(不论是通过变量存储引用还是通过函数调用获取返回值)即为执行主语,执行主语前面的均为执行定语。顺着这个思路,我们从get_parent()开始看起,get_parent()会返回一个节点,我们看后面有没有会返回值的函数,get_node(xxx)会返回值,因此我们再往后看,此时我们发现queue_free()不会返回值,因此它就是执行谓语,其前面紧邻的get_node(xxx)对象就是执行主语,该动作会从该对象身上发出。在前面的所有成分均为其执行定语。
执行主语和执行宾语十分重要,试想一下:如果你选错了一个执行主语:
# 本来我是想让父节点的子节点(兄弟节点)来执行一些动作的
get_parent().get_node("SomeNode").do_action()
# 结果你写成了
get_node("SomeNode").do_action()前面提过,默认的执行主语是self,如果是第一条,那么真正的执行主语是父节点的SomeNode子节点,但是如果省略了get_parent(),那么真正执行主语反而变成了self下方的SomeNode节点,有些情况下,如果没有对应节点,调用get_node()会报错。因此,确定好执行主语非常重要。除此之外,执行宾语也很重要,假设上述do_action()方法需要传递一个float类型的参数,如果你的执行宾语,一个是A.position,是Vector2类型;一个是B.position,是float类型,如果此时你把B.position的定语改为了A.,那么就算属性名是一样的,但是由于定语发生了变化,导致实际获取的position并不是float类型,也就是执行宾语也发生了变化,那么就会触发报错。
那么,赋值语句改如何判断执行主语和执行动作呢?
var a = b实际上,赋值语句相当于调用对象的set()函数,因此可以转化为:
set("a", b)实际上就是执行主语还是self,但是执行宾语变成了两个:一个是叫a的属性(变量),一个是数值b。但与此同时,也发生了一种潜在的语义偏移,此时a就拥有了b的值,变成了可用执行主语(Usable Subject),此时,a是可以作为后文的执行主语使用的,这一点在a为非数值类型时尤为明显。
好了,那么本贴就暂时告一段落,关于本贴的目的,其实很简单:就是提醒大家,在编写代码的时候,大家务必要确定好自己代码的执行主语和执行宾语,这样才能确保代码如期运行。
页:
[1]