用Python实现经典游戏《小蜜蜂》

估计很多老玩家在小时候都玩过Galaxian(小蜜蜂)吧。这款射击游戏的鼻祖叫《太空侵略者》,上手简单,但可玩性很强。

高手用C语言精准复现的1978年《太空侵略者》版本

《太空侵略者》大火之后,新推出的Galaxian(小蜜蜂)于1979年成为其最大竞争对手。由Namco发行的Galaxian为外星敌人提供了新的色彩和不可预测的动作,后者不但会发射炮弹,还会自杀式俯冲攻击。

《Galaxian》在街机游戏中大受欢迎,以至于Namco在两年后又发布了续作《Galaga》——这款游戏使攻击模式更加复杂。

很难说《Galaxian》究竟有多少移植和克隆的版本,因为几乎每个家用游戏机上都有类似版本。

小霸王平台的《Galaxian》

玩家在《Galaxian》中的角色与《太空侵略者》类似,驾驶一艘飞船与一支外星舰队战斗。

与《太空侵略者》不同的是,在《Galaxian》中,外星人总会打破队形向玩家的飞船发起俯冲轰炸。

玩家需要摧毁所有敌人,然后进入下一关。随着玩家的推进,一波又一波的敌人将让过关变得更加困难。

我们这里将着眼于外星人的俯冲机制,用Pygame Zero开发《Galaxian》游戏的核心功能。

用Pygame Zero开发的版本

首先,《Galaxian》拥有一个纵向显示画面,所以我们将游戏区域的宽度和高度分别设置为600和800。

接下来,我们可以用位图创建一个滚动的星空背景。​将位图逐渐往屏幕下方移动,用第二颗恒星来填充第一颗恒星向下滚动时留下的空间,我们还可以在后面添加另一个静态背景图像,这将提供一些视野深度。

然后,我们将玩家的飞船设置为Actor。并在update()函数中捕获左右箭头键,以便在屏幕上左右移动飞船。我们也可以用空格键发射子弹,子弹会沿屏幕向上移动,直到击中外星人或离开屏幕顶部。

和原版《Galaxian》一样,你一次只能发射一颗炮弹,所以我们只需要一个Actor。

外星人排成一行,一起在屏幕上左右移动。在这个例子中,我们只画一种类型的外星人,共画两行。你可以添加额外的类型和任意多行。当我们创建alien Actors时,我们还可以添加一个状态标志,我们需要确定当它们打破队形时,它们在行的哪一边,两边朝相反的方向飞行。在这种情况下,每行左边有4个外星人,右边有4个。

一旦它们在列表中建立起来,我们就可以在每次更新时遍历列表,并向前或向后移动它们。

当我们在移动外星人时,我们也可以查看它们是否与炮弹或玩家飞船相撞。

如果与炮弹碰撞,那么外星人将使用状态标志连续播放爆炸的那几帧,当状态达到5时,它们将不再被绘制到界面上。

如果碰撞发生在玩家的飞船身上,那么玩家会死亡,游戏也就结束了。

我们也可以检查一个随机数,看看外星人是否开始轰炸。如果是,我们将状态设置为1,这将开始调用flyAlien()函数。这个函数会检查外星人的位置,并根据侧边的不同改变外星人的角度,然后根据角度更改x和y坐标。为了方便大家看明白,我们这里处理的比较简单,你也可以使用一些乘数变量将其折叠到x坐标和角度上,将其收窄。

相关代码:

https://github.com/IoToutpost/Python_game

要运行调试请先安装Pygame Zero。

现在大家应该初步掌握了Galaxian游戏的基础知识。你可以试着完善它了。

老游戏新写之Jetpac重返地球

主人公流落外星球,遇到一群不讲道理的外星人。想跑路,载具又摔得七零八落。

任务:​顶着外星人的进攻,把宇宙飞船的零件都找回来组装好,然后为其添加燃料。最后安全逃离,重返地球。

这是由Chris和Tim Stamper兄弟于1983年创建的8位机游戏Jetpac里的剧情,也是其工作室Ultimate Play the Game出品的首批热门游戏之一。

8位ZX Spectrum电脑上的Jetpac

而当一个宇航员和Ultimate Play the Game的徽标在屏幕上出现时,你知道之前的等待是值得的(八十年代能有这么个游戏玩已经很激动)。

游戏的角色是不幸的宇航员杰特曼,他必须收集四处散落的零件,制造火箭并为其添加燃料,同时还要与成群的致命外星人战斗。

