06 pygame新手指南

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

    pygame新手指南

    我所走过的弯路,你不用再走,我是如何学会停止疑虑并爱上blit的。

    Pygame是由Pete Shinners编写的SDL的python包装器。这意味着,使用pygame,您可以在Python中编写游戏或其他多媒体应用程序,这些应用程序将在SDL的支持平台(Windows、Unix、Mac、BeOS和其他平台)上运行。
    Pygame可能很容易学,但是图形编程的世界对于新手来说可能会困惑。我写这篇文章是为了试图提炼我在过去一年里所获得的与pygame的实践知识,它的前身是PySDL。
    我试着根据重要性对这些建议进行排序,但是任何特定的提示都将取决于你自己的背景和项目的细节。

    在Python中舒适工作

    最重要的是使用python来自信。如果你不熟悉你正在使用的语言,那么学习一些可能复杂的图形编程将会是一件很麻烦的事情。
    在python中编写一些相当大的非图形程序——解析一些文本文件,编写一个猜谜游戏或一个日志条目程序之类的东西。熟悉字符串和列表操作——知道如何分割、切片和组合字符串和列表。了解导入是如何工作的——尝试编写一个跨多个源文件的程序。
    编写自己的函数,练习操作数字和字符;知道如何在两者之间进行转换。要达到使用列表和字典的语法学习——您不希望每次需要分割一个列表或对一组键进行排序时,都必须翻参考文档。
    抵制住到邮件列表的诱惑,comp.lan.python或IRC。相反,请打开编译器,并在几个小时内解决这个问题。打印出Python 2。0快速参考,并将其保存在你的电脑上。
    这听起来可能非常枯燥,但是你通过熟悉python获得的自信会在你写你的游戏的时候产生奇妙的效果。与您编写真正的代码时节省的时间相比,您花费在编写python代码的学习时间是微不足道的。

    认识真正需要pygame的哪些部分。

    查看pygame文档索引顶部的各种类可能会让人感到困惑。重要的是要认识到,只有一小部分函数可以做很多事情。许多您可能永远不会使用的类——在一年内,我还没有接触过Channel, Joystick, cursors, Userrect, surfarray or versionn函数。

    知道表面是什么。

    pygame最重要的部分是表面。只要把表面想象成一张白纸。你可以用一个表面做很多事情——你可以在上面画线,用颜色填充部分,复制图像,然后在上面设置或读取单个像素的颜色。一个表面可以是任何大小(在合理的范围内),你可以有任何内容(同样的,在合理的范围内)。
    一个表面是特殊的——用 pygame.display.set_mode()创建的。这个“显示表面”代表屏幕;无论你做什么,都会出现在用户的屏幕上。你只能有一个,这是SDL限制,而不是pygame one。
    那么如何创建表面呢?如上所述,您可以使用pygame.display.set_mode()创建特殊的“显示表面”。你可以通过使用image.load()来创建一个包含图像的表面,或者你可以用font.render()创建一个包含文本的表面。你甚至可以创建一个表面,Surface()完全不包含任何东西。

    Most of the surface functions are not critical. Just learn
    blit(), fill(), set_at() and get_at()

    and you'll be fine.

    Use surface.convert().

    当我第一次阅读surface.convert()的文档时,我并不认为这是我必须担心的事情。“我只使用PNGs,因此我所做的一切都将以相同的格式。”所以我不需要convert()';事实证明,我是非常、非常错误的。
    convert()的“格式”不是文件格式(即PNG、JPEG、GIF),而是所谓的“像素格式”。这指的是一个表面在一个特定的像素中记录单个颜色的特殊方式。如果表面格式与显示格式不一样,SDL将不得不在每一个blit的情况下对其进行实时转换——这是一个相当耗时的过程。不要太担心这个解释;请注意,如果您想要从您的blits中获得任何速度,convert()是必需的。
    如何使用convert?在用image.load()函数创建一个表面之后调用它。而不是仅仅做:

    surface = pygame.image.load('foo.png')
    Do:
    
    surface = pygame.image.load('foo.png').convert()
    

    很容易。你只需要在每个表面调用一次,当你从磁盘上加载图像时。你会对结果感到满意的;我看到通过调用convert()来增加约6x的速度。
    唯一不想使用convert()是当你真正需要绝对控制图像的内部格式——如果写一个图像转换程序,你需要确保输出文件有相同的像素格式与输入文件相同。如果你在写游戏,你需要速度。使用convert()。

    Dirty rect animation.

    由于对pygame.display.update()函数的误解,导致了pygame程序中帧速率不足的最常见原因。使用pygame,仅仅是将某个东西绘制到显示表面并不会导致它出现在屏幕上——您需要调用pygame.display.update()。调用这个函数有三种方法:

    pygame.display.update()
    

    ——这将更新整个窗口(或者全屏显示的整个屏幕)。
    pygame.display.flip()——这也做了同样的事情,如果你使用的是双缓冲硬件加速,也会做正确的事情,而你不是。

    pygame.display.update(a rectangle or some list of rectangles)
    

    ——这个更新只是你指定的屏幕的矩形区域。
    大多数新图形编程人员都使用第一个选项——他们在每个帧上更新整个屏幕。问题是,对于大多数人来说,这是一个不可接受的缓慢。调用update()在我的机器上花费35毫秒,这听起来并不多,直到您意识到千/35=28帧/秒的最大值。这是在没有游戏逻辑,没有blits,没有输入,没有人工智能,什么都没有的情况下。我只是坐在那里更新屏幕,28 fps是我最大的framerate啊。
    这个解决方案被称为“脏矩形动画”。不要在每个帧上更新整个屏幕,只更新自上一帧以来更改的部分。我这样做是通过跟踪列表中的这些矩形,然后在框架的末尾调用update(thedirty矩形)。对于一个移动的精灵,我:
    在精灵当前位置的背景下,将其擦除,并将其擦除。
    将精灵的当前位置矩形附加到一个名为dirtyrects的列表中。
    移动精灵。
    在它的新位置画精灵。
    将精灵的新位置附加到我的dirtyrects列表中。
    调用display.update(dirty_rects)
    速度上的差异是惊人的。考虑到SolarWolf有几十个不断移动的精灵,它的更新很流畅,而且还有足够的时间来显示背景中的视差星场,并更新它。
    有两种情况下,这种技术是行不通的。第一个是整个窗口或屏幕在每一个帧上都被更新的地方——想想一个平滑滚动的引擎,就像一个实时的实时策略游戏或者一个侧滚轴。那么在这种情况下你会怎么做呢?好吧,简短的回答是——不要在pygame中写这种游戏。长些的答案是每次滚动几个像素;不要试图让滚动变得非常平滑。你的玩家将会欣赏一款快速滚动的游戏,并且不会注意到背景跳动。
    最后的提示——不是每一款游戏都需要高帧率。一个战略游戏可以很容易地通过每秒几次更新——在这种情况下,不需要额外的复杂的dirty rect animation。

    六大原则

    硬件表面听起来很美好。

    如果您一直在查看可以使用pygame.display.set_mode()的各种标记,您可能会这样想:嘿,HWSURFACE!好吧,我想要那个——谁不喜欢硬件加速。噢噢噢…DOUBLEBUF;好吧,听起来很快,我想我也想要那样!这不是你的错;我们已经接受了多年的3-d游戏的训练,相信硬件加速是好的,软件渲染是缓慢的。
    不幸的是,硬件渲染带来了一长串的缺点:
    它只适用于某些平台。如果你要的话,Windows机器通常可以得到硬件表面。大多数其他平台都不能。例如,如果安装了X4,如果DGA2正常工作,并且如果moon正确地对齐,那么Linux就可以提供一个硬件表面。如果硬件表面不可用,SDL将无声地给您一个软件表面。
    它只适用全屏。
    它使每个像素的访问变得复杂。如果你有一个硬件表面,你需要在书写或读取单个像素值之前锁定它。如果你不这样做,坏事就会发生。然后,你需要在操作系统变得混乱并开始恐慌之前,再次快速解锁surface。在pygame中,大部分的过程都是自动化的,但这是需要考虑的因素。
    你失去了鼠标指针。如果您指定HWSURFACE(并实际得到它),您的指针通常会消失(或者更糟,挂在半边,半不闪烁状态)。你需要创建一个精灵来充当一个手动鼠标指针,你需要担心指针的加速和灵敏度。什么是痛苦。
    不管怎样,它可能会变慢。许多驱动程序并没有加速我们所做的类型的绘图,而且由于所有的东西都必须在视频总线上运行(除非你也可以把你的源表面塞进视频内存中),否则它最终可能会比软件访问慢。
    硬件渲染有它的位置。它在Windows下非常可靠,所以如果你对跨平台性能不感兴趣,它可能会给你带来显著的速度提升。然而,它是有代价的——增加的痛苦和复杂性。最好的方法是坚持使用好的旧的可靠的SWSURFACE,直到你确定你知道你在做什么。

    不要因为次要问题而分心。

    有时候,新的游戏程序员花了太多时间去担心那些对他们的游戏成功并不重要的问题。想要获得次要问题的愿望是可以理解的,但在创建游戏的过程中,你甚至不知道什么是重要的问题,更不用说你应该选择什么答案了。其结果可能是许多不必要的。
    例如,考虑如何组织图形文件的问题。每个框架应该有自己的图形文件,还是每个精灵?也许所有的图形都应该压缩到一个归档文件中?大量的时间被浪费在这样的项目上,在邮件列表上问这些问题,讨论答案,分析等等,等等,这是一个次要的问题;任何讨论它的时间都应该花在编码实际的游戏上。
    这里的观点是,拥有一个真正实现的“相当不错”的解决方案要好得多,而不是一个完美的解决方案,而这个完美方案是你从来没有写过的。

    Rects are your friends.

    Pete Shinners的包装可能有很酷的alpha效果和快速的速度,但是我不得不承认我最喜欢的pygame是低端的矩形类。矩形是一个简单的矩形——仅由左上角的位置、宽度和高度来定义。很多pygame函数都把rects作为参数,它们也取“rectstyle”,这个序列的值与矩形的值相同,所以如果我需要一个矩形来定义10 20 40 50的面积,我可以做以下任何一项:

    rect = pygame.Rect(10, 20, 30, 30)
    rect = pygame.Rect((10, 20, 30, 30))
    rect = pygame.Rect((10, 20), (30, 30))
    rect = (10, 20, 30, 30)
    rect = ((10, 20, 30, 30))
    

    但是,如果您使用前三个版本中的任何一个,您就可以访问Rect的实用函数。这些功能包括移动、收缩和膨胀的功能,找到两个rects的结合,以及各种碰撞检测功能。
    例如,假设我想要一个包含一个点(x,y)的所有精灵的列表——也许玩家点击了这里,或者这是子弹的当前位置。很简单,如果每个精灵都有一个。矩形 .rect 成员,我就这么做:

    sprites_clicked = [sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)]
    

    Rects与表面或图形函数没有其他关系,除了可以将它们用作参数的事实之外。您还可以在与图形无关的地方使用它们,但仍然需要被定义为矩形。每一个项目,我都会发现一些新的地方可以使用。

    不要使用像素-完美碰撞检测。

    你的精灵在四处移动,你需要知道它们是否相互碰撞。写这样的东西很有诱惑力:
    检查一下是否有碰撞。如果他们不是,就忽略他们。
    对于重叠区域中的每个像素,请查看两个精灵的对应像素是否不透明。如果是这样,就会发生碰撞。
    还有其他的方法来做这个,有一个 sprite masks等等,但是不管你怎么做,它可能会太慢。对于大多数游戏来说,最好是做“sub-rect collision”——为每一个比实际图像小一点的精灵创建一个矩形,并将其用于碰撞。它会快得多,而且在大多数情况下,玩家不会注意到不精确。

    管理事件子系统。

    Pygame的事件系统有点棘手。实际上有两种不同的方法来找出输入设备(键盘、鼠标或操纵杆)在做什么。
    第一个是直接检查设备的状态。你这样做是通过打电话,比如说,

    pygame.mouse.get_pos() or pygame.key.get_pressed().
    

    这将告诉你那个设备在你调用这个函数时的状态。
    第二种方法使用SDL事件队列。这个队列是一系列事件的列表——事件被添加到列表中,当它们被检测到的时候,它们会被从队列中删除。
    每个系统都有优点和缺点。状态检查(系统1)给了您精确——您确切地知道给定输入是何时发出的——如果鼠标被按下(0)是1,那就意味着鼠标左键在此时此刻。事件队列仅仅报告说,鼠标在过去的某个时候宕机了;如果您经常检查队列,这是可以的,但是如果您延迟了通过其他代码检查它,输入延迟就会增加。状态检查系统的另一个优点是它可以很容易地检测到“合唱”;也就是说,在同一时间,几个州。如果你想知道t和f键是否同时下降,请检查:

    if (key.get_pressed[K_t] and key.get_pressed[K_f]):
        print "Yup!"
    

    然而,在队列系统中,每个按键作为一个完全独立的事件到达队列,因此您需要记住,在检查f键时,t键已经关闭,并且还没有出现。稍微复杂一点。
    然而,国家体系有一个巨大的弱点。它只报告设备的状态在它被调用的时候;如果用户点击鼠标按钮,就会在对鼠标的调用之前释放它。按下(),鼠标按钮将返回0——getpressed()完全错过了鼠标按钮。这两个事件,MOUSEBUTTONDOWN和MOUSEBUTTONUP,仍然会处于事件队列中,等待被检索和处理。
    教训是:选择符合你要求的系统。如果您在循环中没有太多的事情——假设您只是处于一个while循环中,等待输入,使用getpressed()或另一个状态函数;延迟会更低。另一方面,如果每个按键都是至关重要的,但是延迟并不那么重要——比如你的用户在一个编辑框中输入一些东西,使用事件队列。一些按键可能会稍晚一些,但至少你会得到它们。
    关于event.poll()和wait()——poll()的注释可能看起来更好,因为它不会阻止程序在等待输入时执行任何操作——wait()暂停程序,直到收到事件为止。然而,poll()在运行时将消耗百分之百可用的CPU时间,并且它将用noevent填充事件队列。使用set_blocked()来选择您感兴趣的事件类型——您的队列将更加易于管理。

    Colorkey与alpha blitting。

    这两种技术有很多混淆,其中很大一部分来自于使用的术语。
    “Colorkey alpha blitting”指的是告诉pygame,某一特定图像中某一特定颜色的所有像素都是透明的,而不是它们恰好是什么颜色。当图像的其余部分被擦出时,这些透明的像素不会被擦干,所以不要模糊背景。这就是我们如何制作出形状不像矩形的精灵。简单地调用surface.set colorkey(color),其中颜色是RGB元组——比如(0,0,0)。这将使源图像中的每个像素都透明而不是黑色。
    “阿尔法”是不同的,它有两种情况。“图像alpha”适用于整个图像,可能是你想要的。alpha被称为“半透明”,它使源图像中的每个像素只部分不透明。例如,如果你将一个表面的alpha值设置为192,然后将它放到一个背景上,那么每个像素的颜色的3/4就会来自源图像,而在背景则是1/4。Alpha值从255到0,其中0是完全透明的,255是完全不透明的。注意,colorkey和alpha blitting可以组合在一起——这会产生一个在某些地方完全透明的图像,而在另一些地方则是半透明的。
    “每像素alpha”是alpha的另一种情况,它更复杂。基本上,源图像中的每个像素都有自己的alpha值,从0到255。因此,当在一个背景上出现时,每个像素都可以有不同的不透明度。这种类型的alpha不能与 colorkey blitting混合,它覆盖了每个图像的alpha值。每个像素的alpha很少在游戏中使用,并且要使用它,你必须在一个带有特殊alpha通道的图形编辑器中保存你的源图像。这很复杂——不要用它。

    像python那样做事情。

    最后一点(这不是最不重要的;它只会在最后出现)。Pygame是一种非常轻量级的SDL包装器,它是围绕本机OS图形调用的轻量级包装器。如果您的代码仍然很慢,并且您已经完成了上面提到的事情,那么问题就在于您在python中处理数据的方式。不管你做什么,某些习语在python里都是很慢的。幸运的是,python是一种非常清晰的语言——如果一段代码看起来笨拙或奇怪,那么它的速度也有可能得到改进。阅读Python的性能提示,了解如何提高代码的速度。也就是说,过早的优化是万恶之源;如果它的速度不够快,不要对代码进行折腾,试图让它更快。有些事情是不应该的:)
    那就这样吧。现在你知道了我所知道的关于使用pygame的所有知识。现在,去写那个游戏吧!

    相关文章

      网友评论

        本文标题:06 pygame新手指南

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