Make Games with Python & Pygame (4)

從如今開始,就是具體遊戲的製做了。做者是每章一個遊戲,有些遊戲我不是很感興趣,只對其中有興趣,因此就只講這一些。python

第一個遊戲就是貪吃蛇遊戲,提及這個遊戲,這多是我玩的最先的遊戲之一了,記得那時彩屏手機沒有出來時,全部單色手機上面幾乎都有這個遊戲,簡直風靡一時啊。之前在單片機的液晶屏上實現過貪吃蛇,不過太簡陋了。編程

看完講貪吃蛇遊戲這章,愈來愈感受到python有意思了,字典這個數據結構的應用讓整個程序一會兒簡單了不少。並且做者寫的很仔細,整個程序設計的思路經過代碼就能一目瞭然,關於旋轉圖像的壞處也有說明。並且在這章最後關於變量是否要複用這點也給了見解,很好很強大。每段代碼爲何這樣寫,也解釋的很清楚。並且它的代碼風格值得我學習。數據結構

下面是貪吃蛇程序的代碼,把代碼敲一遍加深理解。app

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()

而後就來講說上面的這段代碼。先說明下整個程序流程dom

一個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。

相關文章
相關標籤/搜索