打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
机会来了!专业好玩夏令营免费送?!【码趣学院】分享之二:手把手教你用Python写贪吃蛇小游戏


  贪吃蛇游戏玩法  

在贪吃蛇游戏中,玩家将控制一只不断在屏幕上四处行进的小蛇。玩家不能让小蛇减速,只能够控制小蛇的转向。每隔一段时间,屏幕上将出现一个红苹果,苹果的位置是随机的,玩家的目标是让小蛇吃到苹果。游戏开始的时候,蛇的长度很短,之后每一次吃到苹果,小蛇都会变长一点。当小蛇撞到屏幕的边缘时,游戏就结束了。


下面,让我们一起用Python一步步制作贪吃蛇游戏吧!

(最终效果)

  游戏网格  

如果你之前玩过贪吃蛇游戏,你会发现苹果和小蛇的位置其实都是由网格线确定的。 这些由网格线确定的小方格有它们自己的坐标系,如上图,最左上角的小方格坐标为(0,0),最右下角的坐标为(31,23)。

  初始代码  

  1. # 贪吃蛇游戏

  2. # 关注码趣学院

  3. # 预约免费试听课添加yangkesi001

  4. # 发送自己代码给我们赢取价值2000元夏令营

  5.

  6. import random, pygame, sys

  7. from pygame.locals import *

  8.

  9. FPS = 15

 10. WINDOWWIDTH = 640

 11. WINDOWHEIGHT = 480

 12. CELLSIZE = 20

 13. assert WINDOWWIDTH % CELLSIZE == 0, ''Window width must be a multiple of cell size.''

 14. assert WINDOWHEIGHT % CELLSIZE == 0, ''Window height must be a multiple of cell size.''

 15. CELLWIDTH = int(WINDOWWIDTH / CELLSIZE)

 16. CELLHEIGHT = int(WINDOWHEIGHT / CELLSIZE)


上面的代码设定了游戏的常量(constant variables),这些量在游戏进行的过程中将不会被改变。小方格的边长被储存在变量CELLSIZE中。assert语句(第13,14行)确保小方格的尺寸能够和游戏窗口完美契合。例如,如果变量CELLSIZE为10,而游戏窗口的宽WINDOWWIDTH和高WINDOWHEIGHT都被设置为15,那么整个游戏窗口只能放进1.5个小方格。assert语句确保窗口中的小方格数量为整数。

 18. #           R红  G绿  B蓝

 19. WHITE     = (255, 255, 255)

 20. BLACK     = (  0,   0,   0)

 21. RED       = (255,   0,   0)

 22. GREEN     = (  0, 255,   0)

 23. DARKGREEN = (  0, 155,   0)

 24. DARKGRAY  = ( 40,  40,  40)

 25. BGCOLOR = BLACK

 26.

 27. UP = ''up''

 28. DOWN = ''down''

 29. LEFT = ''left''

 30. RIGHT = ''right''

 31.

 32. HEAD = 0 #小蛇头部的索引(index)


  main函数  

 34. def main():

 35.     global FPSCLOCK, DISPLAYSURF, BASICFONT

 36.

 37.     pygame.init()

 38.     FPSCLOCK = pygame.time.Clock()

 39.     DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

 40.     BASICFONT = pygame.font.Font(''freesansbold.ttf'', 18)

 41.     pygame.display.set_caption(''Wormy'')

 42.

 43.     showStartScreen()

 44.     while True:

 45.         runGame()

 46.         showGameOverScreen()

在贪吃蛇游戏程序中,我们把代码的主要部分放在一个叫做runGame的函数中。这是因为我们只想要展示一次游戏的开始界面(一段带有不断旋转的“Wormy”字样的动画),用showStartScreen函数实现。接着我们将调用runGame函数来正式开始贪吃蛇游戏。这个函数将在玩家的小蛇超出窗口边缘或者撞到自身的时候返回(return)(即函数结束)。

 

