Pygame学习笔记(三):如何移动图像?
- Python
- 2015-12-24
- 365热度
- 0评论
导航
学习资料:《How Do I Move An Image?》
很多刚开始学习图形化编程的人,都很难弄明白怎样才能让一个图像在屏幕上移动。如果不理解所有相关的概念,它会很令人费解。
屏幕上的像素点
Pygame有一个display Surface,它是一个基本的在屏幕上可见的图像,这个图像是由像素构成。改变这些像素点的主要方式,是调用blit()函数,它将像素点从一个图像拷贝到另一个图像上。
这是首先要理解的。当你把一个图像blit到屏幕上时,你只是简单地改变了屏幕上某些像素点的颜色。像素点不会被增加或移动,我们只是改变屏幕上已经存在的像素点的颜色。这些你blit到屏幕上的图像,也都是Pygame中的Surface对象,但它们与display Surface没有联系。当图像被blit到屏幕上时,它们被复制到了display中,但此时你仍然保有一份它们原来的拷贝。
通过简短的描述,也许你已经知道了要“移动”一个图像需要些什么。实际上,我们根本没有移动任何东西,仅仅是将图像blit到了一个新的位置。但是,在新的位置画出图像之前,我们必须“擦除”旧图像。否则,图像将会出现在屏幕上的两个位置。通过快速擦除图像,并在新的位置重新绘制它,我们得到了移动的“错觉”。
接下来,我们将整个过程分解为几个简单的步骤。还会提到使多个图像在屏幕上移动的最佳实践。
退一步来看
让我们用实际的代码进一步说明上面的概念。先创建一个包含6个数字的Python列表,并把它想象成一个屏幕。你会发现,这与稍后基于图形的例子惊人地相似。
下面,我们开始创建一个“屏幕”列表,并填满“地形”1和2。
>>> screen = [1, 1, 2, 2, 2, 1] >>> print screen [1, 1, 2, 2, 2, 1]
现在背景已经创建完毕,我们来创建英雄(用数字8表示)。
>>> screen[3] = 8 >>> print screen [1, 1, 2, 8, 2, 1]
现在,整个屏幕上的元素还是静态的。
让英雄移动
在移动角色之前,我们需要记录它的位置。
>>> playerpos = 3 >>> screen[playerpos] = 8 >>> print screen [1, 1, 2, 8, 2, 1]
现在可以很容易地移动他了。让我们试试直接改变playerpos的值,然后把英雄重新绘制到屏幕上。
>>> playerpos = playerpos - 1 >>> screen[playerpos] = 8 >>> print screen [1, 1, 8, 8, 2, 1]
额,现在我们看到了两个英雄,一个在旧位置,一个在新位置。这就是为什么我们在重绘英雄到新位置之前,需要先“擦除”旧英雄的原因。要实现“擦除”效果,我们最好保存一份背景的拷贝。让我们对这个小游戏做一点修改。
创建一个地图
我们要创建一个单独的列表,作为我们的背景。然后将背景的值拷贝到屏幕列表中,最后在屏幕上画出英雄。
>>> background = [1, 1, 2, 2, 2, 1] >>> screen = [0] * 6 >>> for i in range(6): ... screen[i] = background[i] >>> print screen [1, 1, 2, 2, 2, 1] >>> playerpos = 3 >>> screen[playerpos] = 8 >>> print screen [1, 1, 2, 8, 2, 1]
现在我们已经做好了移动英雄的准备。
让英雄移动(v2)
在这个版本中,我们先把英雄从旧位置擦除,然后再把他绘制到新的位置。
>>> print screen [1, 1, 2, 8, 2, 1] >>> screen[playerpos] = background[playerpos] >>> playerpos = playerpos - 1 >>> screen[playerpos] = 8 >>> print screen [1, 1, 8, 2, 2, 1]
可以了,现在英雄向左侧移动了一个位置。我们可以用同样的方法,让英雄再向左移动一个位置。
>>> screen[playerpos] = background[playerpos] >>> playerpos = playerpos - 1 >>> screen[playerpos] = 8 >>> print screen [1, 8, 2, 2, 2, 1]
很棒吧!这并不是你期待看到的动画,但是,只要做一些细微的改动,我们就可以在屏幕上用真正的图形把它绘制出来。
定义:“blit”
接下来,我们会把之前的程序改为图形版本。显示图形时,我们会频繁使用blit这个术语。可以认为它是给像素点赋值。
BLIT: Basically, blit means to copy graphics from one image to another. A more formal definition is to copy an array of data to a bitmapped array destination. You can think of blit as just "assigning" pixels. Much like setting values in our screen-list above, blitting assigns the color of pixels in our image.
有的图形库使用bitblt或blt这样的词汇,意思和这里是一样的。
从列表到屏幕
把上面的例子在Pygame中实现是很简单的。假设我们已经加载了一些图形,命名为“terrain1”、“terrain2”和“hero”。之前我们是把数字赋值到列表中,现在我们把图形blit到屏幕上。还有一个很大的不同点,不再使用单个索引来表示位置,而是使用一个二维坐标系。假定游戏中每一个图形宽度为10个像素点。
>>> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1] >>> screen = create_graphics_screen() >>> for i in range(6): ... screen.blit(background[i], (i*10, 0)) >>> playerpos = 3 >>> screen.blit(playerimage, (playerpos*10, 0))
这段代码看上去是不是很熟悉?让我们将玩家移动一个位置。
>>> screen.blit(background[playerpos], (playerpos*10, 0)) >>> playerpos = playerpos - 1 >>> screen.blit(playerimage, (playerpos*10, 0))
这段代码中,我们在屏幕上显示出了一个背景,画出了角色,并且对角色进行移动。在此基础上,还能做些什么吗?其实现在的实现看上去有些奇怪。首先,我们要找一个更加干净的方式来表示背景和玩家的位置;其次,我们要让移动变得平滑一些,就像真正的动画一样。
屏幕坐标系
向屏幕上放置一个物体时,我们必须告诉blit()函数把图像放到哪里。在Pygame中,我们总是通过(X, Y)坐标来传递位置信息,分别表示放置图像时向右、向下移动的像素点个数。Surface左上角的坐标是(0, 0)。当blit的时候,位置参数代表源图像左上角在目标图像上的位置。
Pygame带有一个很方便的坐标容器,Rect。Rect表示坐标系中的一块矩形区域,它包含左上角和尺寸信息。Rect提供了一些方法,可以方便地进行移动。接下来的例子中,我们使用Rect来表示物体的位置。
Pygame中的很多函数都接受Rect参数,所有这些元素也同样接受包含4个元素的元组(left, top, width, height)。blit函数也接受Rect类型作为位置参数,这种情况下,仅仅使用了Rect的左上角作为真正的位置参数。
改变背景
在本文之前的部分,我们使用包含不同地形的列表来表示背景。这种方式比较适合战棋类游戏(tile-base game),但并不能实现我们想要的平滑效果。我们将采用一个更简单的方法,即改为用单独一张图片覆盖整个屏幕来创建背景。这样,当我们想要“擦除”物体时,只需要把背景中的相关区域blit到屏幕上即可。
通过向blit传递一个可选的第三个Rect类型参数,我们可以让blit只使用源图像的子区域。
还要注意,当我们完成了向屏幕的绘制操作后,要调用pygame.display.update()来显示我们在屏幕上绘制的所有东西。
平滑移动
要让某个东西看起来平滑移动,我们只要每次把它移动几个像素点。下面是让物体在屏幕上平滑移动的代码。
>>> screen = create_screen() >>> player = load_player_image() >>> background = load_background_image() >>> screen.blit(background, (0, 0)) #draw the background >>> position = player.get_rect() >>> screen.blit(player, position) #draw the player >>> pygame.display.update() #and show it all >>> for x in range(100): #animate 100 frames ... screen.blit(background, position, position) #erase ... position = position.move(2, 0) #move player ... screen.blit(player, position) #draw new player ... pygame.display.update() #and show it all ... pygame.time.delay(100) #stop the program for 1/10 second
在循环的最后,我们调用了pygame.time.delay(),这让程序慢了下来。如果不这样做,程序可能运行得很快,以至于你根本看不到它。
接下来干嘛?
到目前为止,我们已经达成了本文一开始的目标。但是,现在的代码离一个真正的游戏还是有相当的差距。我们如何轻易创建多个移动中的物体?像load_player_image()这样神秘的函数里面到底是什么?我们也需要一个方式来获取简单的用户输入,并且循环很多次。我们还要把现在这个例子变成面向对象风格。
首先,看看这些神秘的函数
关于这类函数的详细信息可以在其他tutor或参考文档中找到。pygame.image模块有一个load()函数来实现我们的想要的功能,如下所示:
>>> player = pygame.image.load('player.bmp').convert() >>> background = pygame.image.load('liquid.bmp').convert()
load函数接受一个文件名,返回一个加载了图像的Surface对象。加载完成之后,我们调用了Surface类的convert方法。convert函数也返回一个该图像的新Surface对象,但图像被转换为与我们的显示(display)相同的像素点格式。由于图像与屏幕有着同样的格式,blit时会非常快。如果我们没有进行转换,blit函数会更慢,因为它运行时必须把一种格式的像素点转换为另一种格式。
你可能注意到了,load()和convert()函数都返回了一个新的Surface。这意味着在上面的每一行代码中,我们实际上都创建了两个Surface对象。在其他的编程语言中,这会导致内存泄漏。幸运的是,Python足够聪明,Pygame会恰当地清理掉我们不再使用的Surface对象。
之前看到的另一个神秘的函数是create_screen()。在Pygame中,创建一个新的图形窗口是很简单的,下面的代码可以创建一个640*480的Surface对象。如果不传入其他参数,Pygame会替我们选择最佳的颜色深度和像素格式。
>>> screen = pygame.display.set_mode((640, 480))
处理输入
我们需要向程序中加入“事件处理”。
>>> while 1: ... for event in pygame.event.get(): ... if event.type == QUIT: ... sys.exit() ... move_and_draw_all_game_objects()
如果用户点击窗口的关闭按钮,就退出程序。
移动多个图像
假设我们想让10个不同的图像在屏幕上移动。使用Python中的类是一个很好的方法。我们将创建一个代表游戏对象的类。这个对象有一个函数来移动自己,然后我们就可以创建任意多个对象了。
>>> class GameObject: ... def __init__(self, image, height, speed): ... self.speed = speed ... self.image = image ... self.pos = image.get_rect().move(0, height) ... def move(self): ... self.pos = self.pos.move(0, self.speed) ... if self.pos.right > 600: ... self.pos.left = 0
在我们的类中有两个函数,init函数创建了对象,它放置了对象并设定了速度。move方法将对象移动一步,如果移动得太远,则让对象回到最左边。
组合在一起
把刚才讲到的东西组合在一起,就得到了一个完整的程序:
>>> screen = pygame.display.set_mode((640, 480)) >>> player = pygame.image.load('player.bmp').convert() >>> background = pygame.image.load('background.bmp').convert() >>> screen.blit(background, (0, 0)) >>> objects = [] >>> for x in range(10): #create 10 objects ... o = GameObject(player, x*40, x) ... objects.append(o) >>> while 1: ... for event in pygame.event.get(): ... if event.type in (QUIT, KEYDOWN): ... sys.exit() ... for o in objects: ... screen.blit(background, o.pos, o.pos) ... for o in objects: ... o.move() ... screen.blit(o.image, o.pos) ... pygame.display.update() ... pygame.time.delay(100)
这个例子的最终可运行版本,可以在Pygame的例程中找到,名字叫做“moveit.py”。找来玩一玩。
这里有Pygame的论坛和邮件列表的信息。
最后
Lastly, have fun, that's what games are for!