在这个游戏中,玩家操纵一个小人,躲避从屏幕顶部掉落的一大堆敌人,玩家躲避敌人的时间越久,得到的分数越高。
为了好玩,我们还会为游戏加入一些作弊模式,如果玩家按下“x”键,每一个敌人的速度就会降低到最慢,如果玩家按下“z”键,敌人就会反转方向,沿着屏幕向上移动而不是往下落。
import pygame, random, sys
from pygame.locals import *
WINDOWWIDTH = 600
WINDOWHEIGHT = 600
TEXTCOLOR = (255, 255, 255)
BACKGROUNDCOLOR = (0, 0, 0)
FPS = 40
BADDIEMINSIZE = 10
BADDIEMAXSIZE = 40
BADDIEMINSPEED = 1
BADDIEMAXSPEED = 8
ADDNEWBADDIERATE = 6
PLAYERMOVERATE = 5
def terminate():
pygame.quit()
sys.exit()
def waitForPlayerToPressKey():
while True:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
if event.type == KEYDOWN:
if event.key == K_ESCAPE: # pressing escape quits
terminate()
return
def playerHasHitBaddie(playerRect, baddies):
for b in baddies:
if playerRect.colliderect(b['rect']):
return True
return False
def drawText(text, font, surface, x, y):
textobj = font.render(text, 1, TEXTCOLOR)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
surface.blit(textobj, textrect)
# set up pygame, the window, and the mouse cursor
pygame.init()
mainClock = pygame.time.Clock()
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Dodger')
pygame.mouse.set_visible(False)
# set up fonts
font = pygame.font.SysFont(None, 48)
# set up sounds
gameOverSound = pygame.mixer.Sound('gameover.wav')
pygame.mixer.music.load('background.mid')
# set up images
playerImage = pygame.image.load('player.png')
playerRect = playerImage.get_rect()
baddieImage = pygame.image.load('baddie.png')
# show the "Start" screen
drawText('Dodger', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
drawText('Press a key to start.', font, windowSurface, (WINDOWWIDTH / 3) - 30, (WINDOWHEIGHT / 3) + 50)
pygame.display.update()
waitForPlayerToPressKey()
topScore = 0
while True:
# set up the start of the game
baddies = []
score = 0
playerRect.topleft = (WINDOWWIDTH / 2, WINDOWHEIGHT - 50)
moveLeft = moveRight = moveUp = moveDown = False
reverseCheat = slowCheat = False
baddieAddCounter = 0
pygame.mixer.music.play(-1, 0.0)
while True: # the game loop runs while the game part is playing
score += 1 # increase score
for event in pygame.event.get():
if event.type == QUIT:
terminate()
if event.type == KEYDOWN:
if event.key == ord('z'):
reverseCheat = True
if event.key == ord('x'):
slowCheat = True
if event.key == K_LEFT or event.key == ord('a'):
moveRight = False
moveLeft = True
if event.key == K_RIGHT or event.key == ord('d'):
moveLeft = False
moveRight = True
if event.key == K_UP or event.key == ord('w'):
moveDown = False
moveUp = True
if event.key == K_DOWN or event.key == ord('s'):
moveUp = False
moveDown = True
if event.type == KEYUP:
if event.key == ord('z'):
reverseCheat = False
score = 0
if event.key == ord('x'):
slowCheat = False
score = 0
if event.key == K_ESCAPE:
terminate()
if event.key == K_LEFT or event.key == ord('a'):
moveLeft = False
if event.key == K_RIGHT or event.key == ord('d'):
moveRight = False
if event.key == K_UP or event.key == ord('w'):
moveUp = False
if event.key == K_DOWN or event.key == ord('s'):
moveDown = False
if event.type == MOUSEMOTION:
# If the mouse moves, move the player where the cursor is.
playerRect.move_ip(event.pos[0] - playerRect.centerx, event.pos[1] - playerRect.centery)
# Add new baddies at the top of the screen, if needed.
if not reverseCheat and not slowCheat:
baddieAddCounter += 1
if baddieAddCounter == ADDNEWBADDIERATE:
baddieAddCounter = 0
baddieSize = random.randint(BADDIEMINSIZE, BADDIEMAXSIZE)
newBaddie = {'rect': pygame.Rect(random.randint(0, WINDOWWIDTH-baddieSize), 0 - baddieSize, baddieSize, baddieSize),
'speed': random.randint(BADDIEMINSPEED, BADDIEMAXSPEED),
'surface':pygame.transform.scale(baddieImage, (baddieSize, baddieSize)),
}
baddies.append(newBaddie)
# Move the player around.
if moveLeft and playerRect.left > 0:
playerRect.move_ip(-1 * PLAYERMOVERATE, 0)
if moveRight and playerRect.right < WINDOWWIDTH:
playerRect.move_ip(PLAYERMOVERATE, 0)
if moveUp and playerRect.top > 0:
playerRect.move_ip(0, -1 * PLAYERMOVERATE)
if moveDown and playerRect.bottom < WINDOWHEIGHT:
playerRect.move_ip(0, PLAYERMOVERATE)
# Move the mouse cursor to match the player.
pygame.mouse.set_pos(playerRect.centerx, playerRect.centery)
# Move the baddies down.
for b in baddies:
if not reverseCheat and not slowCheat:
b['rect'].move_ip(0, b['speed'])
elif reverseCheat:
b['rect'].move_ip(0, -5)
elif slowCheat:
b['rect'].move_ip(0, 1)
# Delete baddies that have fallen past the bottom.
for b in baddies[:]:
if b['rect'].top > WINDOWHEIGHT:
baddies.remove(b)
# Draw the game world on the window.
windowSurface.fill(BACKGROUNDCOLOR)
# Draw the score and top score.
drawText('Score: %s' % (score), font, windowSurface, 10, 0)
drawText('Top Score: %s' % (topScore), font, windowSurface, 10, 40)
# Draw the player's rectangle
windowSurface.blit(playerImage, playerRect)
# Draw each baddie
for b in baddies:
windowSurface.blit(b['surface'], b['rect'])
pygame.display.update()
# Check if any of the baddies have hit the player.
if playerHasHitBaddie(playerRect, baddies):
if score > topScore:
topScore = score # set new top score
break
mainClock.tick(FPS)
# Stop the game and show the "Game Over" screen.
pygame.mixer.music.stop()
gameOverSound.play()
drawText('GAME OVER', font, windowSurface, (WINDOWWIDTH / 3), (WINDOWHEIGHT / 3))
drawText('Press a key to play again.', font, windowSurface, (WINDOWWIDTH / 3) - 80, (WINDOWHEIGHT / 3) + 50)
pygame.display.update()
waitForPlayerToPressKey()
gameOverSound.stop()
运行程序后,游戏界面如下图:
游戏导入的模块有pygame,random,sys和pygame.locals。pygame.locals模块包含了几个供Pygame使用的常量,如事件型(QUIT和KEYDOWN等)和键盘按键(K_ESCAPE和K_LEFT)等。
创建常量包含了窗口的宽和高、字体颜色和背景颜色,程序后面调用了mainClock.tick(FPS)则是设置了游戏每秒钟运行的帧数。敌人的宽度和高度均在BADDIEMINSIZE和BADDIEMAXSIZE之间。在游戏循环的每次迭代中,敌人从屏幕上落下的速率在每秒钟BADDIEMINSPEED到BADDIEMINSPEED多个像素之间。游戏循环的每经过ADDNEWBADDIERATE次迭代之后,将在窗口的顶部增加一个新的敌人。如果玩家的角色是移动的,在游戏循环的每次迭代中,PLAYERMOVERATE将保存玩家的角色在窗口中移动的像素数。
我们为游戏创建了几个函数。terminate()函数使游戏退出,waitForPlayerToPressKey()函数有一个无限循环,只有当接收到一个KEYDOWN或QUIT事件时,才会跳出该循环。在循环开始处,pygame.event.get()返回了要检查的事件对象的一个列表。
当程序等待玩家按键的时候,如果玩家关闭了这个窗口,Pygame将生成一个QUIT事件。在这种情况下,调用了terminate()函数。如果接收到一个KEYDOWN事件,那么应该先判断是否按下了ESC键。如果没有生成一个QUIT或KEYDOWN事件,那么代码将保持循环。由于循环什么都没有做,这使得游戏看上去像是已经冻结了,直到玩家按下一个键。
如果玩家的角色和一个敌人碰撞,playerHasHitBaddie()函数将返回True。在Pygame中显示文本,要比直接调用print()函数多花一些步骤,但是,如果把这些代码放入到一个名为drawText()的函数中,那么要在屏幕上显示文本,只需要调用drawText()函数即可。
完成常量和函数的编写之后,下面开始调用创建窗口和时钟的Pygame函数。pygame.init()函数创建了Pygame。pygame.time.Clock()对象保存在mainClock变量中,帮助我们防止程序运行得太快。pygame.display.set_mode()函数创建了一个新的Surface对象,用于在屏幕上显示窗口。pygame.display.set_caption()函数设置了窗口的标题,pygame.mouse.set_visible(False)告诉Pygame上光标不可见,这是因为我们只想要鼠标能够移动屏幕上的角色,而不想让鼠标的光标妨碍到屏幕上的角色的图像。
接下来,创建Sound对象,并且设置背景音乐,背景音乐将在游戏期间持续播放,但是只有当玩家输掉了游戏,才会播放Sound对象。pygame.mixer.music.load()函数加载了一个声音文件以播放背景音乐。这个函数没有返回任何对象,并且每次只能加载一个背景音乐文件。然后加载图像文件用于屏幕上的玩家和角色敌人。玩家角色的图像存储在player.png中,敌人的角色图像存储在baddie.png中。所有敌人角色看上去都是一样的,所以只要为它们准备一个图像文件就可以了。
当游戏第一次启动时,在屏幕上显示“Dodger”名称,然后按下任意键可以开始游戏。当程序开始运行时,topScore变量中的值最初为0。任何时候,当玩家输掉游戏并且得分大于当前的topScore,就用这个更高的分数来替换topScore。
游戏循环的代码通过修改玩家和敌人的位置、处理Pygame生成的事件以及在屏幕上绘制游戏世界,来不断地更新游戏世界的状态。所有这些事件会在1秒钟内发生很多次,这使得游戏“实时”地运行。
有4种不同类型的事件要处理:QUIT、KEYDOWN、KEYUP和MOUSEMOTION。如果Event对象的type属性等于QUIT,则用户关闭了程序;如果事件的类型是KEYDOWN,那么玩家按下了一个按键,当玩家停止按键并且释放该键时,会产生KEYUP事件;在游戏运行中的任何时候,玩家都可以按下键盘上的Esc键来退出游戏。
对于鼠标事件,如果玩家点击鼠标按键,游戏不会做任何事情,但是当玩家移动鼠标的时候,游戏会作出相应。这就使得玩家在游戏中可以有两种方法来控制玩家角色:键盘和鼠标。当鼠标移动时,会产生MOUSEMOTION事件。MOUSEMOTION类型的Event对象,也有一个名为pos的属性,表示鼠标事件的位置。pos属性保存了一个元组,是鼠标光标在窗口中移动到的位置的X坐标和Y坐标。如果事件类型是MOUSEMOTION,玩家的角色将移动到鼠标光标的位置。
把所有数据结构都修改完之后,使用Pygame的图像函数来绘制游戏世界。由于每秒钟会执行多次游戏循环,在新的位置绘制敌人和玩家,会使得它们的移动看上去更平滑自然。
当玩家输掉游戏时,游戏停止播放背景音乐,并且播放“游戏结束”的声音效果。
代码资源:
https://github.com/rectsuly/Python_Dodger_Game