对航空知识有兴趣的朋友都知道,飞行器在月球软着陆不是一件容易的事情。
即使在刚刚过去的2019年,印度也没能成功实现该技术,承载着希望的“月船2号”着陆器在距离月球表面2.1公里的时候失去了信号,从此不知所踪。
迄今为止,只有中美俄三个国家实现了月球软着陆。
印度“月船2号”着陆器失联前的直播画面
在阿波罗11号登月十周年纪念日的时候,阿塔里(Atari)公司曾经出了一款名叫“Lunar Lander”的投币式街机游戏。
游戏的玩法是通过方向调整和推力控制来引导你的着陆器,将其轻轻地放到安全而平坦的停靠区。如果玩家能将着陆器成功停放在更具挑战的险峻区域,将获得额外的积分。
街机的版本是有操纵杆的,玩家可以用其控制方向和大小不同的推力,并以屏幕顶部的海拔,水平速度和垂直速度为指导,在燃料有限的情况下按时降落飞船。
游戏内置了四个难度级别,分别调整了着陆控制器和着陆区域,玩家需要凭借高超的技巧才能涉险过关。
我们这次用Pygame Zero来简单还原一下这款游戏,该框架全名Pygame Zero,是一个基于Pygame的游戏编程框架。它可以更容易地编辑游戏,无需模板、不用编写事件循环,也无需学习复杂的Pygame API,而且支持树莓派。
安装:
pip install pgzero

构思:
首先用绘制好的静态背景替换矢量图形,并将其用作碰撞检测和高度统计。
如果我们的背景是着陆器可以飞行的黑色背景,而着陆区所在的地方是另一种颜色,那么我们可以使用Pygame的image.get_at()函数来测试像素点是否在着陆位置。
我们还可以检测着陆器沿Y轴向下的像素线,直到抵达着陆区,这将让我们获悉着陆器当前的高度。
着陆器的控制非常简单,因为我们可以捕获左右箭头键来增加或减少着陆器的旋转角度。
然而当施加推力(通过按向上箭头)时,事情会变得有些复杂。我们需要记住推力来自哪个方向,让飞船即使打转也将继续沿该方向运动。所以我们在着陆器对象上附加了direction属性。对着陆器的位置施加一点重力,然后我们只需要一点三角函数的知识即可根据着陆器的速度和行进方向算出其运动轨迹。
要判断着陆器安全着陆还是在月球表面撞坏,我们要观察其到达高度1时,飞行器的下降速度和角度。
如果速度足够慢且角度接近垂直,则我们触发着陆成功的消息,游戏结束。
如果着陆器在没有满足这些条件的情况下达到零高度,则我们将判定坠机事故。
有兴趣的话,你还可以在此基础上添加一个有限的燃料表和可变的难度级别之类的东西。甚至可以尝试添加原始街机游戏中火箭助推器噪音的声音。
要点:
推力方位的改变可以有多种方法完成。在本例中我们简单一点,一个方向被施加推力,它就逐渐往那个方向移动,直到新的方向出现推力。你可以尝试对其进行X轴和Y轴方向计算,以获得结合点的坐标值。还可以添加操纵杆控制,提供可变的推力。
以下是核心代码段:
import math
from pygame import image, Color
import time
start_time = time.time()
backgroundImage = image.load('images/background.png')
lander = Actor('lander',(50,30))
lander.angle = lander.direction = -80
lander.thrust = 0.5
gravity = 0.8
lander.burn = speedDown = gameState = gameTime = 0
def draw():
global gameTime
screen.blit('background',(0,0))
screen.blit('space',(0,0))
r = lander.angle
if(lander.burn > 0):
lander.image = "landerburn"
else:
lander.image = "lander"
lander.angle = r
lander.draw()
if gameState == 0:
gameTime = int(time.time() - start_time)
screen.draw.text("Altitude : "+ str(getAlt()), topleft=(650, 10), owidth=0.5, ocolor=(255,0,0), color=(255,255,0) , fontsize=25)
screen.draw.text("Time : "+ str(gameTime), topleft=(40, 10), owidth=0.5, ocolor=(255,0,0), color=(255,255,0) , fontsize=25)
if gameState == 2:
screen.draw.text("Congratulations \nThe Eagle Has Landed", center=(400, 50), owidth=0.5, ocolor=(255,0,0), color=(255,255,0) , fontsize=35)
if gameState == 1:
screen.draw.text("Crashed", center=(400, 50), owidth=0.5, ocolor=(255,0,0), color=(255,255,0) , fontsize=35)
def update():
global gameState, speedDown
if gameState == 0:
if keyboard.up:
lander.thrust = limit(lander.thrust+0.01,0,1)
changeDirection()
lander.burn = 1
if keyboard.left: lander.angle += 1
if keyboard.right: lander.angle -= 1
oldPos = lander.center
lander.y += gravity
newPos = calcNewXY(lander.center, lander.thrust, math.radians(90-lander.direction))
lander.center = newPos
speedDown = newPos[1] - oldPos[1]
lander.thrust = limit(lander.thrust-0.001,0,1)
lander.burn = limit(lander.burn-0.05,0,1)
if speedDown < 0.2 and getAlt() == 1 and lander.angle > -5 and lander.angle < 5:
gameState = 2
if getAlt() == 0:
gameState = 1
def changeDirection():
if lander.direction > lander.angle: lander.direction -= 1
if lander.direction < lander.angle: lander.direction += 1
def limit(n, minn, maxn):
return max(min(maxn, n), minn)
def calcNewXY(xy,speed,ang):
newx = xy[0] - (speed*math.cos(ang))
newy = xy[1] - (speed*math.sin(ang))
return newx, newy
def getAlt():
testY = lander.y+8
height = 0;
while testPixel((int(lander.x),int(testY))) == Color('black') and height < 600:
testY += 1
height += 1
return height
def testPixel(xy):
if xy[0] >= 0 and xy[0] < 800 and xy[1] >= 0 and xy[1] < 600:
return backgroundImage.get_at(xy)
else:
return Color('black')
完整代码请访问:https://github.com/IoToutpost/P
安装:
pip install pgzero
PS:若要全面系统学习Pygame Zero可以参考《趣学Python游戏编程》一书,该书通过十个经典游戏案例,深入浅出地介绍了游戏编程的基本原理,以及Pygame Zero的具体使用方法。相信学完这本书后你也能开发出如此精彩的小游戏。