Wormy 是一个克隆人。玩家开始控制一个不断在屏幕上移动的小虫子。玩家不能停止或减慢蠕虫的速度,但他们可以控制它转向哪个方向。一个红色的苹果随机出现在屏幕上,玩家必须移动虫子,让它吃掉苹果。蠕虫每吃一个苹果,蠕虫就长一段,一个新的应用随机出现在屏幕上。如果蠕虫撞到自己或屏幕边缘,游戏就结束了。
这个源代码可以从【http://invpy.com/wormy.py】下载。如果您得到任何错误消息,请查看错误消息中提到的行号,并检查您的代码是否有任何拼写错误。你也可以在 http://invpy.com/diff/wormy将你的代码复制粘贴到 web 表单中,看看你的代码和书中的代码是否有区别。
2\. # By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
3\. # http://inventwithpython.com/pygame
4\. # Creative Commons BY-NC-SA 3.0 US
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)
17\.
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 # syntactic sugar: index of the worm's head
33\.
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()
47\.
48\.
49\. def
runGame():
50\. # Set a random start point.
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\. # Start the apple in a random place.
59\. apple
= getRandomLocation()
60\.
61\. while
True: # main game loop
62\. for
event in pygame.event.get(): # event handling
loop
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()
76\.
77\. # check if the worm has hit itself or the edge
78\. if
wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or
wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT:
79\. return
# game over
80\. for
wormBody in wormCoords[1:]:
81\. if
wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] ==
wormCoords[HEAD]['y']:
82\. return
# game over
83\.
84\. # check if worm has eaten an apply
85\. if
wormCoords[HEAD]['x'] == apple['x'] and wormCoords[HEAD]['y'] == apple['y']:
86\. # don't remove worm's tail segment
87\. apple
= getRandomLocation() # set a new apple somewhere
88\. else:
89\. del
wormCoords[-1] # remove worm's tail segment
90\.
91\. # move the worm by adding a segment in the direction it
is moving
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)
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)
108\.
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)
114\.
115.
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
126.
127.
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)
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()
# clear event queue
151.
return
152\. pygame.display.update()
153\. FPSCLOCK.tick(FPS)
154\. degrees1
+= 3 # rotate by 3 degrees each frame
155\. degrees2
+= 7 # rotate by 7 degrees each frame
156\.
157\.
158\. def
terminate():
159\. pygame.quit()
160\. sys.exit()
161\.
162\.
163\. def
getRandomLocation():
164\. return
{'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0, CELLHEIGHT - 1)}
165\.
166\.
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()
180\. pygame.time.wait(500)
181\. checkForKeyPress()
# clear out any key presses in the event queue
182\.
183\. while
True:
184\. if
checkForKeyPress():
185\. pygame.event.get()
# clear event queue
186.
return
187\.
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)
193\.
194\.
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)
203\.
204\.
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)
210\.
211\.
212\. def
drawGrid():
213\. for
x in range(0, WINDOWWIDTH, CELLSIZE): # draw
vertical lines
214\. pygame.draw.line(DISPLAYSURF,
DARKGRAY, (x, 0), (x, WINDOWHEIGHT))
215\. for
y in range(0, WINDOWHEIGHT, CELLSIZE): # draw
horizontal lines
216\. pygame.draw.line(DISPLAYSURF,
DARKGRAY, (0, y), (WINDOWWIDTH, y))
217.
218.
219\. if
__name__ == '__main__':
220\. main()如果你稍微玩一下这个游戏,你会注意到苹果和蠕虫身体的部分总是沿着一个网格线排列。我们将把这个网格中的每个方块称为一个单元(网格中的一个空间并不总是被这样称呼,它只是我想出来的一个名字)。单元格有自己的笛卡尔坐标系,其中(0,0)是左上角的单元格,( 31,23)是右下角的单元格。
1\. # Wormy (a Nibbles clone)
2\. # By Al Sweigart [[email protected]](/cdn-cgi/l/email-protection)
3\. # http://inventwithpython.com/pygame
4\. # Creative Commons BY-NC-SA 3.0 US
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)程序开头的代码只是设置了一些游戏中使用的常量变量。单元格的宽度和高度存储在CELLSIZE中。第 13 行和第 14 行的assert语句确保单元格完全适合窗口。例如,如果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 # syntactic sugar: index of the worm's head第 19 到 32 行设置了更多的常量。常数HEAD将在本章后面解释。
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()的函数中。这是因为我们只想在程序启动时(通过调用showStartScreen()函数)显示一次“开始屏幕”(带有旋转“虫状”文本的动画)。然后我们要调用runGame(),这将开始一个虫虫游戏。当玩家的蠕虫撞到墙壁或自身并导致游戏结束时,该函数将返回。
此时,我们将通过调用showGameOverScreen()在屏幕上显示游戏。当该函数调用返回时,循环再次回到起点calls runGame()。第 44 行的while循环将永远循环下去,直到程序终止。
49\. def
runGame():
50\. # Set a random start point.
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\. # Start the apple in a random place.
59\. apple
= getRandomLocation()在游戏开始时,我们希望蠕虫在一个随机的位置开始(但不要太靠近棋盘的边缘),所以我们在startx和starty中存储一个随机坐标。(请记住,CELLWIDTH和CELLHEIGHT是窗口宽度和高度的单元格数量,而不是宽度和高度的像素数量)。
蠕虫的主体将存储在字典值列表中。蠕虫的每个身体部分都有一个字典值。字典将有键'x'和'y'用于该身体部分的 XY 坐标。身体的头部位于startx和starty。另外两个身体部分将是头部左侧的一个和两个细胞。
在wormCoords[0]时,蠕虫的头部将始终是身体的一部分。为了让这段代码更具可读性,我们在第 32 行将HEAD常量设置为0,这样我们就可以用wormCoords[HEAD]代替wormCoords[0]。
61\. while
True: # main game loop
62\. for
event in pygame.event.get(): # event handling
loop
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 是事件处理循环的开始。如果事件是一个QUIT事件,那么我们调用terminate()(我们在之前的游戏程序中定义了与terminate()函数相同的函数)。
否则,如果事件是一个KEYDOWN事件,那么我们检查被按下的键是箭头键还是 WASD 键。我们需要额外的检查,这样蠕虫就不会自己打开。例如,如果蠕虫向左移动,那么如果玩家不小心按下了右箭头键,蠕虫将立即开始向右移动并撞向自己。
这就是为什么我们要检查变量direction的当前值。这样,如果玩家不小心按下了一个箭头键,将导致他们立即崩溃的蠕虫,我们只需忽略该按键。
77\. # check if the worm has hit itself or the edge
78\. if
wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or
wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT:
79\. return
# game over
80\. for
wormBody in wormCoords[1:]:
81\. if
wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] ==
wormCoords[HEAD]['y']:
82\. return
# game over当头部移出栅格边缘时,或者当头部移动到已经被另一个身体分段占据的单元上时,蠕虫崩溃。
我们可以通过查看头部的 X 坐标(存储在wormCoords[HEAD]['x']中)是-1(超过了网格的左边缘)还是等于CELLWIDTH(超过了右边缘,因为最右边的 X 单元坐标比CELLWIDTH小 1),来检查头部是否已经离开了网格的边缘。
如果头部的 Y 坐标(存储在wormCoords[HEAD]['y']中)是-1(超过上边缘)或CELLHEIGHT(超过下边缘),头部也已经离开网格。
我们要结束当前游戏所要做的就是从runGame()中返回。当runGame()返回到main()中的函数调用时,runGame()调用之后的下一行(第 46 行)是对showGameOverScreen()的调用,这使得大的“游戏结束”文本出现。这就是为什么我们在第 79 行有了return语句。
线 80 循环穿过头部(在索引0处)之后的wormCoords中的每个身体段。这就是为什么for循环迭代wormCoords[1:],而不仅仅是wormCoords。如果身体部分的'x'和'y'值与头部的'x'和'y'值相同,那么我们也通过退出runGame()函数来结束游戏。
84\. # check if worm has eaten an apply
85\. if
wormCoords[HEAD]['x'] == apple['x'] and wormCoords[HEAD]['y'] == apple['y']:
86\. # don't remove worm's tail segment
87\. apple
= getRandomLocation() # set a new apple somewhere
88\. else:
89\. del
wormCoords[-1] # remove worm's tail segment我们在蠕虫的头部和苹果的 XY 坐标之间进行类似的碰撞检测。如果它们匹配,我们将苹果的坐标设置为一个随机的新位置(从getRandomLocation()的返回值中获得)。
如果头部没有与苹果相撞,那么我们删除wormCoords列表中的最后一个身体片段。请记住,索引的负整数从列表的末尾开始计数。因此,0是列表中第一项的索引,1是第二项,-1是列表中最后一项,-2是倒数第二项。
第 91 到 100 行的代码(将在“移动蠕虫”一节中描述)将在蠕虫前进的方向上添加一个新的身体段(头部)。这将使蠕虫长一段。由于蠕虫在吃苹果时没有删除最后一个身体部分,蠕虫的总长度增加了一个。但是当第 89 行删除最后一个正文段时,大小保持不变,因为紧接着添加了一个新的头段。
91\. # move the worm by adding a segment in the direction it
is moving
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列表的开头添加了一个新的身体片段。因为 body 段被添加到列表的开头,所以它将成为新的头部。新头部的坐标将紧挨着旧头部的坐标。是否从 X 或 Y 坐标中增加或减去1取决于蠕虫前进的方向。
用第 100 行的insert()列表方法将这个新的头段添加到wormCoords。
与只能将项目添加到列表末尾的append() list 方法不同,insert() list 方法可以将项目添加到列表中的任何位置。insert()的第一个参数是项目应该去的索引(所有最初在这个索引上的项目和之后的项目的索引都增加 1)。如果为第一个参数传递的参数大于列表的长度,那么该项就被添加到列表的末尾(就像append()所做的那样)。insert()的第二个参数是要添加的项目值。在交互式 shell 中键入以下内容,看看insert()是如何工作的:
spam = ['猫','狗','蝙蝠']
spam.insert(0,'青蛙')
垃圾邮件
['青蛙','猫','狗','蝙蝠']
spam.insert(10,42)
垃圾邮件
['青蛙','猫','狗','蝙蝠',42]
spam.insert(2,'马')
垃圾邮件
['青蛙','猫','马','狗','蝙蝠',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)在runGame()函数中绘制屏幕的代码相当简单。第 101 行用背景色填充整个显示表面。第 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)当开始屏幕动画正在播放或游戏结束屏幕正在显示时,右下角会有一些小文本,上面写着“按一个键开始游戏”我们没有在showStartScreen()和showGameOverScreen()中输入代码,而是把它放在一个单独的函数中,简单地从showStartScreen()和showGameOverScreen()中调用函数。
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()返回的列表中有多于零个条目(记住,这个列表中的任何条目都将仅仅是QUIT事件,因为我们将QUIT作为参数传递给了pygame.event.get()),那么在第 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)当蠕虫游戏程序第一次开始运行时,玩家不会自动开始玩游戏。相反,会出现一个开始屏幕,告诉玩家他们正在运行什么程序。开始屏幕也给玩家一个准备游戏开始的机会(否则玩家可能没有准备好并在他们的第一个游戏中崩溃)。
蠕虫开始屏幕需要两个表面对象与“蠕虫!”写在上面的文字。这些是render()方法调用在第 130 和 131 行创建的内容。文本会很大:第 129 行的Font()构造函数调用创建了一个字体对象,大小为 100 磅。第一个“虫虫!”文本将有深绿色背景的白色文本,另一个将有透明背景的绿色文本。
第 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()
# clear event queue
151. return
152\. pygame.display.update()
153\. FPSCLOCK.tick(FPS)showStartScreen()功能将旋转表面物体上的图像文字写在上面。第一个参数是要制作旋转副本的曲面对象。第二个参数是旋转表面的度数。pygame.transform.rotate()函数不会改变您传递给它的表面对象,而是返回一个新的表面对象,其上绘制了旋转后的图像。
请注意,这个新曲面对象可能会比原始曲面对象大,因为所有曲面对象都表示矩形区域,并且旋转曲面的角会突出超过原始曲面的宽度和高度。下面的图片有一个黑色的矩形以及轻微旋转的版本。为了使曲面对象能够适合旋转的矩形(下图中为灰色),它必须大于原始黑色矩形的曲面对象:
旋转的角度是以度数给出的,这是旋转的度量。一个圆有 360 度。完全不旋转就是 0 度。逆时针旋转四分之一就是 90 度。要顺时针旋转,请传递一个负整数。旋转 360 度就是把图像旋转一整圈,也就是说你最后得到的图像和你旋转 0 度得到的图像是一样的。事实上,如果你传递给pygame.transform.rotate()的旋转参数是 360 或更大,那么 Pygame 会自动从中减去 360,直到得到一个小于 360 的数。此图显示了不同旋转量的几个示例:
两人轮换着“虫!”在第 140 和 145 行的动画循环的每一帧上,表面对象被位块传送到显示表面。
在第 147 行上,drawPressKeyMsg()函数调用画出了“按键播放”显示表面对象下角的文本。这个动画循环将一直循环,直到checkForKeyPress()返回一个不是None的值,如果玩家按下一个键就会发生这种情况。在返回之前,简单地调用pygame.event.get()来清除在开始屏幕显示的事件队列中累积的任何其他事件。
你可能想知道为什么我们将旋转的表面存储在一个单独的变量中,而不是仅仅覆盖titleSurf1和titleSurf2变量。原因有二。
首先,旋转 2D 图像从来都不是完美的。旋转后的图像总是近似的。如果您将图像逆时针旋转 10 度,然后再顺时针旋转 10 度,您得到的图像将与开始时的图像不完全相同。把它想象成制作一个影印件,然后是第一个影印件的影印件,以及那个影印件的另一个影印件。如果你一直这样做,随着轻微失真的增加,图像会变得越来越差。
(唯一的例外是将图像旋转 90 度的倍数,如 0 度、90 度、180 度、270 度或 360 度。在这种情况下,像素可以无任何失真地旋转。)
第二,如果你旋转 2D 图像,那么旋转后的图像会比原始图像稍大。如果您旋转了旋转后的图像,那么下一个旋转后的图像将再次稍微变大。如果您继续这样做,最终图像会变得太大,Pygame 无法处理,您的程序会崩溃,并显示错误消息pygame.error: Width or height is too large。
154\. degrees1
+= 3 # rotate by 3 degrees each frame
155\. degrees2
+= 7 # rotate by 7 degrees each frame我们转动两个“虫”的数量文本表面对象存储在degrees1和degrees2中。在动画循环的每次迭代中,我们将存储在degrees1中的数字增加3,将degrees2中的数字增加7。这意味着在动画循环的下一次迭代中,白色文本“虫虫!”表面物体将再旋转 3 度,绿色文字“虫蛀!”曲面对象将再旋转 7 度。这就是为什么一个表面物体比另一个旋转得慢。
158\. def
terminate():
159\. pygame.quit()
160\. sys.exit()terminate()函数调用pygame.quit()和sys.exit(),以便游戏正确关闭。与之前游戏程序中的terminate()功能相同。
163\. def
getRandomLocation():
164\. return
{'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0, CELLHEIGHT - 1)}每当需要苹果的新坐标时,就会调用getRandomLocation()函数。该函数返回一个带有键'x'和'y'的字典,其值设置为随机 XY 坐标。
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()屏幕上的游戏类似于开始屏幕,除了它不是动画。单词“游戏”和“结束”被渲染到两个表面对象上,然后这两个表面对象被绘制在屏幕上。
180\. pygame.time.wait(500)
181\. checkForKeyPress()
# clear out any key presses in the event queue
182\.
183\. while
True:
184\. if
checkForKeyPress():
185\. pygame.event.get()
# clear event queue
186. return在玩家按下一个键之前,文本上的游戏将停留在屏幕上。为了确保玩家不会意外地过早按键,我们将在第 180 行调用pygame.time.wait()时暂停半秒钟。(500 参数代表 500 毫秒的暂停,即半秒。)
然后,checkForKeyPress()被调用,以便忽略自showGameOverScreen()功能启动以来发生的任何按键事件。这种暂停和放下关键事件是为了防止以下情况:假设玩家在最后一刻试图从屏幕边缘转开,但按键太晚,撞上了棋盘边缘。如果发生这种情况,那么按键就会发生在showGameOverScreen()被调用之后,并且按键会导致游戏在屏幕上几乎立即消失。下一场比赛将在那之后立即开始,可能会让玩家大吃一惊。添加这种暂停有助于使游戏更加“用户友好”。
绘制分数的代码、worm、apple 和 grid 都放在单独的函数中。
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参数中传递,这是一个字典列表,每个字典都有一个'x'键和一个'y'键。第 196 行的for循环遍历wormCoords中的每个字典值。
因为网格坐标占据了整个窗口,并且从 0,0 像素开始,所以从网格坐标转换到像素坐标相当容易。第 197 和 198 行简单地将coord['x']和coord['y']坐标乘以CELLSIZE。
第 199 行为 worm 段创建了一个 Rect 对象,该对象将被传递给第 200 行的pygame.draw.rect()函数。请记住,网格中的每个单元格的宽度和高度都是CELLSIZE,所以这就是 segment 的 Rect 对象的大小。第 200 行为该线段绘制了一个深绿色的矩形。然后在此之上,绘制一个更小的亮绿色矩形。这使得蠕虫看起来更好一点。
内部亮绿色矩形从单元格左上角的右侧 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 行所做的),用苹果的位置和大小创建 Rect 对象(第 208 行),然后将这个 Rect 对象传递给pygame.draw.rect()函数。
212\. def
drawGrid():
213\. for
x in range(0, WINDOWWIDTH, CELLSIZE): # draw vertical
lines
214\. pygame.draw.line(DISPLAYSURF,
DARKGRAY, (x, 0), (x, WINDOWHEIGHT))
215\. for
y in range(0, WINDOWHEIGHT, CELLSIZE): # draw
horizontal lines
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))
我们可以在一个for循环中有一行代码,而不是打出所有这些代码行。请注意,垂直线的模式是起点和终点的 X 坐标从0开始,向上到620,每次增加20。Y 坐标始终是起点参数的0和终点参数的WINDOWHEIGHT。这意味着for循环应该迭代range(0, 640, 20)。这就是为什么第 213 行的for循环会迭代range(0, WINDOWWIDTH, CELLSIZE)的原因。
对于水平线,坐标必须是:
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,0),(WINDOWWIDTH,0))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,20),(WINDOWWIDTH,20))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,40),(WINDOWWIDTH,40))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,60),(WINDOWWIDTH,60))
...为简洁起见跳过...
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,400),(WINDOWWIDTH,400))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,420),(WINDOWWIDTH,420))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,440),(WINDOWWIDTH,440))
pygame.draw.line(DISPLAYSURF,DARKGRAY,(0,460),(WINDOWWIDTH,460))
Y 坐标范围从0到460,每次增加20。X 坐标始终是起点参数的0和终点参数的WINDOWWIDTH。我们也可以在这里使用一个for循环,这样我们就不必打出所有的pygame.draw.line()调用。
注意到调用所需的规则模式并使用循环是一个聪明的程序员技巧,可以将我们从大量的输入中解救出来。我们可以把所有 56 个电话都打出来,程序也会完全一样地工作。但是如果我们稍微聪明一点,我们可以省去很多工作。
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()函数。
每次创建一个变量,都会占用少量的计算机内存。您可能认为为两个 Rect 对象重用wormSegmentRect变量是聪明的,如下所示:
199。wormsesegment rect = pygame。矩形(x、y、CELLSIZE、CELLSIZE)
200 .pygame . draw . rect(display surf、DARKGREEN、wormsegmentrect)
201。wormsesegment rect = pygame。矩形(x + 4、y + 4、CELLSIZE - 8、CELLSIZE - 8)
202 .pygame . draw . rect(display surf、GREEN、worminnersegmentrect)
因为第 199 行的pygame.Rect()返回的 Rect 对象在 200 之后就不再需要了,所以我们可以覆盖这个值,重用这个变量来存储第 201 行的pygame.Rect()返回的 Rect 对象。因为我们现在使用了更少的变量,所以节省了内存,对吗?
虽然这在技术上是正确的,但您实际上只节省了几个字节。现代计算机有几十亿字节的内存。所以节省并不多。同时,重用变量降低了代码的可读性。如果程序员在这段代码写完之后通读它,他们会看到wormSegmentRect被传递给第 200 和 202 行上的pygame.draw.rect()调用。如果他们试图找到第一次给wormSegmentRect变量赋值的时间,他们会在第 199 行看到pygame.Rect()调用。他们可能没有意识到由第 199 行的pygame.Rect()调用返回的 Rect 对象与传递给第 202 行的pygame.draw.rect()调用的对象不同。
像这样的小事会让你更难理解你的程序到底是如何工作的。不仅仅是其他程序员看着你的代码会感到困惑。当你写完代码几周后再看自己的代码时,你可能很难记起它到底是如何工作的。代码可读性比到处节省几个字节的内存重要得多。
对于额外的编程实践,你可以从【http://invpy.com/buggy/wormy】下载蠕虫病毒的错误版本,并尝试找出如何修复这些错误。




