从现在开始,就是具体游戏的制作了。作者是每章一个游戏,有些游戏我不是很感兴趣,只对其中有兴趣,所以就只讲这一些。
第一个游戏就是贪吃蛇游戏,说起这个游戏,这可能是我玩的最早的游戏之一了,记得那时彩屏手机没有出来时,所有单色手机上面几乎都有这个游戏,简直风靡一时啊。以前在单片机的液晶屏上实现过贪吃蛇,不过太简陋了。
看完讲贪吃蛇游戏这章,越来越感觉到python有意思了,字典这个数据结构的应用让整个程序一下子简单了很多。而且作者写的很仔细,整个程序设计的思路通过代码就能一目了然,关于旋转图像的坏处也有说明。而且在这章最后关于变量是否要复用这点也给了看法,很好很强大。每段代码为什么这样写,也解释的很清楚。而且它的代码风格值得我学习。
下面是贪吃蛇程序的代码,把代码敲一遍加深理解。
import pygame, sys, random
from pygame.locals import*
FPS = 15
WINDOWWIDTH =640
WINDOWHEIGHT = 480
CELLSIZE = 20
assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size"
assert WINDOWHEIGHT % CELLSIZE == 0, "Window height muset be multiple of cell size"
CELLWIDTH = WINDOWWIDTH / CELLSIZE
CELLHEIGHT = WINDOWHEIGHT / CELLSIZE
#RGB
WHITE = (255, 255, 255)
BLACK = (0, 0 , 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
DARKGREEN = (0, 155, 0)
DARKGRAY = (40, 40, 40)
BGCOLOR = BLACK
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
HEAD = 0 #很巧妙的运用
#main function
def main():
global FPSCLOCK, DISPLAYSURF, BASICFONT
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
pygame.display.set_caption('Wormy')
showStartScreen() #显示起始画面
while True:
runGame() #运行游戏主体
showGameOverScreen() #显示游戏结束画面
def runGame():
#设置蛇身开始在随机位置
startx = random.randint(5, CELLWIDTH-6)
starty = random.randint(5, CELLHEIGHT-6)
wormCoods = [{'x': startx, 'y': starty},
{'x': startx-1, 'y': starty},
{'x': startx-2, 'y': starty}]
direction = RIGHT #蛇初始方向向右
#得到一个随机苹果的位置
apple = getRandomLocation()
while True:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
elif event.type == KEYDOWN: #处理蛇的移动方向
if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:
direction = LEFT
elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:
direction = RIGHT
elif (event.key == K_UP or event.key == K_w) and direction != DOWN:
direction = UP
elif (event.key == K_DOWN or event.key == K_s) and direction != UP:
direction = DOWN
elif event.key == K_ESCAPE:
terminate()
#看蛇身是否撞击到自己或四周墙壁
if wormCoods[HEAD]['x'] == -1 or wormCoods[HEAD]['x'] == CELLWIDTH or wormCoods[HEAD]['y'] == -1 or wormCoods[HEAD]['y'] == CELLHEIGHT:
return #game over
for wormBody in wormCoods[1:]:
if wormBody['x'] == wormCoods[HEAD]['x'] and wormBody['y'] == wormCoods[HEAD]['y']:
return #game over
#蛇是否迟到苹果
if wormCoods[HEAD]['x'] == apple['x'] and wormCoods[HEAD]['y'] == apple['y']:
#不删除蛇身尾段
apple = getRandomLocation() #设置一个新的苹果
else:
del wormCoods[-1] #删除蛇身尾段
#添加蛇身头段
if direction == UP:
newHead = {'x': wormCoods[HEAD]['x'], 'y': wormCoods[HEAD]['y']-1}
elif direction == DOWN:
newHead = {'x': wormCoods[HEAD]['x'], 'y': wormCoods[HEAD]['y']+1}
elif direction == LEFT:
newHead = {'x': wormCoods[HEAD]['x']-1, 'y': wormCoods[HEAD]['y']}
elif direction == RIGHT:
newHead = {'x': wormCoods[HEAD]['x']+1, 'y': wormCoods[HEAD]['y']}
wormCoods.insert(0,newHead)
DISPLAYSURF.fill(BGCOLOR)
drawGrid() #画格子
drawWorm(wormCoods) #画蛇身
drawApple(apple) #画苹果
drawScore(len(wormCoods) - 3)#显示得到分数
pygame.display.update()
FPSCLOCK.tick(FPS)
#提示按键消息
def drawPressKeyMsg():
pressKeySurf = BASICFONT.render('Press a key to play.', True, DARKGRAY)
pressKeyRect = pressKeySurf.get_rect()
pressKeyRect.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT-30)
DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
#检测按键
def checkForKeyPress():
if len(pygame.event.get(QUIT)) > 0:
terminate()
keyUpEvents = pygame.event.get(KEYUP)
if len(keyUpEvents) == 0:
return None
if keyUpEvents[0].key == K_ESCAPE:
terminate()
return keyUpEvents[0].key
#显示开始界面
def showStartScreen():
titleFont = pygame.font.Font('freesansbold.ttf', 100)
titleSurf1 = titleFont.render('Wormy!', True, WHITE, DARKGREEN)
titleSurf2 = titleFont.render('Wormy!', True, GREEN)
degrees1 = 0
degrees2 = 0
while True:
DISPLAYSURF.fill(BGCOLOR)
rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1)
rotatedRect1 = rotatedSurf1.get_rect()
rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
DISPLAYSURF.blit(rotatedSurf1,rotatedRect1)
rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2)
rotatedRect2 = rotatedSurf2.get_rect()
rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
DISPLAYSURF.blit(rotatedSurf2,rotatedRect2)
drawPressKeyMsg()
if checkForKeyPress():
pygame.event.get()
return
pygame.display.update()
FPSCLOCK.tick(FPS)
degrees1 += 3
degrees2 += 7
#游戏结束
def terminate():
pygame.quit()
sys.exit()
#得到随机苹果位置
def getRandomLocation():
return {'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0, CELLHEIGHT - 1)}
#显示游戏结束画面
def showGameOverScreen():
gameOverFont = pygame.font.Font('freesansbold.ttf', 150)
gameSurf = gameOverFont.render('GAME', True, WHITE)
overSurf = gameOverFont.render('OVER', True, WHITE)
gameRect = gameSurf.get_rect()
overRect = overSurf.get_rect()
gameRect.midtop = (WINDOWWIDTH / 2, 10)
overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25)
DISPLAYSURF.blit(gameSurf, gameRect)
DISPLAYSURF.blit(overSurf, overRect)
drawPressKeyMsg()
pygame.display.update()
pygame.time.wait(500)
checkForKeyPress()
while True:
if checkForKeyPress():
pygame.event.get()
return
def drawScore(score):
scoreSurf = BASICFONT.render('Score: %s' %(score), True, WHITE)
scoreRect = scoreSurf.get_rect()
scoreRect.topleft = (WINDOWWIDTH - 120, 10)
DISPLAYSURF.blit(scoreSurf, scoreRect)
def drawWorm(wormCoods):
for coord in wormCoods:
x = coord['x'] * CELLSIZE
y = coord['y'] * CELLSIZE
wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)
pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect)
wormInnerSegmentRect = pygame.Rect(x+4, y+4, CELLSIZE-8, CELLSIZE-8)
pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)
def drawApple(coord):
x = coord['x'] * CELLSIZE
y = coord['y'] * CELLSIZE
appleRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)
pygame.draw.rect(DISPLAYSURF, RED, appleRect)
def drawGrid():
for x in range(0, WINDOWWIDTH, CELLSIZE):
pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT))
for y in range(0, WINDOWHEIGHT, CELLSIZE):
pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y))
if __name__ == '__main__':
main()
然后就来说说上面的这段代码。先说明下整个程序流程。
一个main()函数里面就是这个程序的大概流程,首先相关pygame的初始化,显示游戏启动画面,接着进入死循环,runGame()就是游戏主体部分了,是程序核心。而如果游戏失败,就会只执行显示结束画面showGameOverScreen(),然后再次进行游戏。
先看看程序开始初始化的部分有哪些东西。
我们把蛇身看成是一段一段组成的,CELLSIZE这个常量就是蛇身每段的大小。我们要确保蛇身与整个显示屏幕的大小是成整数倍的关系,不然显示就有问题,所以在开始加了异常处理。assert 语句检测我们给定的屏幕大小是否与蛇身段大小成整数倍。
我们可以通过蛇身来与屏幕具体像素联系起来,简化编程,所以有了CELLWIDTH 和 CELLHEIGHT两个变量。然后我们把定义我们游戏会用到的颜色和把方向也定义成大写的变量,这样增强代码可读性。最后有个HEAD这个变量是在后面很有用的。下面会介绍。
main()函数开始的一部分代码含义
首先我们定义了三个全局变量,因为它们会在其它的一些函数中出现。分别是帧率,游戏显示窗口,基本字体,用global关键字修饰。
然后Pygame进行初始化,设置一些数据,比如帧率,加载基本字体,设置窗口标题,窗口大小。
然后显示游戏启动画面,接着进入游戏循环体,运行游戏。游戏启动画面放在后面说,先说循环体里面的东西。
看看runGame()这个游戏核心部分是怎么实现的。
首先蛇刚开始出来时因显示在屏幕的随机的一个位置,所以我们需要产生随机数,产生随机数要在程序开始出加载python的random模块。为了防止蛇身一出来就离墙太近,导致游戏失败,所以我们的蛇身会离墙有一段距离。产生的随机数范围为(5,CELLWIDTH-6)。然后我们用字典这种数据结构将坐标存放起来(字典真是好用),因为开始蛇身只有三段,所以有3个字典元素。用列表把这三个字典元素包容在一起。蛇的初始方向设为像右。
设置完蛇身的初始状态,我们就要设置初始苹果的状态,getRandomLocation()函数产生一个随机位置用于放置苹果。蛇的头部元素是wormCoords[0],为了代码可读性,因为后面对蛇身头部的操作比较多,所以我们用wormCoods[HEAD]代替wormCoods[0]。这就是为什么前面开始我们要设置一个HEAD全局变量的原因了。
接着是游戏循环了,所以跟游戏相关的关键操作都在这里面了,比如显示,按键交互等。循环体中主要处理按键消息,for event in pygame.event.get()得到所有的消息,然后对消息进行判断处理,如果是消息是退出,则终止游戏。接着处理通过按键处理蛇身的移动,看看这里的if里面的条件有个and,所以条件都满足才执行代码。条件是这样来的,比如我们蛇身开始方向向右,如果我们按左键的蛇身就向左移动的话,那么蛇身就会自己相撞,游戏马上失败,这游戏就不合理了。所以我们要按下左键的时候要保证蛇身移动方向不向右,其它方向按键的处理也是一个道理。代码如下:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
elif event.type == KEYDOWN: #处理蛇的移动方向
if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:
direction = LEFT
elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:
direction = RIGHT
elif (event.key == K_UP or event.key == K_w) and direction != DOWN:
direction = UP
elif (event.key == K_DOWN or event.key == K_s) and direction != UP:
direction = DOWN
elif event.key == K_ESCAPE:
terminate()
蛇身移动一个距离完我们就要判断它是否撞到墙壁或自身。代码如下:
#看蛇身是否撞击到自己或四周墙壁
if wormCoods[HEAD]['x'] == -1 or wormCoods[HEAD]['x'] == CELLWIDTH or wormCoods[HEAD]['y'] == -1 or wormCoods[HEAD]['y'] == CELLHEIGHT:
return #game over
for wormBody in wormCoods[1:]:
if wormBody['x'] == wormCoods[HEAD]['x'] and wormBody['y'] == wormCoods[HEAD]['y']:
return #game over
第一个if是判断是否撞到墙壁,拿X方向上来说,最左边左边是-1,左右边则是CELLWIDTH,因为坐标从0开始,到CELLWIDTH-1结束。Y方向上同理。
然后是检测蛇身是否撞到自己,这里用一个循环,依次检查从头部后面的第二段蛇身开始,所以范围是wormCoods[1:] 看 蛇身是否与蛇的头部相撞,只需判断两个坐标是不是相等就是了。
接着就判断蛇是否吃到苹果。代码如下:
#蛇是否迟到苹果
if wormCoods[HEAD]['x'] == apple['x'] and wormCoods[HEAD]['y'] == apple['y']:
#不删除蛇身尾段
apple = getRandomLocation() #设置一个新的苹果
else:
del wormCoods[-1] #删除蛇身尾段
这里跟判断蛇身是否自己相撞是一个道理,只要判断蛇头的坐标是否与苹果的坐标相等。需要注意的是,当吃到苹果的话,蛇的尾段还是保留的,然后得到一个新的放置苹果的随机位置。如果没有吃到的话,我们就要删除尾段,蛇身前进一格,这个想一想就明白了。
删除掉蛇身尾段的话,接着就要把丢失的尾段补起来,也就是要添加蛇身的头段。代码如下:
#添加蛇身头段
if direction == UP:
newHead = {'x': wormCoods[HEAD]['x'], 'y': wormCoods[HEAD]['y']-1}
elif direction == DOWN:
newHead = {'x': wormCoods[HEAD]['x'], 'y': wormCoods[HEAD]['y']+1}
elif direction == LEFT:
newHead = {'x': wormCoods[HEAD]['x']-1, 'y': wormCoods[HEAD]['y']}
elif direction == RIGHT:
newHead = {'x': wormCoods[HEAD]['x']+1, 'y': wormCoods[HEAD]['y']}
wormCoods.insert(0,newHead)
添加蛇身头段的位置就要根据蛇身的移动方向来决定了。最后我们嗲用列表的插入方法,把新的蛇头插入到列表开始位置。wormCoods.insert(0,newHead)。
这就是整个游戏的核心部分了。下面就是根据我们上面对蛇的状态的修改做出的一些显示更新操作了。代码如下:
DISPLAYSURF.fill(BGCOLOR)
drawGrid() #画格子
drawWorm(wormCoods) #画蛇身
drawApple(apple) #画苹果
drawScore(len(wormCoods) - 3)#显示得到分数
pygame.display.update()
FPSCLOCK.tick(FPS)
这几个函数都比较简单(略)
现在来说说游戏启动画面函数。showStartScreen().代码如下
def showStartScreen():
titleFont = pygame.font.Font('freesansbold.ttf', 100)
titleSurf1 = titleFont.render('Wormy!', True, WHITE, DARKGREEN)
titleSurf2 = titleFont.render('Wormy!', True, GREEN)
degrees1 = 0
degrees2 = 0
while True:
DISPLAYSURF.fill(BGCOLOR)
rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1)
rotatedRect1 = rotatedSurf1.get_rect()
rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
DISPLAYSURF.blit(rotatedSurf1,rotatedRect1)
rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2)
rotatedRect2 = rotatedSurf2.get_rect()
rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
DISPLAYSURF.blit(rotatedSurf2,rotatedRect2)
drawPressKeyMsg()
if checkForKeyPress():
pygame.event.get()
return
pygame.display.update()
FPSCLOCK.tick(FPS)
degrees1 += 3
degrees2 += 7
启动画面的显示效果为两个字符串不断的旋转。所以需要创建两个字体字体对象和两个旋转角度的变量。还需要提示显示按下按键开始游戏。所以还有一个显示按键消息的函数,drawPressKeyMsg()。不单独现在这里面的原因而封装成一个函数的原因是在游戏结束画面中也会用到这个函数。接着检查按键消息,如果有按键按下则进入游戏,最后在函数末尾更新显示及改变每次增加的角度值。其中旋转图像用到的是pygame.transform.rotate()函数。
文章里面还说到了旋转这个方式并不是很完美。因为图像的旋转会给图像带来失真,使图像扭曲。除非你每次旋转的角度是90的整数倍。
最后给出程序运行效果截图:
我越来越觉得python很有爱啊。最后还要介绍一款文本编辑器,也是昨天才发现的。名字就是Sublime Text 2。太舒服了这款文本编辑器,而且各种主题很炫。
这是它的下载地址也是官网地址:http://www.sublimetext.com/,最新版是2.0.1。