本文提供的代码片段包含收集火箭零件和燃料,以便杰特曼搭载火箭起飞的技巧。

我们可以对所有屏幕元素和Actor碰撞例程使用内置的Pygame Zero Actor对象,以便处理重力并拾取物品。

首先,我们需要初始化Actor。

我们需要游戏中的主人公杰特曼,地面,火箭的三个零件,还有火箭发动机所需的一些燃料。

每个Actor的行为方式将由一组列表决定。我们有一个重力对象列表,每帧绘制的对象,平台列表,碰撞对象列表以及可以拾取的对象列表。

杰特曼跳进火箭,回家了。欢呼!

我们的draw()函数很简单,因为它循环遍历绘制列表中的项目列表,然后再绘制几个条件元素。

update()功能是所有动作发生的地方:我们检查键盘输入来移动杰特曼,将重力应用于重力列表上的所有项目,检查与平台列表的碰撞,如果杰特曼触摸它,则拾取这个项目(对象),应用对杰特曼的任何推力,并移动杰特曼持有的任何物品随他一起移动。

完成所有操作后,我们可以检查添加的燃料量是否已达到火箭可升空的程度。

如果查看辅助函数checkCollisions()checkTouching(),你会发现它们使用了不同的碰撞检测方法,第一种方法是检查与指定点的碰撞,以便我们可以检测到与actor顶部或底部的碰撞以及触摸冲突是矩形或边界框的冲突,因此如果两个Actor的边界框相交,则会记录一个冲突。

另一个辅助函数applyGravity()使重力列表中的所有元素向下移动,直到Actor的底部撞到碰撞列表上的某物为止。

目前的程序主要就是组装一枚火箭,加满燃料,然后升空。你后续要添加的是一群讨厌的外星人,以及一种用激光枪摧毁它们的方法。

以上就是Mark的Jetpac代码,你可以在这里下载。

https://github.com/IoToutpost/Python_game/

Have fun.

树莓派音乐盒,让你像DJ一样玩音乐

用按钮来控制LED灯,是树莓派比较经典的一种应用。

那用按钮来控制音乐呢?

我们这次的任务是做个树莓派音乐盒,以便你能像DJ一样,通过它不停的切换和调整音乐。

硬件需求:

  • 树莓派1个
  • 面包板1个
  • 接触式开关4个
  • 公对母跳线5根
  • 母对母跳线4根
  • 扬声器或耳机1个

软件需求:

Raspbian 最新版

制作过程:

先找到你想播放的音频文件,如果你自己没有什么准备,可以在Raspbian系统的/usr/share/sonic-pi/samples目录中找一些示例音乐。

不过你要用Python播放声音的话,得将里面的.flac文件转换为.wav文件。

批量转换命令:

for f in *.flac; do ffmpeg -i "$f" "${f%.flac}.wav"; done

如果没出错,你现在应该得到大量的wav文件了。

挑出四个备用,对应4个按钮。将其保存在gpio-music-box目录中

drum_tom_mid_hard.wav
drum_cymbal_hard.wav
drum_snare_hard.wav
drum_cowbell.wav

电路最基本的接法:

当然,用面包板会让你更方便。

示例程序中有四个按钮,所以我们要分别接入GPIO4、10、17、27四个口。

接下来开始撸代码。

import pygame
from gpiozero import Button

pygame.init()

drum = pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_tom_mid_hard.wav")
cymbal = pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cymbal_hard.wav")
snare = pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_snare_hard.wav")
bell = pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cowbell.wav")

btn_drum = Button(4)
btn_cymbal = Button(17)
btn_snare= Button(27)
btn_bell = Button(10)

btn_drum.when_pressed = drum.play
btn_cymbal.when_pressed = cymbal.play
btn_snare.when_pressed = snare.play
btn_bell.when_pressed = bell.play

以上的代码应该能正常工作,但不够简洁优雅,我们用Python的字典功能来优化一下。

创建一个字典。