游戏结束时,我们需要调用showGameOverScreen来展示游戏的结束界面。当这个函数返回的时候,while循环重新进行,runGame函数再次被调用,游戏重新开始。第44行的while循环将永远进行下去,直到程序被终止。


  独立的runGame函数  


 49. def runGame():

 50.     # 为小蛇设置一个随机的出发点

 51.     startx = random.randint(5, CELLWIDTH - 6)

 52.     starty = random.randint(5, CELLHEIGHT - 6)

 53.     wormCoords = [{''x'': startx,     ''y'': starty},

 54.                   {''x'': startx - 1, ''y'': starty},

 55.                   {''x'': startx - 2, ''y'': starty}]

 56.     direction = RIGHT

 57.

 58.     # 把苹果放在一个随机的位置

 59.     apple = getRandomLocation()

在游戏的开始,我们希望小蛇能够在一个随机的位置出现(但不要离窗口的边缘太近)。所以我们需要在变量startxstarty中分别储存一个随机的坐标值。(注意:CELLWIDTHCELLHEIGHT是窗口横向和竖向上小方格的数量,而不是小方格自己的宽度和高度)。

 

在上面这段代码中,我们确定了游戏一开始时小蛇的长度和小蛇身体各部分的位置。小蛇的身体以字典(dictionary)的形式被储存起来。其中,头部坐标由变量startxstarty确定,剩下的两端则被放在头部左侧的两个方格中。小蛇每一段身体的横纵坐标被储存在字典的x、y键值(key)中。所有代表小蛇身体的字典被储存在名为wormCoords的列表(list)中。

 

小蛇的头部永远都是wormCoords列表的第一个值wormCoords[0]。为了让代码更具可读性,我们在代码的第32行设置了一个常量HEAD,它的值为0,这样以来,我们就可以用wormCoords[HEAD]来代替wormCoords[0]


  事件处理循环  

 61.     while True: # 游戏主循环

 62.         for event in pygame.event.get(): # 事件处理循环

 63.             if event.type == QUIT:

 64.                 terminate()

 65.             elif event.type == KEYDOWN:

 66.                 if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:

 67.                     direction = LEFT

 68.                 elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:

 69.                     direction = RIGHT

 70.                 elif (event.key == K_UP or event.key == K_w) and direction != DOWN:

 71.                     direction = UP

 72.                 elif (event.key == K_DOWN or event.key == K_s) and direction != UP:

 73.                     direction = DOWN

 74.                 elif event.key == K_ESCAPE:

 75.                     terminate()

从第61行开始,我们进入了游戏的主循环。第62行的for循环用于进行事件(event)处理。如果事件(event)是QUIT事件,我们将调用terminate()函数 。

 

如果时间不是QUIT,而是按下键盘(KEYDOWN)的话,我们就检测按下的的键是不是WASD中的某一个。除此之外,我们还需要一个额外的检测, 确保小蛇不会转到和当前行进方向相反的方向,因为这样它就会立马撞到自己啦!例如,如果小蛇在向左行进,而玩家按到了向右的按键,在我们代码的控制下,小蛇是不会做出反应的!


  撞击检测  

77.         # 检测小蛇是否撞到自己或者超出窗口边缘

78.         if wormCoords[HEAD][''x''] == -1 or wormCoords[HEAD][''x''] == CELLWIDTH or wormCoords[HEAD][''y''] == -1 or wormCoords[HEAD][''y''] == CELLHEIGHT:

79.             return # 游戏结束

80.         for wormBody in wormCoords[1:]:

81.             if wormBody[''x''] == wormCoords[HEAD][''x''] and wormBody[''y''] == wormCoords[HEAD][''y'']:

82.                 return # 游戏结束

这段代码中,我们检测小蛇的头是否超出了游戏窗口的边缘,或者撞到了一个被自己的另一段身体占据的小方格。

 

