看得出来,你对新定义的blit_new函数非常不理解。我把你的代码下载下来仔细分析了一遍,现在给你解答吧。
你的第二个问题(2、为什么screen.blit(background, (0, 0)),而blit_new(screen, turtle, position, 200)开头不需要screen.?)放到最后回答,这个与blit_new的逻辑无关,是另一个方面的问题。
尽管用注释在后面说明了每个参数是什么意思,但blit_new这个函数的签名(def blit_new(t, s, l, o))太过简陋了,不如一开始就用有意义的参数名这样也有助于理解代码逻辑。
于是乎我把代码的参数名按照注释修改了过来并且增加了类型注释,逻辑完全没有修改:
def blit_new(
target: pygame.Surface,
source: pygame.Surface,
location: tuple[int, int],
opacity: int
) -> None:
x, y = location[0], location[1]
temp = pygame.Surface((source.get_width(), source.get_height())).convert()
temp.blit(target, (-x, -y))
temp.blit(source, (0, 0))
temp.set_alpha(opacity)
target.blit(temp, location)
参数名后面的冒号以及pygame.Surface这部分叫做类型注释,用于说明参数的类型,不添加也没有影响;-> None表明这个函数的返回值是NoneType类型。
关于函数签名的修改介绍到这里接下来结合调用这个函数的时机来分析函数运行的逻辑。
在while循环中,函数是这样被调用的:blit_new(screen, turtle, position, 200)。
所以target参数对应的是screen;
source参数对应的是turtle;
location参数对应的是(turtle)的location;
opacity参数对应的是200。
进入函数内部再来分析它的运行过程:
x, y = location[0], location[1]
这行代码是获取location的x、y坐标。
location是之前通过turtle.get_rect()获得的,也就是说它其实是一个Rect对象,
记录了turtle的位置和尺寸;
temp = pygame.Surface((source.get_width(), source.get_height())).convert()
由于source是一个Surface对象,所以调用的两个函数分别获取了Surface对象的宽度和高度,然后组成了一个元组,利用这个元组再创建一个Surface对象。
经过之前的参数分析我们知道source就是turtle,所以这行其实就是创建了一个和turtle一样大的Surface对象,最后的.convert()是为Surface对象增加了透明图层。
也就是说
temp这个变量代表的是一个
大小与turtle相同且
带有透明通道的Surface对象。
temp.blit(target, (-x, -y))
这行代码的意思是把targe绘制到temp的(-x, -y)位置。
我们知道此处的target是screen,temp是一个新建的大小与turtle相同的Surface对象;x、y分别表示turtle在screen上的左上角坐标。
如果不理解可以参考下面这张图片:
(-x, -y)表示把screen向左向上分别偏移x、y个单位,如此一来尽管screen完全盖住了temp,但是二者中心点重合。
理解这行代码之后下一行就相对简单一些了。
temp.blit(source, (0, 0))
把source绘制到temp的(0, 0)位置。因为source代表turtle,而且temp与turtle大小相同,所以就是让turtle覆盖到temp上。
此时我们来分析temp的构成,temp由三层Surface叠加而成:最下层是一个黑色的矩形框,大小与turtle相同;中间层是screen;最上层是turtle。三者合而为一。
这行代码是设置temp的透明度,取值范围是0-255,数字越大越透明。调用函数时透明度传入的是200,所以此时的opacity为200。
target.blit(temp, location)
最后这一行就是把temp绘制到target的location位置。bit这个函数的第二个参数需要一个坐标,而location这个Rect对象本身就存储了有关坐标的信息所以可以直接把它当坐标传给bit函数。
target是screen,temp是叠加而成的一个新的Surface对象,具有200的透明度。
location是turtle的位置信息,而且location的中心点坐标与screen的中心点坐标一样(前面有设置location.center = width//2, height//2),所以这行代码就是把temp绘制到screen的正中心。
由于temp具有透明度所以背景会透出来,turtle也会看起来有些透明效果。
以上就是blit_new函数的运行逻辑。还是建议以后写函数不要用过于简单的参数名,否则自己都看不懂参数表示什么含义,分析逻辑也就更难了。
代码是写给人看的,机器只看二进制码。
#################分割线###################
最后再来回答你的第二个问题,为什么有的函数开头有前缀screen. temp.而blit_new这个函数却没有?
这里涉及到了两个知识点:类与对象,命名空间。
类与对象不做解释,因为内容过多,你学完了这部分自然知道为啥要这么些了。
接下来我尝试用简单的语言介绍命名空间。
想象这样一种情景,你们班上有一个同学小王,隔壁班也有一个同学小王。所有人一起在操场上的时候如果老师要点名你们班的小王怎么办?
肯定不能直接喊:“小王同学出列”,更好的做法是“三班的小王同学出列”。老师给你们班所在的位置分配了一个名字,叫做三班。然后在这个区域内寻找小王同学,这样就避免了找到其他班级同名同学的问题。
更进一步我们可以把“三班的小王”这样写
三班.小王,这就表示小王属于三班这个空间。类似地,Python也会帮我们把内存进行划分并命名。
turtle = pygame.image.load("turtle.png")这样一行代码的意思就是在内存中加载turtle.png这张图片并创建一个Surface对象,然后把Surface对象所在的这块内存空间命名为turtle。
这块空间还有其他一些东西,想要使用这块空间里面的东西时我们就可以采用这样的格式:turtle.xxx。
例如,调用get_rect方法获取大小位置信息可以这样写turtle.get_rect(),这表明get_rect这个方法只属于turtle这块内存。
类似地pygame.display.flip()也是类似的意思,pygame这块巨大的内存中划分出一块名为display的内存,在里面有个叫做flip的函数。
综上,我们可以把命名空间这个概念这样理解:把一块内存空间进行命名,然后通过形如aaa.bbb.ccc这样的形式使用内存中的函数、变量等等,这样的形式也可以表明它们之间的层级关系或者说所属关系。
以aaa.bbb.ccc为例,bbb直接属于aaa,ccc直接属于bbb,间接属于aaa。
到此为止,我们了解了为什么有些函数前面具有前缀。再来说说为什么有些函数前面没有前缀,在一个Python文件内,当变量、函数等没有任何缩进时,我们说它们在级别上属于最顶级的,这种顶级的名称是不需要加前缀的。blit_new函数定义时是顶格的,所以
以上就是针对你的所有问题的回答,前前后后编写超过一个小时,如果觉得有用请给个最佳答案,谢谢~