Pygame 官方文档 - Tutorials - 我如何移动一个图像(How Do I Move An Image?)

这篇教程介绍了如何使用Pygame让图像在屏幕上移动。关键在于理解不是移动像素,而是通过快速擦除和重新绘制图像来创建运动的幻觉。教程通过逐步讲解,包括创建背景、跟踪对象位置、使用单独的背景副本来擦除和重新绘制,以及实现平滑移动,帮助初学者掌握Pygame中的图像移动技巧。
摘要由CSDN通过智能技术生成
帮助!我如何移动一个图像?(Help! How Do I Move An Image?)

作者: Pete Shinners
联系方式: pete@shinners.org
        许多刚接触编程和图形的人很难弄清楚如何使图像在屏幕上移动。 如果不理解所有概念,它可能会非常混乱。 你不是第一个被困在这里的人,我会尽力一步一步地去教明白。我们甚至会尝试以保持动画效率的方法结束。
        请注意,在本文中我们不会教您使用python编程,只是向您介绍pygame的一些基础知识。

仅仅将像素出现在屏幕上(Just Pixels On The Screen)
        Pygame有一个用于显示Surface对象。这基本上是在屏幕上可见的图像,图像由像素组成。更改这些像素的主要方法是调用blit()函数。这会将像素从一个图像复制到另一个图像。
        这是首先要了解的事情。当您将图像blit到屏幕上时,您只需简单地更改屏幕上像素的颜色即可。不添加或移动像素,我们只需更改屏幕上已有像素的颜色。您在屏幕上blit的这些图像也是pygame中的Surface对象,但它们绝不会连接用于到显示Surface对象。当它们与屏幕相连时,它们会被复制到显示屏中,但您仍然拥有原始文件的唯一副本。
        有了这个简短的描述。也许你已经可以理解“移动”图像需要什么。我们实际上根本没有移动任何东西。我们只是将图像置于新位置。但在我们将图像绘制到新位置之前,我们需要“擦除”旧图像。否则,图像将在屏幕上的两个位置可见。通过快速擦除图像并在新的地方重新绘制图像,我们实现了运动的“幻觉”。
        通过本教程的其余部分,我们将把这个过程分解为更简单的步骤。甚至解释了让多个图像在屏幕上移动的最佳方法。你可能已经有问题了。比如,在将图像绘制到新位置之前,我们如何“擦除”图像?也许你还是完全迷失了?希望本教程的其余部分可以为您提供一些解决方案。

让我们退回一步吧(Let’s Go Back A Step)
        也许像素和图像的概念对你来说仍然有些陌生? 好消息,对于接下来的几节我们将使用能够完成我们想要的所有事情的代码,它只是不使用像素。 我们将创建一个包含6个数字的小python列表,并想象它代表了我们可以在屏幕上看到的一些奇妙的图形。 可能实际上令人惊讶的是,这恰恰代表了我们稍后将使用真实图形做什么。
所以让我们首先创建我们的screen(屏幕)列表,并用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进行一些图形编程时,可能已经达到了这个目标。 屏幕上有一些漂亮的东西,但它无法移动到任何地方。 也许现在我们的屏幕只是一个数字列表,更容易看到如何移动他?

让英雄移动(Making The Hero Move)
        在我们开始移动角色之前。 我们需要为他追踪某种位置。 在我们绘制他的最后一节中,我们选择了一个任意位置。 让我们这次正式做一点。

>>> 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]

        哎呦。 现在我们可以看到两个英雄。 一个处于旧位置,另一个位于新位置。 这正是我们在将他拉到新位置之前需要“抹掉”旧位置英雄的原因。 要删除他,我们需要将列表中的值更改回英雄在那之前的值。 这意味着我们需要在英雄替换它们之前跟踪屏幕上的值。 有几种方法可以做到这一点,但最简单的方法通常是保留屏幕背景的单独副本。 这意味着我们需要对我们的小游戏进行一些更改。

创建地图(Creating A Map)
        我们想要做的是创建一个单独的列表,我们将其称为背景。 我们将创建背景,使它看起来像我们的原始屏幕,用1和2填充。 然后我们将每个项从背景复制到屏幕。 之后我们终于可以将我们的英雄画回到屏幕上。

>>> 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]

        这似乎是一项额外的工作。 在我们最后一次尝试让他移动之前,我们离得不远了。 但是这次我们有了正确移动他所需的额外信息。