那么应该如何检测小蛇的头是否超出窗口边缘呢?由于窗口内所有小方格的坐标有一定范围,我们只需要检测小蛇是否超出这个范围就可以了。网格横坐标的范围是0到CEELWIDTH-1,纵坐标的范围是0到CELLHEIGHT-1。因而如果小蛇头部的横坐标为-1(超出窗口左侧)或CELLWIDTH(超出窗口右侧),或者纵坐标为-1(超出窗口上沿)或CELLHEIGHT(超出窗口下沿),小蛇就超出了窗口的范围。

 

这时,我们的代码会让游戏结束,第79行的returnrunGame函数停止并返回到第46行该函数被调用的地方。接着,showGameOverScreen函数被调用,游戏界面上出现了“Game Over”的字样。

 

第80行到82行对储存在wormCoords中小蛇身体进行循环。wormCoords的索引从0开始,而0储存的是小蛇的头部,小蛇的身体部分从索引1开始,所以我们使用wormCoords[1:]从索引1开始循环。如果小蛇头部的横纵坐标x、y和身体的横纵坐标x、y相等,我们的代码就将结束游戏并退出runGame函数,返回到第46行函数被调用的地方,显示游戏结束页面(和上面相似)。


    吃到苹果啦!  

 84.         # 检测小蛇是否吃到苹果

 85.         if wormCoords[HEAD][''x''] == apple[''x''] and wormCoords[HEAD][''y''] == apple[''y'']:

 86.             # 暂时不要移除小蛇的尾部

 87.             apple = getRandomLocation() # 在某处放一个新苹果

 88.         else:

 89.             del wormCoords[-1] # 移除小蛇的尾部

这段代码用于检测小蛇是否吃到了苹果,检测方法和上面一段检测小蛇是否撞到了自身相似:如果小蛇头部的横纵坐标x、y和苹果的横纵坐标x、y相同的话,小蛇就吃到了苹果。如果小蛇吃掉了苹果,我们就在一个新的位置放一个新苹果,这个新位置将由getRandomLocation函数随机产生。

 

如果小蛇没有吃到苹果,我们将小蛇的尾部,即身体的最后一段从wormCoords列表中删去。注意,负数索引值代表从列表的末尾开始数,-1代表列表的最后一项,-2代表倒数第二项。

 

为了不断更新小蛇的位置,我们需要删除小蛇的尾部并在小蛇移动的方向上画一个新的头部,这样小蛇才能不断行进并且在没吃到苹果的时候保持身体长度不变。代码的第89行移除了小蛇的尾部。在下面的“移动小蛇”模块,即代码的91到100行,我们将会在小蛇移动的方向上添加一段身体作为小蛇移动后的头部。


  移动小蛇  

91.         # 在小蛇行进的方向上添加一段身体

92.         if direction == UP:

93.             newHead = {''x'': wormCoords[HEAD][''x''], ''y'': wormCoords[HEAD][''y''] - 1}

94.         elif direction == DOWN:

95.             newHead = {''x'': wormCoords[HEAD][''x''], ''y'': wormCoords[HEAD][''y''] + 1}

96.         elif direction == LEFT:

97.             newHead = {''x'': wormCoords[HEAD][''x''] - 1, ''y'': wormCoords[HEAD][''y'']}

98.         elif direction == RIGHT:

99.             newHead = {''x'': wormCoords[HEAD][''x''] + 1, ''y'': wormCoords[HEAD][''y'']}

100.         wormCoords.insert(0, newHead)

为了移动小蛇,我们要在wormCoords列表的开头给小蛇添加一段新的身体。因为这段身体被添加到了列表开头,所以它将成为小蛇的新头部。新头部的坐标将和旧头部的坐标相邻。我们将根据小蛇移动的方向对横纵坐标加1或者减1。第100行的insert() 能够将新头部添加到列表开头。


  insert()与append()比较  

insert()append()有不同的功能。append()只能在列表末尾添加一项内容,而insert 能把一项或多项内容添加到列表的任何位置。insert括号中的第一个参数代表第一项内容将要插入的位置(用索引值表示)。如果这个值大于原有列表的最大索引值,所有的内容都会被添加到列表的末尾。insert的第二个变量是所要插入的内容。我们可以在解释器(interactive shell)输入以下代码,看看insert()到底是如何运行的:


