01 pygame游戏编程入门之一:如何移动图像?
pygame游戏编程入门之一:如何移动图像?
作者: Pete Shinners(pete@shinners.org)
翻译: 杨晓宏(561071777@qq.com)
许多编程新手或者对图形用户界面没有研究的人,很难弄清楚如何让图像在屏幕上移动。
如果不理解所有概念,就很难厘清其中的关键。
你不是第一个被困在这里的人,本篇文章就会尽力让你走出泥澡。
在文章的最后,甚至会用相当高效的方法向你展示动画的方法。
请注意,在本文中,我们不会教你python编程,只是会介绍pygame的一些基本知识。
屏幕上只是一些像素
Pygame有一个显示表面。这基本上是一个可以在屏幕上看到的图像,图像是由像素组成的。改变这些像素的主要方法是调用blit()函数。这将把像素从一个图像复制到另一个图像上。
这是我们首先要理解的。
当你在屏幕上显示一个图像时,你只是在改变屏幕上像素的颜色。像素没有被添加或移动,我们只是改变了屏幕上的像素的颜色。
你在屏幕上看到的这些图像也在pygame中,但它们与显示表面没有任何连接。当它们被显示在屏幕上时,它们会被复制到显示器中,但是你仍有一个独一无二的原始副本。
通过此简要描述,也许您已经理解了“移动”一个图像所需要的工作。
我们实际上什么都没动,只是把图像放在一个新位置上。
但在我们把图像画在新位置之前,我们需要“擦掉”旧的图像。否则,图像将在屏幕上的两个地方可见。
通过快速删除图像并将其重新绘制到一个新的地方,我们就达到了运动的“错觉”。
在本教程的其余部分,我们将把这个过程分解成更简单的步骤。甚至会解释让多个图像在屏幕上移动的最好方法。
你可能又有问题了。比如,我们如何在将图像绘制到一个新位置之前“擦除”图像?也许你还是完全迷路了?
希望本教程的后边部分能帮你解决这些问题。
让我们退后一步
也许像素和图像的概念对你来说还是有点陌生的?
好消息,在接下来的几节中,我们将使用代码来做我们想做的所有事情,它只是不使用像素和图像。
我们将创建一个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]
如果你用pygame做一些图形编程,可能你看到了你的英雄。
你在屏幕上有一些好看的东西,但是它不能移动到任何地方。
现在我们的屏幕只是一个数字列表,也许我们更容易移动他?
让英雄动起来
在我们开始移动角色之前。我们需要跟踪他的一些位置。
在最后一节,当在屏幕上画出他的时候,我们选了一个任意的位置。
这次我们再正式点。
>>> 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]
哎呦!
现在我们可以看到两个英雄。一个在旧的位置,一个在新位置。
这正是我们在把他画到新位置之前,需要“抹去”旧位置的原因。
为了消除他,我们需要把列表中的那个值改为英雄在那里之前的值。这意味着我们需要在英雄替换它们之前跟踪到屏幕上的值。
有几种方法可以做到这一点,但最简单的方法通常是保留一个单独的屏幕背景副本。这意味着我们需要对我们的小游戏做些设置。
创建一个地图
我们要做的是创建一个单独的列表,我们称之为背景。
我们将创建背景,让它看起来像我们原来的屏幕,完全是1和2构成。
我们需要将每一项从背景复制到屏幕。这样,我们终于可以把英雄画到正常屏幕上了。
>>> background = [1, 1, 2, 2, 2, 1]
>>> screen = [0]*6 #a new blank screen
>>> 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]
这似乎是一项额外工作。
这项工作,让我们又回到了英雄移动前的状态。
但这一次,我们有了额外信息,可以正确地移动他了。
让英雄移动 (步骤 2)
这一次,我们很容易将英雄转移到周围其它地方。
首先,我们要把这位英雄从他的原始位置上抹去。我们通过将原始值从背景复制到屏幕上来实现这一点。
然后我们在屏幕上画出他的新位置。
>>> 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:基本上,BLIT的意思是将图形从一个图像复制到另一个图像。
一个更正式的定义是将一组数据复制到位图数组目的地。你可以把blit看作是“分配”像素。就像在上面的列表中设置值一样,在我们的图像中,“blitting”赋予了像素颜色。
其他的图形库使用“bitblt”这个词,或者仅仅是blt,但是他们谈论的是同一件事。
它基本上是把内存从一个地方复制到另一个地方。实际上,它比直接复制内存要高级一些,因为它需要处理像素格式、剪裁和扫描线的内容。
高级blitters也可以处理诸如透明度和其他特殊效果。
从列表到真实的像素图形
把上面的代码引伸到下边的例子,让它们在pygame工作也是非常简单的。
我们假装加载了一些漂亮的图形,并将它们命名为“terrain1”、“terrain2”和“英雄”。
在将数字分配到一个列表之前,我们现在将图形画到屏幕上。
另一个大的变化是,现在需要一个二维坐标,而不是将位置作为单个索引(0到5)。
我们假装游戏中的每一个图形都是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))
嗯,这段代码看起来应该很熟悉,更希望上面的代码应该有一点意义。
在列表中设置简单值,显示了在屏幕上设置像素的相似性(使用blit)。
唯一真正的额外工作是把玩家的位置转换成屏幕上的坐标。现在我们只使用一个粗糙的(playerpos 10,0),当然也可以做得更好。
现在让我们把玩家的图像移到一个位置上。这段代码应该没有任何惊奇之处。
>>> screen.blit(background[playerpos], (playerpos*10, 0))
>>> playerpos = playerpos - 1
>>> screen.blit(playerimage, (playerpos*10, 0))
出来了!这段代码,我们展示了如何显示一个简单的背景和一个英雄的图像。
然后我们把英雄的一个格子移到左边。
那么从哪里开始呢?对一个人来说,代码还是有点力所难及。
我们首先要做的是找到一种更明晰的方式来表示背景和玩家的位置。
然后应该会获得更流畅,更真实的动画效果。
屏幕坐标系
要在屏幕上放置一个物体,我们需要告诉blit()函数在哪里放置图像。在pygame中,我们总是把位置作为(X,Y)坐标。
这表示基准位置右边的像素数量,以及基准位置下边的像素数量以放置图像。
一个表面的左上角是坐标(0,0),向右移动一点是(10,0),然后向下移动同样多为(10,10)。当blitting时,位置参数表示距离左上角的数值,然后放置物件在目标位置上。
Pygame有一个方便的容器来表示这些坐标,它是一个Rect,Rect在这些坐标中代表一个矩形区域。它有左上角位置和一个大小参数。这个矩形有很多便利的方法可以帮助你移动和定位它们。
在下一个例子中,我们将用Rect表示对象的位置。
要知道pygame中的许多函数都有矩形参数。所有这些函数都可以接受一个简单的4个元素元组(左、上、宽、高)。您并不总是需要使用这些Rect对象,但您主要内容就是使用它们。
而且,blit()函数可以接受Rect作为位置参数,它只是简单地使用矩形的左上角作为实际位置。
改变背景
在之前的章节中,我们一直将背景存储为不同类型的地面列表。这是创建一个基于tile-based的游戏的好方法。
但我们想要平滑滚动。为了让它更简单一些,我们将背景变成一个覆盖整个屏幕的单一图像。
这样,当我们想要“擦除”对象时(在重新绘制它们之前),我们只需要将擦除的背景部分放到屏幕上。
通过将一个可选的第三个矩形参数传递给blit,告诉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() 。
这减慢了的程序。否则它可能跑得太快,以至于你可能看不到它。
那么,下一步?
好了,我们找到了方法。
这篇文章完成了它所承诺的一切!
但到此为止,为下一个畅销游戏,这些代码还远远不够。
例如,我们如何简单地移动多个对象?
像loadplayerimage()这样的神秘函数到底是什么?
我们还需要一种方法来获得用户输入,并且循环超过100帧每秒。
我们将借用这个例子,把过程变成一个面向对象的编程过程,让妈妈为我们感到自豪。
首先,神秘的函数
关于这些函数和功能的完整信息,可以在其他教程和相关参考中找到。
pygame.image模块有一个load()函数,它可以做到希望的事情。
加载图像应该象下边这样。
>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('liquid.bmp').convert()
这也很简单,load函数只接受一个文件名,并返回一个加载有图像的新surface。
加载后,我们调用Surface的方法,convert()。
转换返回一个新的图像surface,转换成了我们的显示器相同的像素格式。
由于这些图像在屏幕上是相同格式,所以它们blit很快。
如果不进行转换,blit()函数就会变慢,因为它必须从一种类型的像素转换到另一种类型。
您可能还注意到load()和convert()都返回新的Surface。这意味着我们在每个流程都创建了两个曲面。
在其他编程语言中,这会导致内存泄漏(这不是一件好事)。幸运的是,Python足够智能来处理这个问题。pygame将清理没有使用的surface。
在上面的例子中看到的另一个神秘函数是createscreen()。
在pygame中,为图形创建一个新窗口是很简单的。下面是创建640x480surface的代码。因为没有传递其他参数,pygame将选择最好的颜色深度和像素格式。
>>> screen = pygame.display.set_mode((640, 480))
处理输入
我们迫切需要在主循环中捕捉任何用户输入(例如用户关闭窗口时)。
在程序中添加“事件处理”,所有图形程序都使用此基于事件的设计。
程序会得到“键盘按下”或“鼠标移动”等事件,然后程序对不同的事件作出反应。
代码是什么样的,请看下边。
代码一直循环,直到用户要求我们停止循环为止。
>>> while 1:
... for event in pygame.event.get():
... if event.type in (QUIT, KEYDOWN):
... sys.exit()
... move_and_draw_all_game_objects()
这段代码做了一段简单的事:无限循环,然后检查用户事件。
如果用户按下键盘或关闭按钮,就退出程序。
在检查了所有的事件之后,程序移动并绘制所有的游戏对象。
(当然也会在移动之前把它们擦掉)
移动多个图像
这儿是真正重要的部分。
假设我们想要10个不同图像在屏幕上移动。处理这个问题的好方法是使用python的类。
我们创建一个代表游戏对象的类。这个类的对象有一个移动自身的函数,然后我们可以创建尽可能多的拥有此函数的对象。
绘制和移动物体的功能需要以一种方式工作,即每次刷新移动一个帧。
下面是创建类的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</i>
... 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)
正是如此!
这是我们在屏幕上激活10个物体的代码。
唯一需要注意的是我们清除所有物体并绘制所有对象的两个循环。
为了正确地处理,我们需要在绘制任何对象之前先删除所有对象。
在这个示例中,可能并不重要,但是当对象重叠时,使用这两个循环就变得很重要了。
从这里开始你就靠自己了
那么,在你的学习道路上,接下来会发生什么呢?
首先,我们来看看这个例子。这个例子的完整运行版本在pygame示例目录中可以找到。它是一个名为moveit.py的文件。
多看看代码,运行,玩玩游戏,再学习它是怎么构成的。
你要做的游戏可能不止这一种类型的对象。
当你不想再显示它们的时候,要找到一种干净的“删除”对象的方法。还要传递已更改的屏幕区域列表,给display.update()方法调用。
在pygame中还有其他教程和示例,涵盖了这些问题。
所以当你准备好继续学习的时候,继续阅读。:-)
最后,你可以自由地使用pygame邮件列表或聊天室。如果有任何问题。总有人可以帮你解决这类问题。
最后,祝玩得开心,这就是游戏的目的!