让英雄移动(再来一遍)(Making The Hero Move (Take 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”的定义(Definition: “blit”)
        在接下来的部分中,我们将把程序从使用列表转换为使用屏幕上的真实图形。 显示图形时,我们会经常使用术语blit。 如果您不熟悉图形工作,那么您可能不熟悉这个常用术语。
        BLIT:基本上,blit意味着将图形从一个图像复制到另一个图像。 更正式的定义是将数据数组复制到位映射数组(bitmapped array)的目的地。 您可以将blit视为“赋值(assigning)”像素。 就像在上面的screen列表中设置值一样,blitting会指定图像中像素的颜色。
        其他图形库将使用单词bitblt,或者只是blt,但它们正在谈论相同的事情。 它基本上是将内存从一个地方复制到另一个地方。 实际上,它比直接复制内存要先进一些,因为它需要处理像素格式化(pixel formats),剪切(clipping)和扫描线间距(scanline pitches)等事情。 高级的blitters也可以处理透明度和其他特殊效果。

从列表到屏幕(Going From The List To The Screen)
        将我们在上面看到的代码带到示例中并使它们与pygame一起使用非常简单。 我们假装我们已经加载了一些漂亮的图形,并将它们命名为“terrain1”,“terrain2”和“hero”。 在我们将数字赋值给列表之前,我们现在将图形blit到屏幕上。 另一个重大变化,我们现在需要一个二维坐标,而不是将位置用作单个索引(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))

        你得到了它。 通过这段代码,我们展示了如何在其上显示带有英雄形象的简单背景。 然后我们正确地将那个英雄向左移动了一个空格。 那么,我们现在该去哪呢? 好吧,一个代码仍然有点尴尬。 我们要做的第一件事是找到一种更清晰的方式来表示背景和玩家的位置。 那么也许是一些更流畅,真实的动画。

屏幕坐标(Screen Coordinates)
        要在屏幕上定位对象,我们需要告诉blit()函数放置图像的位置。在pygame中,我们总是将位置作为(X,Y)坐标传递。这表示向右的像素数,以及向下的像素数用来放置图像的。 Surface对象的左上角是坐标(0,0)。稍微向右移动(10,0),然后向下移动(10,10)。 当需要blitting时,position参数表示源的左上角应放在目标上的位置。
        Pygame为这些坐标提供了一个方便的容器,它是一个Rect。 Rect基本上代表这些坐标中的矩形区域。它有左上角以及大小。 Rect提供了许多方便的方法,可以帮助您移动和定位它们。在下面的例子中,我们将用Rect表示对象的位置。
        我们也知道pygame中的很多函数都需要Rect参数。所有这些函数也可以接受4个元素(左,上,宽,高)【(left, top, width, height)】的简单元组。您并不总是需要使用这些Rect对象,但您主要想要这样做。此外,blit()函数可以接受Rect作为它的位置参数,它只是使用Rect的左上角作为实际位置。

改变背景(Changing The Background)
        在前面的所有部分中,我们一直将背景存储为不同类型的地面列表。 这是创建基于图块的游戏的好方法,但我们想要平滑滚动。 为了使这更容易,我们将把背景更改为覆盖整个屏幕的单个图像。 这样,当我们想要“擦除”我们的对象时(在重绘它们之前),我们只需要将擦除背景的部分blit到屏幕上。
        通过将可选的第三个Rect参数传递给blit,我们告诉blit仅使用源图像的子部分。 您将看到在下面使用,当我们删除玩家的图像。
另请注意,现在当我们完成绘制到屏幕时,我们调用pygame.display.update(),它将显示我们在屏幕上绘制的所有内容。

平滑移动(Smooth Movement)
        为了使某些东西看起来平滑移动,我们只希望一次移动几个像素。 以下是使对象在屏幕上平滑移动的代码。 基于我们现在已经知道的,这看起来应该很简单。

>>> screen = create_screen()
>>> player = load_player_image()
>>> background = load_background_image()
>>> screen.blit(background, (0, 0))        #绘制背景
>>> position = player.get_rect()
>>> screen.blit(player, position)          #绘制玩家
>>> pygame.display.update()                #然后,把它们都展示出来
>>> for x in range(100):                   #保证100帧率
...     screen.blit(background, position, position) #擦除
...     position = position.move(2, 0)     #移动玩家
...     screen.blit(player, position)      #绘制新玩家
...     pygame.display.update()            #然后,把它们都展示出来
...     pygame.time.delay(100)             #暂停程序 1/10 秒

        那就是你想要的。 这是在屏幕上平滑地使对象动起来所需的所有代码。 我们甚至可以使用漂亮的背景角色。 以这种方式进行背景的另一个好处是,玩家的图像可以具有透明度或剪切部分,它仍然可以在背景上正确绘制(免费奖励)。
        我们还在上面的循环结束时调用了pygame.time.delay()。 这会使我们的程序变慢一点,否则它可能会运行得如此之快以至于你可能看不到它。

所以,下一步是什么?(So, What Next?)
        那么我们得到了它。 希望这篇文章已经完成了它承诺要做的一切。 但是,此时代码还没有为下一个畅销游戏做好准备。 我们如何轻松拥有多个移动物体? 那些究竟是什么神秘的函数,如load_player_image()? 我们还需要一种方法来获得简单的用户输入,并循环超过100帧。 我们将以此为例,将其转化为面向对象的创作,让妈妈感到自豪。

首先,神秘函数(First, The Mystery Functions)
        有关这些类型函数的完整信息可以在其他教程和参考中找到。 pygame.image模块有一个load()函数,可以实现我们想要的。 加载图像的行应该成为这样。

>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('liquid.bmp').convert()

        我们可以看到这很简单,加载函数只需要一个文件名并返回一个带有加载图像的新Surface。加载后,我们调用Surface的方法convert()。转换返回一个新的图像Surface,但现在转换为与我们的显示器相同的像素格式。由于图像在屏幕上的格式相同,因此它们会非常快速地进行blit。如果我们没有转换,blit()函数会变慢,因为它必须从一种像素转换为另一种像素。
        您可能还注意到load()和convert()都返回了新的Surface。这意味着我们真的在每行上创建了两个Surface。在其他编程语言中,这会导致内存泄漏(不是一件好事)。幸运的是,Python足够智能来处理这个问题,而pygame将正确地清理我们最后没有使用的Surface。
        我们在上面的例子中看到的另一个神秘功能是create_screen()。在pygame中,为图形创建一个新窗口很简单。创建640x480表面的代码如下。通过不传递任何其他参数,pygame将为我们选择最佳的颜色深度和像素格式。

>>> screen = pygame.display.set_mode((640, 480))

处理一些输入(Handling Some Input)
        我们迫切需要更改主循环以查找任何用户输入(例如,当用户关闭窗口时)。 我们需要在程序中添加“事件处理”。 所有图形程序都使用此基于事件的设计。 该程序从计算机获取“键盘按下”或“鼠标移动”等事件。 然后程序响应不同的事件。 这是代码应该有的样子。 我们将继续循环,直到用户要求我们停止,而不是循环100帧。

>>> while 1:
...     for event in pygame.event.get():
...         if event.type in (QUIT, KEYDOWN):
...             sys.exit()
...     move_and_draw_all_game_objects()

        这段代码的作用是,首先永远循环,然后检查用户是否有任何事件。 如果用户按下键盘或窗口上的关闭按钮,我们退出程序。 在我们检查了所有事件之后,我们移动并绘制了游戏对象。 (当然,我们也会在移动之前擦除它们)

移动多个图像(Moving Multiple Images)
        这是我们真正要改变一切的部分。 假设我们想在屏幕上移动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方法将对象移动一步。 如果它走得太远,它会将对象移回左侧。

把它们组合在一起(Putting It All Together)
        现在有了我们的新对象的类,我们可以把整个游戏放在一起。 以下是我们程序的主要功能。

>>> 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)

        那就是我们要的代码。 这是我们在屏幕上为10个对象设置动画所需的代码。 可能需要解释的唯一一点是我们用来清除所有对象并绘制所有对象的两个循环。 为了正确地做事,我们需要在绘制任何对象之前擦除它们。 在我们的示例中,这可能无关紧要,但是当对象重叠时,使用这样的两个循环变得很重要。

从这里开始你就只能靠自己了(You Are On Your Own From Here)
        那么你的学习之路接下来会是什么? 那么先用这个例子来玩吧。 pygame示例目录【pygame.examples】中提供了此示例的完整运行版本。 这是名为moveit.py的示例。 阅读代码并使用它,运行它,然后学习它。
        您可能想要处理的事情可能是拥有多种类型的对象。 当您不想再显示对象时,找到一种干净地“删除”对象的方法。 还要更新display.update()调用以传递屏幕上已更改的区域列表。
        pygame中还有其他教程和示例可以解决这些问题。 所以,当你准备继续学习时,继续阅读。?
        最后,您可以随时访问pygame邮件列表或聊天室,对这些内容有任何疑问。 总有人可以帮助您完成这类事情。
        最后,玩得开心,这就是游戏的用途!
 

以上内容,自己翻译,可能有误,可参考:Tutorials - 我如何移动一个图像(How Do I Move An Image?)

点我回顶部

 
 
 
 
 
 
 
Fin.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值