>>> spam = [''cat'', ''dog'', ''bat'']

>>> spam.insert(0, ''frog'')

>>> spam

[''frog'', ''cat'', ''dog'', ''bat'']

>>> spam.insert(10, 42)

>>> spam

[''frog'', ''cat'', ''dog'', ''bat'', 42]

>>> spam.insert(2, ''horse'')

>>> spam

[''frog'', ''cat'', ''horse'', ''dog'', ''bat'', 42]

>>> 

  绘制屏幕  

101.         DISPLAYSURF.fill(BGCOLOR)

102.        drawGrid()

103.        drawWorm(wormCoords)

104.        drawApple(apple)

105.        drawScore(len(wormCoords) - 3)

106.        pygame.display.update()

107.        FPSCLOCK.tick(FPS)

这段代码相对而言比较简单。第101行用背景颜色BGCOLOR填充整个屏幕。第102到105行画出格子、小蛇、苹果以及要在屏幕上显示的分数。接着,我们调用pygame.display.update()函数,从而让游戏界面成功地显示在电脑屏幕上。

  在屏幕上显示文字  

109. def drawPressKeyMsg():

110.     pressKeySurf = BASICFONT.render(''Press a key to play.'', True, DARKGRAY)

111.     pressKeyRect = pressKeySurf.get_rect()

112.     pressKeyRect.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT - 30)

113.     DISPLAYSURF.blit(pressKeySurf, pressKeyRect)

当显示游戏的开始动画和结束界面的时候,在游戏窗口的右下角会有一行小字显示“Press a key to play”(按下任意键开始游戏)。上面的函数drawPressKeyMsg()就是为了在屏幕上显示这行字。有了这个函数后,我们只需要分别在showStartScreen()showGameOverScreen()函数中分别调用drawPressKeyMsg函数即可。


  checkForKeyPress()函数  

116. def checkForKeyPress():

117.     if len(pygame.event.get(QUIT)) > 0:

118.         terminate()

119.

120.     keyUpEvents = pygame.event.get(KEYUP)

121.     if len(keyUpEvents) == 0:

122.         return None

123.     if keyUpEvents[0].key == K_ESCAPE:

124.         terminate()

125.     return keyUpEvents[0].key

这段函数首先检测在事件队列中是否存在QUIT事件。通过在第117行调用pygame.event.get()函数,我们将得到事件列表中的所有QUIT事件(因为我们把QUIT作为了函数参数)。如果事件队列中没有QUIT事件,pygame.event.get()将返回一个空列表:[]。

 

如果pygame.event.get()返回一个空列表,第117行len()函数的返回值将为0。如果pygame.event.get()返回的不是空列表,len()函数的返回值将大于0,程序将会调用第118行的terminate()函数使得程序终止。

 

接着pygame.event.get()得到一个存有所有KEYUP(松开按键)事件的列表。如果玩家松开的是esc键,那么程序也会终止。否则,pygame.event.get()返回的的第一个键也将被checkForKeyPress()函数返回。


  开始界面  

128. def showStartScreen():

129.     titleFont = pygame.font.Font(''freesansbold.ttf'', 100)

130.     titleSurf1 = titleFont.render(''Wormy!'', True, WHITE, DARKGREEN)

131.     titleSurf2 = titleFont.render(''Wormy!'', True, GREEN)

132.

133.     degrees1 = 0

134.     degrees2 = 0

135.     while True:

136.         DISPLAYSURF.fill(BGCOLOR)

当贪吃蛇游戏开始运行的时候,玩家并不会自动开始游戏。在玩家真正开始游戏之前,会出现一个开始界面告诉玩家他们正在运行什么游戏。开始界面也让玩家做好开始游戏的准备,否则,在他们还没准备好的时候小蛇可能就要撞死了!

 

代码的第130行和131行创建了两个Surf对象(object)titleSurf1titleSurf2,两个Surf内分别是两个不同颜色的“Wormy!”文本框。第129行的Font()函数将字体大小设定为100。第一个“Wormy!”文本框由白色文字和绿色背景组成,第二个由绿色文字和透明(transparent)背景组成。第135行的代码让开始动画循环播放。


    让文字转起来!  