button_sounds = {Button(4): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_tom_mid_hard.wav"),
                 Button(17): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cymbal_hard.wav"),
                 Button(27): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_snare_hard.wav"),
                 Button(10): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cowbell.wav")}

然后用Button作为键,Sound作为值。

优化后的代码:

import pygame
from gpiozero import Button

pygame.init()

button_sounds = {Button(4): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_tom_mid_hard.wav"),
                 Button(17): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cymbal_hard.wav"),
                 Button(27): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_snare_hard.wav"),
                 Button(10): pygame.mixer.Sound("/home/pi/gpio-music-box/samples/drum_cowbell.wav")}

for button, sound in button_sounds.items():
    button.when_pressed = sound.play

是不是简洁多了?

好了,拿去玩吧。

视频讲解地址:

https://v.qq.com/x/page/x0967xsxe7j.html

线索:Raspberrypi.org

编译:王文文

你能不损失数百万美元成功登月吗?

对航空知识有兴趣的朋友都知道,飞行器在月球软着陆不是一件容易的事情。

即使在刚刚过去的2019年,印度也没能成功实现该技术,承载着希望的“月船2号”着陆器在距离月球表面2.1公里的时候失去了信号,从此不知所踪。

迄今为止,只有中美俄三个国家实现了月球软着陆。

印度“月船2号”着陆器失联前的直播画面

在阿波罗11号登月十周年纪念日的时候,阿塔里(Atari)公司曾经出了一款名叫“Lunar Lander”的投币式街机游戏。

游戏的玩法是通过方向调整和推力控制来引导你的着陆器,将其轻轻地放到安全而平坦的停靠区。如果玩家能将着陆器成功停放在更具挑战的险峻区域,将获得额外的积分。

街机的版本是有操纵杆的,玩家可以用其控制方向和大小不同的推力,并以屏幕顶部的海拔,水平速度和垂直速度为指导,在燃料有限的情况下按时降落飞船。

游戏内置了四个难度级别,分别调整了着陆控制器和着陆区域,玩家需要凭借高超的技巧才能涉险过关。

我们这次用Pygame Zero来简单还原一下这款游戏,如果你不打算外接操纵杆,用键盘操作也可以。

构思

首先用绘制好的静态背景替换矢量图形,并将其用作碰撞检测和高度统计。

如果我们的背景是着陆器可以飞行的黑色背景,而着陆区所在的地方是另一种颜色,那么我们可以使用Pygame的image.get_at()函数来测试像素点是否在着陆位置。

我们还可以检测着陆器沿Y轴向下的像素线,直到抵达着陆区,这将让我们获悉着陆器当前的高度。

着陆器的控制非常简单,因为我们可以捕获左右箭头键来增加或减少着陆器的旋转角度。

然而当施加推力(通过按向上箭头)时,事情会变得有些复杂。我们需要记住推力来自哪个方向,让飞船即使打转也将继续沿该方向运动。所以我们在着陆器对象上附加了direction属性。对着陆器的位置施加一点重力,然后我们只需要一点三角函数的知识即可根据着陆器的速度和行进方向算出其运动轨迹。

要判断着陆器安全着陆还是在月球表面撞坏,我们要观察其到达高度1时,飞行器的下降速度和角度。

如果速度足够慢且角度接近垂直,则我们触发着陆成功的消息,游戏结束。

如果着陆器在没有满足这些条件的情况下达到零高度,则我们将判定坠机事故。

有兴趣的话,你还可以在此基础上添加一个有限的燃料表和可变的难度级别之类的东西。甚至可以尝试添加原始街机游戏中火箭助推器噪音的声音。

要点:

推力方位的改变可以有多种方法完成。在本例中我们简单一点,一个方向被施加推力,它就逐渐往那个方向移动,直到新的方向出现推力。你可以尝试对其进行X轴和Y轴方向计算,以获得结合点的坐标值。还可以添加操纵杆控制,提供可变的推力。

以下是核心代码段:

接下来看看效果:

视频:https://v.qq.com/x/page/b095944gc59.html

完整代码请访问:https://github.com/IoToutpost/Python_game

调试前记得先安装Pygame Zero。

线索:Wireframe #37

编译:王文文

解析Python开发的一款迷你跑步游戏

之前“IoT前哨站”上发了一些用Python写文本游戏的文章。不管对于Python开发者来说还是对于游戏爱好者来说,都非常适合打基础。

这次我们迈入图形时代,来看看国外开发者“Rik Cross”制作的一款迷你跑步游戏。

他用了不到一百行代码,就写出了值得一玩的2D动作游戏,怎么做到的?

在此之前,先向大家介绍一个游戏框架:pgzero。

该框架全名Pygame Zero,是一个基于Pygame的游戏编程框架。它可以更容易地编辑游戏,无需模板、不用编写事件循环,也无需学习复杂的Pygame API,而且支持树莓派。

安装
pip install pgzero

需求:
通过键盘的左右键操作,让运动员向前奔跑,每过25米有路标提醒,最后看谁在百米跑步中耗时最少。

代码下载地址:
https://github.com/IoToutpost/Python_game/tree/master/Sprint

素材(地址同上):
Images文件夹中有21张图,包括运动员的动作分解、跑道等。

其中的关键代码,是一个叫做Sprinter()的类。

class Sprinter(Actor):
     def init(self, **kwargs):
         super().init(image='idle', pos=(200,220), **kwargs)
         self.startTime = time()
         self.finishTime = time()
         self.runFrames = ['run' + str(i) for i in range(1,16)]
         self.timeOnCurrentFrame = 0
         self.speed = 0
         self.lastPressed = None
         self.keyPressed = False
         self.distance = 0
# 将运动员推进到下一帧
def nextFrame(self):
    # 如果当前空闲,则启动正在运行的动画。 
    if self.image == 'idle':
        self.image = self.runFrames[0]
    else:
        # 在列表中找到下一个图像,然后返回到第一个图像 
        # 当列表已经到末尾的时候
        nextImageIndex = (self.runFrames.index(self.image) + 1) % len(self.runFrames)
        self.image = self.runFrames[nextImageIndex]

# 检查左右方向键是否正确
# 被交替按下
def isNextKeyPressed(self):
    if keyboard.left and self.lastPressed is not 'left' and not keyboard.right:
        self.lastPressed = 'left'
        return True
    if keyboard.right and self.lastPressed is not 'right' and not keyboard.left:
        self.lastPressed = 'right'
        return True
    return False

def update(self):
    # 更新运动员的速度
    # 交替按键加速
    if self.isNextKeyPressed() and self.distance < 100:
        self.speed = min(self.speed + ACCELERATION, 0.15)
    # 如果没有按键,减速
    else:
        self.speed = max(0, self.speed-DECELERATION)
    # 根据运动员的速度更新距离 
    self.distance += self.speed
    # 根据运动员的速度对其进行动画 
    self.timeOnCurrentFrame += 1
    if self.speed > 0 and self.timeOnCurrentFrame > 10 - (self.speed * 75):
        self.timeOnCurrentFrame = 0
        self.nextFrame()
    # 如果不移动,则设置为空闲
    if self.speed <= 0:
        self.image = 'idle'

里面有一些变量用来跟踪运动员的速度和距离,以及全局的常量(ACCELERATION和DECELERATION的值)。这样可以确定玩家的速度变化。这些数字很小,因为它们代表了玩家加速和减速时,每一帧对应的米数。

玩家通过交替按左右键来增加运动员的速度。这个输入由Sprinter类中的isNextKeyPressed()方法处理,如果按下正确的键,该方法将返回True。

lastPressed变量用于确保左右键被交替按压。如果未按下任何键,玩家会减速,并且该减速程度应小于加速度,以免让玩家突然停下。

在游戏设计中,作者用了gameart2d.com上一个名为“The Boy”的免费形象来作为运动员,里面有15张跑步动作分解图构成的循环。他将从空闲状态开始,只要速度大于0,就切换到跑步动作循环。

这是通过index()在runFrames列表中查找当前运动员图像的名称来实现的,程序会将当前图像设置为列表中的下一个图像(并且在到达列表的末尾时返回第一个图像)。

我们还需要让运动员在跑步动作循环中以成比例的速度向前移动,通过跟踪当前图像显示的帧数来实现(在那个名为timeOnCurrentFrame的变量中)。

为了给玩家一种移动的错觉,作者添加了一些经过玩家的物品:一条终点线和三个显示跑动距离的标记。

这些物品出现的时机是根据运动员在屏幕上的x位置和运动距离计算出来的。

​然而,这意味着每个物品距离玩家最多只有100像素的距离,似乎移动的有点缓慢。我们可以通过SCALE因子来解决,它对应的是运动员跑过的米数和屏幕上像素之间的关系。比如设成1:75。

这些物品最初被绘制在屏幕看不见的右侧,然后向左移动并更快地经过运动员。

最后,startTime和finishTime变量用于计算比赛时间。这两个值最初都设置为比赛开始的时间,只要跑动的距离小于100,finishTime就会更新。使用time模块,比赛时间可以简单地计算为finishTime – startTime。

附注:该游戏在树莓派和Windows PC上都能跑,如果要试玩,记得在Python文件前面加pgzrun命令。

Have fun.

素材:Wireframe #23

编译:王文文,前51CTO安全频道主编,RedHat认证工程师,华为HCIP-IoT认证工程师。