美文网首页python
01 pygame游戏编程入门之一:如何移动图像?

01 pygame游戏编程入门之一:如何移动图像?

作者: 易景漫游杨晓宏 | 来源:发表于2019-07-16 14:54 被阅读0次

    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邮件列表或聊天室。如果有任何问题。总有人可以帮你解决这类问题。
    最后,祝玩得开心,这就是游戏的目的!

    相关文章

      网友评论

        本文标题:01 pygame游戏编程入门之一:如何移动图像?

        本文链接:https://www.haomeiwen.com/subject/mqctlctx.html