137.         rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1)

138.         rotatedRect1 = rotatedSurf1.get_rect()

139.         rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)

140.         DISPLAYSURF.blit(rotatedSurf1, rotatedRect1)

141.

142.         rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2)

143.         rotatedRect2 = rotatedSurf2.get_rect()

144.         rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)

145.         DISPLAYSURF.blit(rotatedSurf2, rotatedRect2)

146.

147.         drawPressKeyMsg()

148.

149.         if checkForKeyPress():

150.             pygame.event.get() # 清除事件列表151.             return

152.         pygame.display.update()

153.         FPSCLOCK.tick(FPS)

在上一段代码中,我们设定了文字的颜色和大小,现在我们要让文字转起来!

Pygame.transform.rotate()将会让写有“Wormy!”字样的Surf旋转起来。括号内有两个参数,第一个是将被旋转的Surf对象,第二个是需要旋转的角度。Pygame.tramsform.rotate()函数不会改变你传递(pass)的Surf参数本身,而是返回一个新的Surf对象,并把旋转过的文本框画在上面。

 

需要注意的是,新的Surf对象可能会比原来的大,因为最初的Surf对象只需刚好能在装下文本框,新的Surf对象则需装下旋转后的文本框。如下图, 黑色矩形代表原Surf对象/文本框,灰色矩形代表新的Surf对象。

旋转的程度是用角度制来表示的。旋转一圈是360度, 不旋转是0度。旋转1/4圈是90度。顺时针旋转用负数表示。由于每旋转360度就又能得到一个一样的图像,每当得到一个大于360的角度,pygame.transform.rotate()就会减去360的倍数,直到旋转角度小于360,如下图:

我们在第140行和第145行使用blit将旋转后的“Wormy!”显示在屏幕上。

 

第147行的drawPressKeyMsg()函数在屏幕右下角显示“Press a key to play”字样。这段动画会不断循环播放,直到玩家按下任意键,这时checkForKeyPress()函数将返回一个不为None的值。在checkForKeyPress()函数返回前,程序调用pygame.event.get()函数来清除无用的事件队列。


  不完美的旋转  

你可能会想,我们为什么要把旋转过的Surf储存在新的变量rotatedSurf1rotatedSurf2中,而不是直接更新原有的变量titleSurf1titleSurf2呢?

 

有两个原因:

首先,我们往往不能够完美地旋转2D图像,我们只能做近似的旋转。如果我们把图像逆时针旋转10度再顺时针旋转10度,我们得到的图像并不能和一开始的完全重合。这就好比复印,如果我们不断对新的复印件进行复印,图像的质量会越来越差。

(这种不完美性只有一个意外:当你将图像旋转90度的倍数,比如90,180,270,360度等,我们得到的图像将是准确的)。

 

第二,经过旋转的2D图像将会比原来的图像稍大,对旋转过的图像再次进行旋转,旋转过的图像又会更大一些。如果你不断进行旋转,最后图像会变得非常大,以至于PyGame无法处理,然后你的程序就会崩溃,并提示:pygame.error: Width or height is too large.(pygame错误:宽度或高度过大)。

154.         degrees1 += 3 # 每帧旋转3度

155.         degrees2 += 7 # 每帧旋转7度

我们旋转两个“Wormy!”的角度被储存在degrees1degrees2中。循环每进行一次,我们就让degrees1增加3,让degress2增加7。也就是每一次循环,让一个文本框旋转3度,另一个文本框旋转7度。这就是为什么一个文本框旋转得比另一个要慢。

158. def terminate():

159.     pygame.quit()

160.     sys.exit()

terminate()函数调用了pygame.quit()sys.exit(),因而游戏能够无误关闭。这里的terminate()函数就是之前提到的terminate()函数。


  确定苹果出现的位置  

163. def getRandomLocation():

