從現在開始,就是具體遊戲的製作了。作者是每章一個遊戲,有些遊戲我不是高度興趣,只對其中有興趣,所以就只講這一些。
第一個遊戲就是貪吃蛇遊戲,說起這個遊戲,這可能是我玩的最早的遊戲之一了,記得那時彩屏手機沒有出來時,所有單色手機上面幾乎都有這個遊戲,簡直風靡一時啊。以前在單片機的液晶屏上實現過貪吃蛇,不過太簡陋了。
看完講貪吃蛇遊戲這章,越來越感覺到python有意思了,字典這個資料結構的應用讓整個程式一下子簡單了很多。而且作者寫的很仔細,整個程式設計的思路通過代碼就能一目瞭然,關於旋轉映像的壞處也有說明。而且在這章最後關於變數是否要複用這點也給了看法,很好很強大。每段代碼為什麼這樣寫,也解釋的很清楚。而且它的代碼風格值得我學習。
下面是貪吃蛇程式的代碼,把代碼敲一遍加深理解。
import pygame, sys, randomfrom pygame.locals import*FPS = 15WINDOWWIDTH =640WINDOWHEIGHT = 480CELLSIZE = 20assert 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 / CELLSIZECELLHEIGHT = WINDOWHEIGHT / CELLSIZE#RGBWHITE = (255, 255, 255)BLACK = (0, 0 , 0)RED = (255, 0, 0)GREEN = (0, 255, 0)DARKGREEN = (0, 155, 0)DARKGRAY = (40, 40, 40)BGCOLOR = BLACKUP = 'up'DOWN = 'down'LEFT = 'left'RIGHT = 'right'HEAD = 0 #很巧妙的運用#main functiondef main():global FPSCLOCK, DISPLAYSURF, BASICFONTpygame.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 = LEFTelif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:direction = RIGHTelif (event.key == K_UP or event.key == K_w) and direction != DOWN:direction = UPelif (event.key == K_DOWN or event.key == K_s) and direction != UP:direction = DOWNelif 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 overfor 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 Noneif 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()returndef 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'] * CELLSIZEy = coord['y'] * CELLSIZEwormSegmentRect = 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'] * CELLSIZEy = coord['y'] * CELLSIZEappleRect = 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 = LEFTelif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:direction = RIGHTelif (event.key == K_UP or event.key == K_w) and direction != DOWN:direction = UPelif (event.key == K_DOWN or event.key == K_s) and direction != UP:direction = DOWNelif 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 overfor 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。