164.     return {''x'': random.randint(0, CELLWIDTH - 1), ''y'': random.randint(0, CELLHEIGHT - 1)}

当我们需要一个新苹果出现,我们就需要调用getRandomLocation()函数。这个函数将返回一个存有横纵坐标x、y的字典,x、y的值是由random.randint随机产生的。

  游戏结束界面  

167. def showGameOverScreen():

168.     gameOverFont = pygame.font.Font(''freesansbold.ttf'', 150)

169.     gameSurf = gameOverFont.render(''Game'', True, WHITE)

170.     overSurf = gameOverFont.render(''Over'', True, WHITE)

171.     gameRect = gameSurf.get_rect()

172.     overRect = overSurf.get_rect()

173.     gameRect.midtop = (WINDOWWIDTH / 2, 10)

174.     overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25)

175.

176.     DISPLAYSURF.blit(gameSurf, gameRect)

177.     DISPLAYSURF.blit(overSurf, overRect)

178.     drawPressKeyMsg()

179.     pygame.display.update()

游戏结束界面和游戏开始界面相似, 只不过游戏结束界面不是动画。我们用两个Surf对象分别放置“Game”和“Over”两个单词。

180.     pygame.time.wait(500)

181.     checkForKeyPress() # 清除事件队列中按下键盘度事件

182.

183.     while True:

184.         if checkForKeyPress():

185.             pygame.event.get() # 清除事件队列

186.             return

“Game Over”会一直显示在屏幕上,直到玩家按下任意键。为了防止玩家太早(很可能是不小心)按下键盘,我们在第180行使用pygame.time.wait()添加半秒的等待时间。在这半秒内,按下键盘也不会退出游戏结束界面从而重新开始游戏。(括号内的参数500代表500毫秒,即0.5秒)。这是为了类似这样的情况发生:比如玩家在最后关头试着避开屏幕的边缘,但是由于按键太晚导致小蛇撞死。如果是这样的话,玩家很可能是在showGameOverScreen()被调用后才按下的键,而这时按下的键会导致退出游戏界面并且重新开始游戏。


  Drawing函数  

下面的几段代码用于在屏幕上绘制分数,小蛇,苹果,和网格线。

188. def drawScore(score):

189.     scoreSurf = BASICFONT.render(''Score: %s'' % (score), True, WHITE)

190.     scoreRect = scoreSurf.get_rect()

191.     scoreRect.topleft = (WINDOWWIDTH - 120, 10)

192.     DISPLAYSURF.blit(scoreSurf, scoreRect)

drawScore()函数设置了字体格式并将用参数score传递的文字内容显示在屏幕上。

195. def drawWorm(wormCoords):

196.     for coord in wormCoords:

197.         x = coord[''x''] * CELLSIZE

198.         y = coord[''y''] * CELLSIZE

199.         wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)

200.         pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect)

201.         wormInnerSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8)

202.         pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)

drawWorm()函数给小蛇的每段身体绘制一个绿色的小方块(用绿色填充游戏屏幕上的一个网格)。每段身体都由wormCoords参数来传递(wormCoords是一个包含各段身体的坐标值的字典变量)。第196行的for循环对字典中的每个值进行循环。

 

由于网格的坐标占据了整个窗口并且是从(0,0)像素点开始的,我们很容易将网格坐标转化为像素坐标。这是通过第197行和第198行的乘式来实现的。

 

第199行创建了一个Rect对象,用于把小蛇的某段身体参数传递到第200行的pygame.draw.rect()中。由于所有网格小方格的边长都为CELLSIZE,小蛇的身体片段的边长也应为CELLSIZE。第200行通过将一个小方格填充为深绿色来绘制小蛇的身体片段。然后在深绿色小方格上面,我们叠加一个小一些的亮绿色方格,这让小蛇变得更好看一些。

 

亮绿色方格的横坐标比网格小方格要多4个像素(往右4个像素),纵坐标也多4个像素(向下4个像素),它的边长比网格小方格要小8个像素,因此亮绿色方格的下部和右部和网格也有4像素的距离。

205. def drawApple(coord):

206.     x = coord[''x''] * CELLSIZE

207.     y = coord[''y''] * CELLSIZE

208.     appleRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)

209.     pygame.draw.rect(DISPLAYSURF, RED, appleRect)

drawApple()函数和drawWorm()函数非常相似,只不过画红苹果时我们仅仅填充了了一个红色的方格,我们所要做的只是在206行和207行把坐标转换为像素坐标,在208行创建一个Rect对象来储存苹果的位置和大小,然后把这个Rect对象传递给pygame.draw.rect()函数。

212. def drawGrid():

213.     for x in range(0, WINDOWWIDTH, CELLSIZE): # 画竖线

214.         pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT))

215.     for y in range(0, WINDOWHEIGHT, CELLSIZE): # 画横线

216.         pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y))

为了让网格更明显,我们调用pygame.draw.line()函数来画出网格的横竖线。

 

通常,要画32条竖线,我们需要调用32次pygame.draw.line()函数:

pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, 0), (0, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (20, 0), (20, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (40, 0), (40, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (60, 0), (60, WINDOWHEIGHT))

......

pygame.draw.line(DISPLAYSURF, DARKGRAY, (560, 0), (560, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (580, 0), (580, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (600, 0), (600, WINDOWHEIGHT))

pygame.draw.line(DISPLAYSURF, DARKGRAY, (620, 0), (620, WINDOWHEIGHT))

为了简便,我们可以使用第213行到第214行的for循环来代替以上这些代码。很多其他规律的图形也可以用循环来完成,这样我们就不需要打一大堆相似的代码。

219. if __name__ == ''__main__'':

220.     main()

在所有需要的常数、函数、和全局变量都被定义和创建后,我们调用main()函数来开始游戏。


  不要重复使用变量名  

让我们再回顾一下drawWorm()函数中的几行代码:

199.         wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)

200.         pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect)

201.         wormInnerSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8)

202.         pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)

可以注意到,我们在第199行和201行创建了两个不同的Rect对象。第199行的Rect对象被储存在wormSegmentRect这一局部变量中,并被传递到了第200行的pygame.draw.rect()函数中。第201行对Rect对象被储存在wormInnerSegmentRect局部变量中,并被传递到了第202行的pygame.draw.rect()函数中。

 

每一次我们创建一个新变量,它都会占据电脑的一点记忆空间。你可能觉得重复使用wormSetmentRect变量是更为经济实用的选择,像这样:

199.         wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)

200.         pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect)

201.         wormSegmentRect = pygame.Rect(x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8)

202.         pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)

你也许会认为:因为第199行中由pygame.Rect()返回的Rect对象在第200行之后不会再被用到,我们可以覆盖它原来的值,并重新利用这个变量来储存第201行中由pygame.Rect()返回的Rect对象。这样我们就能节约一些电脑记忆空间了。

 

虽然这理论上是对的,但实际上重复使用变量节约的空间不过几字节。如今电脑都有上亿字节的记忆空间,所以我们节约下来的空间其实并没有多少。而且,重复使用变量降低了程序的可读性。如果一个程序员读了上面的代码,他会看到wormSegmentRect被传递给了第200行和202行的pygame.draw.rect()函数。他大概也会看到第199行的pygame.Rect()函数首次给wormSegmentRect变量赋了值。但他可能不会发现第199行由pygame.Rect()返回的Rect对象和第202行传递给pygame.draw.rect()的不是一回事。

 

这些小细节会使得你程序的可读性降低。不只是读你程序的程序员会感到疑惑,几个星期以后,当你自己回头看自己写的程序时,你很可能也不记得当初这个程序到底是怎么运行的了。我们要记住的是,程序的可读性比节约几个字节要重要得多。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Python实现“智能”贪吃蛇
Pygame教程(非常详细)
draw2d里面的一函数 translateToAbsolute(rect)
用pygame开发自己的游戏-2.让方块动起来
飞机大战编程
pygame菜鸟入门指南
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服