目錄
> 武裝飛船
> 開始游戲項目
> 創(chuàng)建pygame窗口以及相應(yīng)用戶輸入
?>> 初始化程序
?>> 創(chuàng)建surface對象
?>> 事件監(jiān)聽
?>> 游戲循環(huán)
> 設(shè)置背景色
> 創(chuàng)建設(shè)置類
> 添加飛船圖像
> 創(chuàng)建ship類
>> pygame.image
?>> get_rect( )
?>> surface.blit( )
> 在屏幕上繪制飛船
> 重構(gòu):模塊game_functions
> 函數(shù) check_events( )
> 函數(shù) update_screen()
> 駕駛飛船
> 響應(yīng)按鍵
> 允許不斷移動
> 左右移動
> 調(diào)整飛船的速度
> 限制飛船的活動范圍
> 重構(gòu)check_events()
> 簡單回顧
> alien_invasion.py
> settings.py
> game_functions.py
> ship.py
> 射擊
> 添加子彈設(shè)置 ?????????
> 創(chuàng)建Bullet類
> 將子彈存儲到編組中
> 開火
> 刪除已消失的子彈
> 限制子彈數(shù)量
> 創(chuàng)建函數(shù)update_bullets( )
> 創(chuàng)建函數(shù) fire_bullet( )
> 快捷結(jié)束游戲
> 外星人
> 創(chuàng)建第一個外星人
> 創(chuàng)建Alien類
> 創(chuàng)建Alien實例
> 讓外星人出現(xiàn)在屏幕上
> 創(chuàng)建一群外星人
> 確定一行可容納多少個外星人
> 創(chuàng)建多行外星人
> 創(chuàng)建外星人群
> 重構(gòu)create_fleet( )
> 添加行
>? 讓外星人群移動
> 向右移動外星人
> 創(chuàng)建表示外星人移動方向的設(shè)置
> 檢查外星人是否撞到了屏幕邊緣
> 向下移動外星人群并改變移動方向
> 射殺外星人
> 檢測子彈與外星人的碰撞
?> 為測試創(chuàng)建大子彈
> 生成新的外星人群
> 提高子彈的速度
> 重構(gòu)update_bullets( )
> 結(jié)束游戲
> 檢測外星人和飛船碰撞
> 響應(yīng)外星人和飛船碰撞
> 有外星人到達屏幕底端
> 游戲結(jié)束
> 確定應(yīng)運行游戲的哪些部分
> 記分
> 添加Play按鈕
> 創(chuàng)建Botton類
?> 在屏幕上繪制按鈕
?> 開始游戲
> 重置游戲
> 將Play按鈕切換到非活動狀態(tài)
> 隱藏光標
> 提高等級
> 修改速度設(shè)置
> 重置速度
> 記分
> 顯示得分
> 創(chuàng)建記分牌
> 在外星人被消滅時更新得分
> 將消滅的每個外星人的點數(shù)都計入得分
> 提高點數(shù)
> 將得分圓整
> 最高得分
> 顯示等級
> 顯示余下的飛船數(shù)
> End
> 武裝飛船
> 開始游戲項目
> 創(chuàng)建pygame窗口以及相應(yīng)用戶輸入
首先,我們創(chuàng)建一個空的pygame窗口。
# alien_invasion.py
import sys
import pygame
def run_game():
# 初始化游戲并創(chuàng)建一個屏幕文件
pygame.init()
screen = pygame.display.set_mode((900, 600)) # 指定窗口尺寸
pygame.display.set_caption("Alien Invasion") # 設(shè)置窗口標題
# 開始游戲的主循環(huán)
while True:
# 監(jiān)視鍵盤和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT: # pygame.QUIT 指點擊右上角窗口的"X"號
sys.exit() # 點擊后,卸載所有pygame模塊
# 讓最近繪制的屏幕可見
pygame.display.flip()
run_game() # 初始化游戲并開始主循環(huán)
此時運行程序,會發(fā)現(xiàn)窗口已建立(其默認色為黑):
?>> 初始化程序
在使用 Pygame 編寫程序前,要做的第一個步驟是“初始化程序”,讓Python能正確工作:
pygame.init()
?>> 創(chuàng)建surface對象
在Pygame中,surface是屏幕的一部分,用于顯示游戲元素。
screen = pygame.display.set_mode((1200, 800)) # 指定窗口尺寸
? 對象screen是一個surface。在這個游戲中,每個元素(如外星人、飛船)都是一個surface。display.set_mode() 返回的surface表示整個游戲窗口。
?>> 事件監(jiān)聽
? pygame提供了一個event事件模塊,其包含了常用到的游戲事件。
# 循環(huán)獲取事件,監(jiān)聽事件狀態(tài),使用get()獲取事件
for event in pygame.event.get():
# 判斷事件類型,用戶是否點了"X"關(guān)閉按鈕
# pygame.QUIT 指點擊右上角窗口的"X"號
if event.type == pygame.QUIT:
# 點擊后,卸載所有pygame模塊
pygame.quit()
?下面列出了常用event事件類型和事件方法:
事件類型 | 描述 | 成員屬性 |
---|---|---|
QUIT | 用戶按下窗口的關(guān)閉按鈕 | none |
ATIVEEVENT | Pygame被激活或者隱藏 | gain,state |
KEYDOWN | 鍵盤按下 | unicode、key、mod |
KEYUP | 鍵盤放開 | key、mod |
MOUSEMOTION | 鼠標移動?? | pos, rel, buttons |
MOUSEBUTTONDOWN | 鼠標按下? | pos, button |
MOUSEBUTTONUP | 鼠標放開? | pos, button |
JOYAXISMOTION | 游戲手柄(Joystick or pad) 移動? | joy, axis, value |
JOYBALLMOTION? | 游戲球(Joy ball) 移動?? | joy, axis, value |
JOYHATMOTION | 游戲手柄(Joystick) 移動? ?? | joy, axis, value |
JOYBUTTONDOWN | 游戲手柄按下 | joy, button |
JOYBUTTONUP | 游戲手柄放開? ?? | joy, button |
VIDEORESIZE | Pygame窗口縮放?? | size, w, h |
VIDEOEXPOSE | Pygame窗口部分公開(expose)? | none |
USEREVENT | 觸發(fā)一個用戶事件?? | 事件代碼 |
鍵盤事件常量名 | 描述 |
---|---|
K_BACKSPACE | 退格鍵(Backspace) |
K_TAB | 制表鍵(Tab) |
K_CLEAR | 清除鍵(Clear) |
K_RETURN | 回車鍵(Enter) |
K_PAUSE | 暫停鍵(Pause) |
K_ESCAPE | 退出鍵(Escape) |
K_SPACE | 空格鍵(Space) |
K_0...K_9 | 0...9 |
K_a...Kz | a...z |
K_DELETE | 刪除鍵(delete) |
K_KP0...K_KP9 | 0(小鍵盤)...9(小鍵盤) |
K_F1...K_F15 | F1...F15 |
K_UP | 向上箭頭(up arrow) |
K_DOWN | 向下箭頭(down arrow) |
K_RIGHT | 向右箭頭(right arrow) |
K_LEFT | 向左箭頭(left arrow) |
KMOD_ALT | 同時按下Alt鍵 |
方法 | 說明 |
---|---|
pygame.event.get() | 從事件隊列中獲取一個事件,并從隊列中刪除該事件 |
pygame.event.wait()? | 阻塞直至事件發(fā)生才會繼續(xù)執(zhí)行,若沒有事件發(fā)生將一直處于阻塞狀態(tài) |
pygame.event.set_blocked()? | 控制哪些事件禁止進入隊列,如果參數(shù)值為None,則表示禁止所有事件進入 |
pygame.event.set_allowed()?? | 控制哪些事件允許進入隊列 |
pygame.event.pump()? | 調(diào)用該方法后,Pygame 會自動處理事件隊列 |
pygame.event.poll()? | 會根據(jù)實際情形返回一個真實的事件,或者一個None |
pygame.event.peek()?? | 檢測某類型事件是否在隊列中 |
pygame.event.clear() | 從隊列中清除所有的事件 |
pygame.event.get_blocked()? | 檢測某一類型的事件是否被禁止進入隊列 |
pygame.event.post()?? | 放置一個新的事件到隊列中 |
pygame.event.Event()?? | 創(chuàng)建一個用戶自定義的新事件 |
?>> 游戲循環(huán)
?? 這個游戲由while循環(huán)控制,其中包含一個事件循環(huán)以及管理屏幕更新的代碼。
# 開始游戲的主循環(huán)
while True:
# 監(jiān)視鍵盤和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT: # pygame.QUIT 指點擊右上角窗口的"X"號
sys.exit() # 點擊后,卸載所有pygame模塊
# 讓最近繪制的屏幕可見
pygame.display.flip()
?游戲畫面和操作狀態(tài)會因動畫效果和玩家操作而改變,因此需要以循環(huán)的方式實時更新screen顯示內(nèi)容,下面提供了兩種方法:
pygame.display.flip() # 法1
pygame.display.update() # 法2
- 前者更新整個待顯示的內(nèi)容
- 后者可以根據(jù)選定的區(qū)域來部分更新內(nèi)容(若無提供位置參數(shù),則其作用與前者同)
> 設(shè)置背景色
Pygame默認創(chuàng)建一個黑色屏幕,我們可以將背景設(shè)置為另一種顏色。
# alien_invasion.py
--snip--
def run_game():
--snip--
pygame.display.set_caption("Alien Invasion")
bg_color = (230,230,230) # 設(shè)置背景色
# 開始游戲的主循環(huán)
while True:
--snip--
# 每次循環(huán)時都重繪屏幕
screen.fill(bg_color)
# 讓最近繪制的屏幕可見
pygame.display.flip()
run_game() # 初始化游戲并開始主循環(huán)
在pygame中,顏色是以RGB值指定的。這種顏色由紅色、綠色和藍色值組成,每個值的取值范圍都為0~255。(255,0,0)表示紅色,(0,255,0)表示綠色,(0,0,255)表示藍色。
在本例中,我們創(chuàng)建了一種背景色,并將其存儲在bg_color中。緊接著調(diào)用方法screen.fill(),用背景色填充屏幕。這個方法只接受一個實參:一種顏色。
bg_color = (230,230,230) # 設(shè)置背景色
pygame.Surface.fill(bg_color) # 使用純色填充 Surface 對象
> 創(chuàng)建設(shè)置類
編寫一個settings模塊, 其中包含一個名為Settings的類,用于將所有設(shè)置存儲在一個地方。 這樣,我們就只用傳遞一個設(shè)置對象,要修改游戲,只需修改settings.py中的一些值即可。
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
"""初始化游戲的設(shè)置"""
# 屏幕設(shè)置
self.screen_width = 900
self.screen_height = 600
self.bg_color = (230, 230, 230)
為創(chuàng)建Settings實例并使用它來訪問設(shè)置,將alien_invasion.py修改如下:
# alien_invasion.py
import sys
import pygame
from settings import Settings
def run_game():
# 初始化游戲并創(chuàng)建一個屏幕文件
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height)) # 指定窗口尺寸
pygame.display.set_caption("Alien Invasion") # 設(shè)置窗口標題
# 開始游戲的主循環(huán)
while True:
# 監(jiān)視鍵盤和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT: # pygame.QUIT 指點擊右上角窗口的"X"號
sys.exit() # 點擊后,卸載所有pygame模塊
# 每次循環(huán)時都重繪屏幕
screen.fill(ai_settings.bg_color)
# 讓最近繪制的屏幕可見
pygame.display.flip()
run_game() # 初始化游戲并開始主循環(huán)
使用方式如上:在主程序文件中,導入了Settings類。調(diào)用pygame.init(),再創(chuàng)建一個Settings實例,并將其存儲再變量ai_settings中。創(chuàng)建屏幕時,直接使用ai_settings的屬性。
> 添加飛船圖像
為游戲選擇素材時,務(wù)必要注意許可。
https://pixabay.com/網(wǎng)站提供的圖形無需許可,推薦使用。
在游戲中幾乎可以使用任何類型的圖像文件,但使用位圖(.bmp)最簡單,因為Pygame默認加載位圖。雖可配置Pygame以使用其他文件類型,但有些類型要求你安裝相應(yīng)圖像庫。
選擇好適合的圖像文件后,在主項目文件夾(此處為alien_invasion)中新建一個文件夾,將其命名為images,并將圖像文件保存在其中。
> 創(chuàng)建ship類
# ship.py
import pygame
class Ship:
def __init__(self, screen): # 后者指定了要將飛船繪往哪
"""初始化飛船并設(shè)置其初始位置"""
self.screen = screen
# 加載飛船圖像并獲取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
# 將每艘新飛船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx # 取屏幕中央x坐標賦值
self.rect.bottom = self.screen_rect.bottom # 取屏幕底部賦值
def blitme(self):
"""在指定位置繪制飛船"""
self.screen.blit(self.image, self.rect)
>> pygame.image
pygame.image是用于圖像傳輸?shù)膒ygame模塊。
- pygame.image.load()? —? 從文件加載新圖片
- pygame.image.save()? —? 將圖像保存到磁盤上
- pygame.image.get_extended()? —? 檢測是否支持載入擴展的圖像格式
- pygame.image.tostring()? —? 將圖像轉(zhuǎn)換為字符串描述
- pygame.image.fromstring()? —? 將字符串描述轉(zhuǎn)換為圖像
- pygame.image.frombuffer()? —? 創(chuàng)建一個與字符串描述共享數(shù)據(jù)的 Surface 對象
?>> get_rect( )
使用get_rect( ) 獲取相應(yīng)surface的屬性rect.
常用rect函數(shù)的參數(shù)有以下形式:
?????? x,y
?????? top, left, bottom, right
?????? topleft, bottomleft, topright, bottomright
?????? midtop, midleft, midbottom, midright
?????? center, centerx, centery
?????? size, width, height
?????? w,h
要將游戲元素居中,可設(shè)置相應(yīng)rect對象的屬性center、centerx或centery;
要讓游戲元素與屏幕邊緣對齊,可使用屬性top、bottom、left或right;
要調(diào)整游戲元素的水平或垂直位置,可使用屬性x和y(分別為相應(yīng)矩形左上角的x、y坐標)
注意:在Pygame中,原點(0,0) 位于屏幕左上角,向右下方移動時,坐標值將增大。在1200x800的屏幕上,原點位于左上角,而右下角的坐標為(1200,800)
例:將self.rect.centerx(飛船中心的x坐標)設(shè)為表示屏幕的矩形的屬性centerx,
?????? 并將self.rect.bottom(飛船下邊緣的y坐標)設(shè)置為表示屏幕的矩形的屬性bottom。
Pygame將使用這些rect屬性來放置飛船圖像,使其與屏幕下邊緣對齊并水平居中。self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom
?>> surface.blit( )
pygame.Surface.blit()? | 將一個圖像(Surface 對象)繪制到另一個圖像上 |
"""在指定位置繪制飛船"""
self.screen.blit(self.image, self.rect)
> 在屏幕上繪制飛船
更新alien_invasion.py,使其創(chuàng)建一艘飛船,并調(diào)用其方法blitme( ) :
--snip--
from ship import Ship
def run_game():
--snip--
pygame.display.set_caption("Alien Invasion") # 設(shè)置窗口標題
# 創(chuàng)建一艘飛船
ship = Ship(screen)
# 開始游戲的主循環(huán)
while True:
--snip--
# 每次循環(huán)時都重繪屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 讓最近繪制的屏幕可見
pygame.display.flip()
run_game() # 初始化游戲并開始主循環(huán)
- 必須在主while循環(huán)之前創(chuàng)建Ship實例,以免每次循環(huán)時都創(chuàng)建一艘飛船。
- 填充背景后再調(diào)用ship.blitme()將飛船繪到屏幕上,確保它出現(xiàn)在背景前面。
> 重構(gòu):模塊game_functions
重構(gòu)旨在簡化既有代碼的結(jié)構(gòu),使其更容易擴展。我們將創(chuàng)建新模塊game_functions,它將存儲大量讓游戲 《外星人入侵》運行的函數(shù)。通過創(chuàng)建該模塊,可避免alien_invasion.py太長,并使其邏輯更容易理解。
> 函數(shù) check_events( )
首先把管理事件的代碼移到一個名為check_events( )的函數(shù)中,以簡化run_game( )并隔離事件管理循環(huán)。通過隔離事件循環(huán),可將事件管理與游戲的其他方面(如更新屏幕)分離。
# game_functions.py
import sys
import pygame
def check_events():
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT: # pygame.QUIT 指點擊右上角窗口的"X"號
sys.exit() # 點擊后,卸載所有pygame模塊
同時要記得修改alien_invasion.py,導入該模塊并將事件循環(huán)替換為對check_events()的調(diào)用。
> 函數(shù) update_screen()
為進一步簡化run_game(),將更新屏幕的代碼移到函數(shù)update_screen()中:
# game_functions.py
--snip--
def update_screen(ai_settings, screen, ship):
"""更新屏幕上的圖像,并切換到新屏幕"""
# 每次循環(huán)時都重繪屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 讓最近繪制的屏幕可見
pygame.display.flip()
修改alien_invasion.py,導入該模塊,并將更新屏幕替換為對函數(shù)update_screen()的調(diào)用:
# alien_invasion.py
--snip--
import game_functions as gf
def run_game():
--snip--
# 開始游戲的主循環(huán)
while True:
gf.check_events()
gf.update_screen(ai_settings, screen, ship)
run_game() # 初始化游戲并開始主循環(huán)
在主程序文件中,不再需要直接導入sys,因為當前只有在模塊game_functions中使用了它。出于簡化的目的,我們給導入的模塊game_functions指定了別名gf。
> 駕駛飛船
編寫代碼,在用戶按左或者右箭頭時作出響應(yīng)。
> 響應(yīng)按鍵
每當用戶按鍵時,都將在Pygame中注冊一個事件。事件都是通過方法pygame.event.get()獲取的,因此在函數(shù)check_events()中,我們需要指定要檢查哪些類型的事件。每次按鍵都被注冊為一個KEYDOWN事件。檢測到KEYDOWN事件時,我們需要檢查按下的是否是特定的鍵。
# game_functions.py
# 例如,如果按下的是右箭頭鍵,我們就增大飛船的rect.centerx值,將飛船向右移動:
--snip--
def check_events(ship):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.rect.centerx += 1 # 向右移動飛船
# alien_invasion.py 更新check_events()參數(shù)
--snip--
# 開始游戲的主循環(huán)
while True:
gf.check_events(ship)
gf.update_screen(ai_settings, screen, ship)
現(xiàn)在運行alien_invasion.py,則每按右箭頭鍵一次,飛船都將向右移動1像素。
> 允許不斷移動
在上面的實驗中,我們發(fā)現(xiàn),當玩家按住右箭頭鍵不放時,飛船只會移動一次,即1像素?,F(xiàn)在我們希望飛船不斷地向右移動,直到玩家松開為止。
我們將讓游戲檢測pygame.KEYUP事件,以便玩家松開右箭頭鍵時我們能夠得知。
飛船的屬性都由Ship類控制,因此我們將給這個類添加一個名為moving_right的屬性和一個名為update()的方法。方法update()檢查標志moving_right的狀態(tài),如果這個標志為True,就調(diào)整飛船的位置。每當需要調(diào)整飛船的位置時,我們都調(diào)用這個方法。
# ship.py
--snip--
class Ship:
def __init__(self, screen): # 后者指定了要將飛船繪往哪
--snip--
# 移動標志
self.moving_right = False
def update(self):
"""根據(jù)移動標志調(diào)整飛船的位置"""
if self.moving_right:
self.rect.centerx += 1
--snip--
同時修改check_events()中當玩家按下/松開右箭頭時的事件響應(yīng)代碼:
# game_functions.py
--snip--
def check_events(ship):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True # 按下設(shè)置為True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False # 松開設(shè)置為False
--snip--
最后修改alien_invasion中的while循環(huán),以便每次執(zhí)行循環(huán)時都調(diào)用方法update():
# alien_invasion.py
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings, screen, ship)
飛船的位置將在檢測到鍵盤事件后(但在更新屏幕前)更新。這樣,玩家輸入時,飛船的位置將更新,從而確保使用更新后的位置將飛船繪制到屏幕上。
> 左右移動
完成了向右移動后,向左移動則同理。再次對Ship類和函數(shù)check_events() 進行修改:
# ship.py
--snip--
# 移動標志
self.moving_right = False
self.moving_left = False
def update(self):
"""根據(jù)移動標志調(diào)整飛船的位置"""
if self.moving_right:
self.rect.centerx += 1
if self.moving_left:
self.rect.centerx -= 1
在方法update()中,我們添加了if而不是elif代碼塊。
若玩家同時按下左右箭頭鍵,將先增大飛船的rect.centerx值,再降低,即飛船的位置保持不變。若用elif代碼塊來處理向左移動的情況,右箭頭鍵將始終處于優(yōu)先地位。從向左移動切換到向右移動時,玩家可能同時按住左右箭頭鍵,在這種情況下,前面的做法讓移動更準確。(測試可知,同時按下左右箭頭鍵,if下飛船不動,而elif下飛船始終向右)
# game_functions.py
--snip--
def check_events(ship):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
?這里之所以可以使用elif代碼塊,是因為每個事件都只與一個鍵相關(guān)聯(lián)。如果玩家同時按下左右箭頭鍵,將檢測到兩個不同的事件。
> 調(diào)整飛船的速度
我們可以在Settings類中添加屬性ship_speed_factor,用于控制飛船的速度:
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
--snip--
# 飛船的設(shè)置
self.ship_speed_factor = 1.5
然而,rect的centerx等屬性只能存儲整數(shù)值,因此我們需要對Ship類作修改:
# ship.py
import pygame
class Ship:
def __init__(self, ai_settings, screen): # 后者指定了要將飛船繪往哪
"""初始化飛船并設(shè)置其初始位置"""
self.screen = screen
self.ai_settings = ai_settings
--snip--
# 在飛船的屬性center中存儲小數(shù)值
self.center = float(self.rect.centerx)
--snip--
def update(self):
"""根據(jù)移動標志調(diào)整飛船的位置"""
# 更新飛船的center值,而不是rect
if self.moving_right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left:
self.center -= self.ai_settings.ship_speed_factor
# 根據(jù)self.center更新rect對象
self.rect.centerx = self.center
可以使用小數(shù)來設(shè)置rect的屬性,但rect將只存儲這個值的整數(shù)部分。為準確地存儲飛船的位置,我們定義了一個可存儲小數(shù)值的新屬性self.center。
(使用函數(shù)float()將self.rect.centerx的值轉(zhuǎn)換為小數(shù),并將結(jié)果存儲到self.center中。)
更新self.center后,我們再根據(jù)它來更新控制飛船位置的 self.rect.centerx。self.rect.centerx將只存儲self.center的整數(shù)部分,但對顯示飛船而言,這問題不大。
因為在Ship類中__init__( )形參中添加了ai_settings,讓飛船能夠獲取其速度設(shè)置。 所以在alien_invasion.py中創(chuàng)建Ship實例時,需傳入實參ai_settings:
# alien_invasion.py
--snip--
def run_game():
--snip--
ship = Ship(ai_settings, screen)
--snip--
> 限制飛船的活動范圍
當前,若我們按住箭頭鍵一段時間,就會發(fā)現(xiàn)飛船移到了屏幕之外,無法看見。下面就來限制其活動范圍,讓飛船到達屏幕邊緣后停止移動。
# ship.py
--snip--
def update(self):
"""根據(jù)移動標志調(diào)整飛船的位置"""
# 更新飛船的center值,而不是rect
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
--snip--
> 重構(gòu)check_events()
隨著游戲開發(fā)的進行,函數(shù)check_events()將越來越長,我們將其部分代碼放在兩個函數(shù)中:
一個處理KEYDOWN事件,另一個處理KEYUP事件:
# game_functions.py
--snip--
def check_keydown_events(event, ship):
"""響應(yīng)按鍵"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
def check_keyup_events(event, ship):
"""響應(yīng)松開"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def check_events(ship):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
if event.type == pygame.QUIT: # pygame.QUIT 指點擊右上角窗口的"X"號
sys.exit() # 點擊后,卸載所有pygame模塊
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
--snip--
> 簡單回顧
下一節(jié)將添加射擊功能,這需要新增一個名為bullet.py的文件,并對一些既有文件進行修改。 當前,我們有四個文件,其中包含很多類、函數(shù)和方法。添加其他功能之前,為讓你清楚這個項目的組織結(jié)構(gòu),先來回顧一下這些文件。
> alien_invasion.py
主文件alien_invasion.py創(chuàng)建一系列整個游戲都要用到的對象:存儲在ai_settings中的設(shè)置、 存儲在screen中的主顯示surface以及一個飛船實例。文件alien_invasion.py還包含游戲的主循環(huán), 這是一個調(diào)用check_events()、ship.update()和update_screen()的while循環(huán)。
# alien_invasion.py
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
def run_game():
# 初始化游戲并創(chuàng)建一個屏幕文件
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height)) # 指定窗口尺寸
pygame.display.set_caption("Alien Invasion") # 設(shè)置窗口標題
# 創(chuàng)建一艘飛船
ship = Ship(ai_settings, screen)
# 開始游戲的主循環(huán)
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings, screen, ship)
run_game() # 初始化游戲并開始主循環(huán)
> settings.py
文件settings.py包含Settings類,這個類只包含方法__init__(),它初始化控制游戲外觀和飛船速度的屬性。
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
"""初始化游戲的設(shè)置"""
# 屏幕設(shè)置
self.screen_width = 900
self.screen_height = 600
self.bg_color = (230, 230, 230)
# 飛船的設(shè)置
self.ship_speed_factor = 1.5
> game_functions.py
文件game_functions.py包含一系列函數(shù),游戲的大部分工作都是由它們完成的。函數(shù) check_events()檢測相關(guān)的事件,如按鍵和松開,并使用輔助函數(shù)check_keydown_events()和check_keyup_events()來處理這些事件。就目前而言,這些函數(shù)管理飛船的移動。模塊 game_functions還包含函數(shù)update_screen(),它用于在每次執(zhí)行主循環(huán)時都重繪屏幕。
> ship.py
文件ship.py包含Ship類,這個類包含方法__init__()、管理飛船位置的方法update()以及在屏幕上繪制飛船的方法blitme()。表示飛船的圖像存儲在文件夾images下的文件ship.bmp中。
# ship.py
import pygame
class Ship:
def __init__(self, ai_settings, screen): # 后者指定了要將飛船繪往哪
"""初始化飛船并設(shè)置其初始位置"""
self.screen = screen
self.ai_settings = ai_settings
# 加載飛船圖像并獲取其外接矩形
self.image = pygame.image.load('images/ship.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
# 將每艘新飛船放在屏幕底部中央
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
# 在飛船的屬性center中存儲小數(shù)值
self.center = float(self.rect.centerx)
# 移動標志
self.moving_right = False
self.moving_left = False
def update(self):
"""根據(jù)移動標志調(diào)整飛船的位置"""
# 更新飛船的center值,而不是rect
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
# 根據(jù)self.center更新rect對象
self.rect.centerx = self.center
def blitme(self):
"""在指定位置繪制飛船"""
self.screen.blit(self.image, self.rect)
> 射擊
添加射擊功能(玩家按空格鍵發(fā)射子彈[小矩形]的代碼)。
子彈將在屏幕中向上穿行,抵達屏幕上邊緣后消失。
> 添加子彈設(shè)置 ?????????
首先,更新settings.py,在其方法__init__( )末尾存儲新類Bullet所需的值:
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
--snip--
# 子彈設(shè)置 (創(chuàng)建寬3像素、高15像素的深灰色子彈,速度為1)
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
> 創(chuàng)建Bullet類
# bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一個對飛船發(fā)射的子彈進行管理的類"""
def __init__(self, ai_settings, screen, ship):
"""在飛船所處的位置創(chuàng)建一個子彈對象"""
super(Bullet, self).__init__()
self.screen = screen
# 在(0,0)處創(chuàng)建一個表示子彈的矩形,再設(shè)置正確的位置
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width,
ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
# 存儲用小數(shù)表示的子彈位置
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
"""向上移動子彈"""
# 更新表示子彈位置的小數(shù)值
self.y -= self.speed_factor
# 更新表示子彈的rect的位置
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上繪制子彈"""
pygame.draw.rect(self.screen, self.color, self.rect)
?Bullet類繼承了我們從模塊pygame.sprite中導入的Sprite類。通過使用精靈,可將游戲中相關(guān)的元素編組,進而同時操作編組中的所有元素。這里還調(diào)用了super()來繼承Sprite.【python游戲編程之旅】第六篇---pygame中的Sprite(精靈)模塊和加載動畫 - 走看看
from pygame.sprite import Sprite class Bullet(Sprite): def __init__(self, ai_settings, screen, ship): """在飛船所處的位置創(chuàng)建一個子彈對象""" # 這里使用了Python2.7語法,也可簡寫為 super().__init__() super(Bullet, self).__init__()
子彈并非基于圖像的,因此我們必須使用pygame.Rect()類從空白開始創(chuàng)建一個矩形。
# 在(0,0)處創(chuàng)建一個表示子彈的矩形,再設(shè)置正確的位置 # Rect(left,top,width,height) self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height) # 根據(jù)飛船位置調(diào)整子彈正確位置,讓子彈看起來像是從飛船中射出的 self.rect.centerx = ship.rect.centerx self.rect.top = ship.rect.top
參數(shù) 含義 left 與窗口左邊界的距離 right 與窗口左邊界的距離+圖像本身的寬度(width) top 與窗口上邊界的距離 bottom 與窗口上邊界的距離 + 圖像本身的高度(height) 在pygame中,以左上角為坐標原點,水平向右為x軸正方向,豎直向下為y軸正方向。 子彈發(fā)射向上移動,本質(zhì)上y坐標將不斷減小,x坐標不變,直線往上,據(jù)此更新子彈位置:
def update(self): """向上移動子彈""" # 更新表示子彈位置的小數(shù)值 self.y -= self.speed_factor # 更新表示子彈的rect的位置 # 先前用self.y存儲小數(shù)表示的子彈位置 self.y = float(self.rect.y) self.rect.y = self.y
調(diào)用pygame.draw.rect() ——繪制矩形,繪制子彈。
def draw_bullet(self): """在屏幕上繪制子彈""" # pygame.draw.rect(Surface, color, Rect, width=0): return Rect # 在Surface上繪制矩形,color:顏色,Rect形式是((x, y),(width,height)), # width表示線條的粗細,單位為像素;默認值為0,表示填充矩形內(nèi)部。 pygame.draw.rect(self.screen, self.color, self.rect)
?pygame.draw —— Pygame中繪制圖形的模塊
?pygame.draw.rect() —— 繪制矩形
> 將子彈存儲到編組中
首先,我們將在alien_invasion.py中創(chuàng)建一個編組(group),用于存儲所有有效的子彈,以便管理發(fā)射出去的所有子彈。這個編組將是pygame.sprite.Group類的一個實例; pygame.sprite. Group類類似于列表,但提供了有助于開發(fā)游戲的額外功能。在主循環(huán)中,我們將使用這個編組在屏幕上繪制子彈,以及更新每顆子彈的位置:
# alien_invasion.py
--snip--
from pygame.sprite import Group
def run_game():
--snip--
# 創(chuàng)建一個用于存儲子彈的編組
bullets = Group()
# 開始游戲的主循環(huán)
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(ai_settings, screen, ship, bullets)
run_game() # 初始化游戲并開始主循環(huán)
這個編組是在while循環(huán)外創(chuàng)建的,這樣就無需每次運行該循環(huán)時創(chuàng)建一個新的子彈編組
注意! 如果在循環(huán)內(nèi)部創(chuàng)建這樣的編組,游戲運行時將創(chuàng)建數(shù)千個子彈編組,導致游戲慢的像蝸牛。如果游戲停滯不前,請仔細檢查主while循環(huán)中發(fā)生的情況!
我們將bullets傳遞給了check_events()和update_screen()。在check_events()中,需要在玩家按空格鍵時處理bullets;而在update_screen()中,需要更新要繪制到屏幕上的bullets。
當你對編組調(diào)用update()時,編組將自動對其中的每個精靈調(diào)用update(),因此代碼行 bullets.update()將為編組bullets中的每顆子彈調(diào)用bullet.update()。?
> 開火
在game_functions.py中,我們需要修改check_keydown_events(),以便在玩家按空格鍵時發(fā)射一顆子彈。我們無需修改check_keyup_events(),因為玩家松開空格鍵時什么都不會發(fā)生。我們還需修改update_screen(),確保在調(diào)用flip()前在屏幕上重繪每顆子彈。
# game_functions.py
--snip--
from bullet import Bullet
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_SPACE:
# 創(chuàng)建一顆子彈,并將其加入到編組bullets中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
--snip--
def check_events(ai_settings, screen, ship, bullets):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
--snip--
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship,bullets)
--snip--
def update_screen(ai_settings, screen, ship, bullets):
"""更新屏幕上的圖像,并切換到新屏幕"""
# 每次循環(huán)時都重繪屏幕
screen.fill(ai_settings.bg_color)
# 在飛船和外星人后面重繪所有子彈
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
# 讓最近繪制的屏幕可見
pygame.display.flip()
方法 bullets.sprites()返回一個列表,其中包含編組bullets中的所有精靈。為在屏幕上繪制發(fā)射的所有子彈,我們遍歷編組bullets中的精靈,并對每個精靈都調(diào)用draw_bullet().
for bullet in bullets.sprites(): bullet.draw_bullet()
此時運行alien_invasion.py,將能夠左右移動飛船,并發(fā)射任意數(shù)量的子彈。子彈在屏幕上向上穿行,抵達屏幕底部后消失:
Q:game_functions.py中函數(shù)update_screen飛船和子彈繪制順序先后有影響嗎?
# 繪制子彈 for bullet in bullets.sprites(): bullet.draw_bullet() # 繪制飛船 ship.blitme()
> 刪除已消失的子彈
在前一個步驟,子彈抵達屏幕頂端后消失,這僅僅是因為Pygame無法在屏幕外面繪制它們。這些子彈實際上依然存在,它們的y坐標為負數(shù),且越來越小。這是個問題,因為它們將繼續(xù)消耗內(nèi)存和處理能力。
子彈消除的檢測條件即子彈已穿過屏幕頂端,
此時子彈的rect的bottom屬性為零
# alien_invasion.py
--snip--
def run_game():
---snip--
while True:
--snip--
# 刪除已消失的子彈
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets)) # 檢查語句
gf.update_screen(ai_settings, screen, ship, bullets) # 更新屏幕
run_game() # 初始化游戲并開始主循環(huán)
Q:在刪除子彈時,為什么要遍歷bullets.copy() 【其副本】而非bullets本身?
A:在for循環(huán)中,不應(yīng)從列表或編組中刪除條目,因此必須遍歷編組的副本。
????? 因為在刪除列表元素時,Python會自動對列表內(nèi)存進行收縮并移動列表元素以保證所有元素之間沒有空隙,因此元素列表的索引是會變化的。
????? 循環(huán)bullets刪除時,刪除1 后,2 補位1,3補位2,循環(huán)看似循環(huán)到2,實際上被刪除的是3,而2并沒有被檢測到。
????? 循環(huán)bullets.copy刪除時,刪除1后,2補位1,但因為bullets.copy中的1沒有被刪除,2不會前移,仍能被檢測,這樣就避免了上面的問題。雖然bullets中的元素被刪除了,但是bullets.copy的元素并沒有改動,所以會循環(huán)到bullets(=bullets.copy)的中的所有元素。
通過print語句可以檢測子彈是否被刪除,檢測完后記得將print語句去掉,以免降低游戲運行速度。
> 限制子彈數(shù)量
對同時出現(xiàn)在屏幕上的子彈數(shù)量進行限制。
首先,在settings.py中存儲所允許的最大子彈數(shù):
# settings.py
--snip--
# 子彈設(shè)置
self.bullet_speed_factor = 1 # 子彈速度
self.bullet_width = 3 # 寬
self.bullet_height = 15 # 高
self.bullet_color = 60, 60, 60 # 顏色(深灰)
self.bullets_allowed = 3 # 允許的最大子彈數(shù)
創(chuàng)建新子彈即玩家按下空格,因此在創(chuàng)建新子彈前(即game_functions.py的check_keydown_events()中),檢查未消失的子彈數(shù)是否小于該設(shè)置:
# game_functions.py
--snip--
def check_keydown_events(event, ai_settings, screen, ship, bullets):
"""響應(yīng)按鍵"""
--snip--
elif event.key == pygame.K_SPACE:
# 創(chuàng)建一顆子彈,并將其加入到編組bullets中
if len(bullets) < ai_settings.bullets_allowed: # 檢查子彈數(shù)量
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
--snip--
> 創(chuàng)建函數(shù)update_bullets( )
編寫并檢查子彈管理代碼后,可將其移到模塊game_functions中,以讓主程序文件alien_invasion.py盡可能簡單。我們創(chuàng)建一個名為update_bullets()的新函數(shù),并將其添加到game_functions.py的末尾:
# game_functions.py
--snip--
def update_bullets(bullets):
"""更新子彈的位置,并刪除已消失的子彈"""
# 更新子彈位置
bullets.update()
# 刪除已消失的子彈
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets))
同時用該函數(shù)替代原在alien_invasion.py中的代碼:
# alien_invasion.py
--snip--
while True:
gf.check_events(ai_settings, screen, ship, bullets) # 檢查事件
ship.update() # 更新飛船狀態(tài)
gf.update_bullets(bullets) # 更新子彈狀態(tài)
gf.update_screen(ai_settings, screen, ship, bullets) # 更新屏幕
我們讓主循環(huán)包含盡可能少的代碼,這樣只要看函數(shù)名就能迅速知道游戲中發(fā)生的情況:
- 主循環(huán)檢查玩家的輸入
- 更新飛船的位置
- 更新所有未消失的子彈的位置
- 使用更新后的位置來繪制新屏幕
> 創(chuàng)建函數(shù) fire_bullet( )
這里將發(fā)射子彈的代碼移到一個獨立的函數(shù)中,這樣,在check_keydown_events()中只需使
用一行代碼來發(fā)射子彈,讓elif代碼塊變得非常簡單:
# game_functions.py
--snip--
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
def fire_bullet(ai_settings, screen, ship, bullets):
"""如果還沒有到達限制,就發(fā)射一顆子彈"""
# 創(chuàng)建一顆新子彈,并將其加入到編組bullets中
if len(bullets) < ai_settings.bullets_allowed: # 檢查限制
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
> 快捷結(jié)束游戲
在每次測試時都需要用鼠標來關(guān)閉它,并不方便,因此我們來添加一個結(jié)束游戲的快捷鍵Q:
# game_functions.py
--snip--
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_q:
sys.exit()
添加此代碼塊,以便玩家按Q時結(jié)束游戲。這樣的修改很安全,因為Q鍵離箭頭鍵和空格鍵很遠,玩家不小心按Q鍵而導致游戲結(jié)束的可能性不大。
> 外星人
> 創(chuàng)建第一個外星人
> 創(chuàng)建Alien類
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""表示單個外星人的類"""
def __init__(self, ai_settings, screen):
"""初始化外星人并設(shè)置其起始位置"""
super(Alien, self).__init__()
self.screen = screen
self.ai_settings = ai_settings
# 加載外星人圖像,并設(shè)置其rect屬性
self.image = pygame.image.load('images/alien.bmp')
self.rect = self.image.get_rect()
# 每個外星人最初都在屏幕左上角附近
self.rect.x = self.rect.width
self.rect.y = self.rect.height
# 存儲外星人的準確位置
self.x = float(self.rect.x)
def blitme(self):
"""在指定位置繪制外星人"""
self.screen.blit(self.image, self.rect)
除位置不同外,這個類的大部分代碼都與Ship類相似。每個外星人最初都位于屏幕左上角附近,我們將每個外星人的左邊距都設(shè)置為外星人的寬度,并將上邊距設(shè)置為外星人的高度。
> 創(chuàng)建Alien實例
在alien_invasion.py中創(chuàng)建一個Alien實例:
# alien_invasion.py
--snip--
from alien import Alien
def run_game():
--snip--
# 創(chuàng)建一個外星人
alien = Alien(ai_settings,screen)
# 開始游戲的主循環(huán)
while True:
--snip--
gf.update_screen(ai_settings, screen, ship, alien, bullets) # 更新屏幕
run_game() # 初始化游戲并開始主循環(huán)
> 讓外星人出現(xiàn)在屏幕上
為了讓外星人出現(xiàn)在屏幕上,我們在update_screen( ) 中調(diào)用其方法blitme( ):
# game_functions.py
--snip--
def update_screen(ai_settings, screen, ship, alien, bullets):
--snip--
alien.blitme()
# 讓最近繪制的屏幕可見
pygame.display.flip()
完成后執(zhí)行程序,可以看到一個外星人成功的顯示在了屏幕上:?
> 創(chuàng)建一群外星人
繪制一群外星人,需要確定一行能容納多少個外星人以及要繪制多少行外星人。我們首先計算外星人之間的水平間距,并創(chuàng)建一行外星人,再確定可用的垂直空間,并創(chuàng)建整群外星人。
> 確定一行可容納多少個外星人
屏幕寬度存儲在 ai_settings.screen_width中,但需要在屏幕兩邊留下一定的邊距(此處設(shè)為外星人的寬度),因此可用于放置外星人的水平空間為屏幕寬度減去外星人寬度的兩倍:
available_space_x = ai_settings.screen_width – (2 * alien_width)
外星人之間也需留出一定的空間,即外星人寬度。因此,顯示一個外星人所需的水平空間為外星人寬度的兩倍:一個寬度用于放置外星人,另一個寬度為外星人右邊的空白區(qū)域。
number_aliens_x = available_space_x / (2 * alien_width)
> 創(chuàng)建多行外星人
創(chuàng)建一行外星人,首先在alien_invasion.py中創(chuàng)建一個名為aliens的空編組,用于存儲全部
外星人,再調(diào)用game_functions.py中創(chuàng)建外星人群的函數(shù):
# alien_invasion.py
--snip--
def run_game():
--snip--
# 創(chuàng)建一個外星人編組
aliens = Group()
# 創(chuàng)建外星人群
gf.create_fleet(ai_settings, screen, aliens)
# 開始游戲的主循環(huán)
while True:
--snip--
gf.update_screen(ai_settings, screen, ship, aliens, bullets) # 更新屏幕
run_game() # 初始化游戲并開始主循環(huán)
# game_functions.py
--snip--
def update_screen(ai_settings, screen, ship, aliens, bullets):
--snip--
aliens.draw(screen) # 在屏幕上繪制編組的每個外星人
# 讓最近繪制的屏幕可見
pygame.display.flip()
對編組調(diào)用draw( )時,Pygame自動繪制編組的每個元素,繪制位置由元素的屬性rect決定。在這里,aliens.draw(screen)在屏幕上繪制編組中的每個外星人。
> 創(chuàng)建外星人群
創(chuàng)建新函數(shù)create_fleet(),利用其來創(chuàng)建外星人群:
# game_functions.py
--snip--
from alien import Alien
--snip--
def create_fleet(ai_settings, screen, aliens):
"""創(chuàng)建外星人群"""
# 創(chuàng)建一個外星人,并計算一行可容納多少個外星人
alien = Alien(ai_settings, screen)
alien_width = alien.rect.width # 外星人間距為外星人寬度
available_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
# 創(chuàng)建第一行外星人
for alien_number in range(number_aliens_x):
# 創(chuàng)建一個外星人并將其加入當前行
alien = Alien(ai_settings, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
int(available_space_x / (2 * alien_width)) 此處使用了int() 來確保計算得到的外星人數(shù)量為整數(shù),因為我們不希望某個外星人只顯示一部分,而且函數(shù)range()也需要一個整數(shù)。函數(shù)int()相當于向下圓整。?
此時屏幕如上所示, 這行外星人在屏幕上稍微偏向了左邊,這實際上是有好處的,因為我們將讓外星人群往右移, 觸及屏幕邊緣后稍微往下移,然后往左移,以此類推。就像經(jīng)典游戲《太空入侵者》,相比于只往下移,這種移動方式更有趣。我們將讓外形人群不斷這樣移動,直到所有外星人都被擊落或有 外星人撞上飛船或抵達屏幕底端。
> 重構(gòu)create_fleet( )
鑒于創(chuàng)建外星人的工作還未完成,我們稍微清理一下函數(shù)creat_fleet();
下面creat_fleet()和兩個新韓淑,get_number_aliens_x()和create_alien():
# game_functions.py
--snip--
def get_number_aliens_x(ai_settings, alien_width):
"""計算每行可容納多少個外星人"""
available_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def create_alien(ai_settings, screen, aliens, alien_number):
"""創(chuàng)建一個外星人并將其加入當前行"""
alien = Alien(ai_settings, screen)
alien_width = alien.rect.width # 外星人間距為外星人寬度
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
def create_fleet(ai_settings, screen, aliens):
"""創(chuàng)建外星人群"""
# 創(chuàng)建一個外星人,并計算每行可容納多少個外星人
alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
# 創(chuàng)建第一行外星人
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number)
> 添加行
要創(chuàng)建外星人群,需計算屏幕可容納多少行,并對創(chuàng)建一行外星人的循環(huán)重復(fù)相應(yīng)的次數(shù)。
垂直空間:將屏幕高度減去第一行外星人的上邊距(外星人高度)、飛船的高度以及最初外星人群與飛船的距離(外星人高度的兩倍):
available_space_y = ai_settings.screen_height – 3 * alien_height – ship_height
最初外星人群與飛船的距離是為在飛船上方留出一定區(qū)域,給玩家留出射殺外星人的時間。
每行下方都要留出一定的空白區(qū)域,并將其設(shè)置為外星人的高度。為計算可容納的行數(shù),我們將可用垂直空間除以外星人高度的兩倍。
number_rows = available_space_y / (2 * alien_height)
# game_functions.py
--snip--
def get_number_rows(ai_settings, ship_height, alien_height):
"""計算屏幕可容納多少行外星人"""
available_space_y = (ai_settings.screen_height -
(3 * alien_height) - ship_height)
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(ai_settings, screen, aliens, alien_number, row_number):
--snip--
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
aliens.add(alien)
def create_fleet(ai_settings, screen, ship, aliens):
--snip--
number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
# 創(chuàng)建外星人群
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number, row_number)
# alien_invasion.py
# 創(chuàng)建外星人群 (補充新增參數(shù)ship)
gf.create_fleet(ai_settings, screen, ship, aliens)
此時運行游戲,將看到一群外星人:
>? 讓外星人群移動
下面來讓外星人群在屏幕上向右移動,撞到屏幕邊緣后下移一定的距離,再沿相反的方向移動。所有外星人將不斷移動直到都被消滅,有外星人撞上飛船,或有外星人抵達屏幕底端。
> 向右移動外星人
首先在settings.py中添加一個控制外星人速度的設(shè)置:
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
--snip--
# 外星人設(shè)置
self.alien_speed_factor = 1
然后,使用這個設(shè)置來實現(xiàn)update( ):
# alien.py
--snip--
def update(self):
"""向右移動外星人"""
self.x += self.ai_settings.alien_speed_factor
self.rect.x = self.x
這里同樣先使用屬性self.x跟蹤每個外星人的準確位置(該屬性可存儲小數(shù)值),再使用該值來更新外星人的rect的位置。
在主while循環(huán)中更新每個外星人的位置,同時在文件gf.py末尾添加新函數(shù)update_aliens():
# alien_invasion.py
--snip--
while True:
gf.check_events(ai_settings, screen, ship, bullets) # 檢查事件
ship.update() # 更新飛船狀態(tài)
gf.update_bullets(bullets) # 更新子彈狀態(tài)
gf.update_aliens(aliens) # 更新外星人狀態(tài)
--snip--
# game_functions.py
--snip--
def update_aliens(aliens):
"""更新外星人群中所有外星人的位置"""
aliens.update()
我們對編組aliens()調(diào)用方法update(),這將自動對每個外星人調(diào)用方法update().
此時運行游戲,會看到外星人群向右移,并逐漸在屏幕右邊緣消失。
> 創(chuàng)建表示外星人移動方向的設(shè)置
接著來創(chuàng)建讓外星人撞到屏幕右邊緣后向下移動,再向左移動的設(shè)置。
先在settings.py中定義需要的設(shè)置:
# settings.py
--snip--
# 外星人設(shè)置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10 # 向下移動速度
# fleet_direction為1表示向右移,為-1表示向左移
self.fleet_direction = 1
向下移動速度fleet_drop_speed:區(qū)分于水平速度,這樣可分別調(diào)整兩種速度
控制移動方向fleet_direction: 使用值-1與1來表示
注:實現(xiàn)fleet_direction設(shè)置,可以將其設(shè)置為文本值,如'left'或'right',但這樣就必須編寫if-elif語句來檢查外星人群的移動方向。鑒于只有兩個可能的方向,我們使用值1和-1來表 示它們,并在外星人群改變方向時在這兩個值之間切換。另外,鑒于向右移動時需要增大每個外星人的x坐標,而向左移動時需要減小每個外星人的x坐標,使用數(shù)字來表示方向更合理。
> 檢查外星人是否撞到了屏幕邊緣
現(xiàn)在編寫方法check_edges()檢查是否有外星人撞到了屏幕邊緣,并修改update()讓外星人更準確的移動(將移動量設(shè)置為外星人速度和方向±1的乘積,讓其向左或向右移)
# alien.py
--snip--
def check_edges(self):
"""如果外星人位于屏幕邊緣,就返回True"""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right: # 位于右邊緣
return True
elif self.rect.left <= 0: # 位于左邊緣
return True
def update(self):
"""向右(dirction為1)或左(dirction為-1)移動外星人"""
self.x += (self.ai_settings.alien_speed_factor *
self.ai_settings.fleet_dirction)
self.rect.x = self.x
> 向下移動外星人群并改變移動方向
有外星人到達屏幕邊緣時,需要將整群外星人下移。
# game_functions.py
--snip--
def check_fleet_edges(ai_settings, aliens):
"""有外星人到達屏幕邊緣時采取相應(yīng)的措施"""
for alien in aliens.sprites():
if alien.check_edges(): # 若有外星人到達屏幕邊緣
change_fleet_direction(ai_settings, aliens)
break
def change_fleet_direction(ai_settings, aliens):
"""將整群外星人下移,并改變他們的方向"""
for alien in aliens.sprites(): # 將整群外星人下移
alien.rect.y += ai_settings.fleet_drop_speed
ai_settings.fleet_direction *= -1 # 改變方向
def update_aliens(ai_settings, aliens):
"""檢查是否有外星人位于屏幕邊緣,并更新整群外星人的位置"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
# alien_invasion.py 更新函數(shù)(添加新增參數(shù)ai_settings)
--snip--
gf.update_aliens(ai_settings, aliens) # 更新外星人狀態(tài)
--snip--
現(xiàn)在運行游戲,外星人群將在屏幕上來回移動,并在抵達屏幕邊緣后向下移動。
> 射殺外星人
運行游戲會發(fā)現(xiàn),當我們發(fā)射的子彈擊中外星人時,子彈將穿過外星人,這是因為我們還沒有檢查碰撞。在游戲編程中,碰撞指的是游戲元素重疊在一起。要讓子彈能夠擊落外星人,我們將使用sprite.groupcollide()檢測兩個編組的成員之間的碰撞。
> 檢測子彈與外星人的碰撞
子彈擊中外星人時,我們要馬上知道,以便碰撞發(fā)生后讓外星人立即消失。為此,我們將在更新子彈的位置后立即檢測碰撞。
方法sprite.groupcollide()將每顆子彈的rect同每個外星人的rect進行比較,并返回一個字典,其中包含發(fā)生了碰撞的子彈和外星人。在這個字典中,每個鍵都是一顆子彈,而相應(yīng)的值都是被擊中的外星人。
# game_functions.py
--snip--
def update_bullets(aliens, bullets):
"""更新子彈的位置,并刪除已消失的子彈"""
# 更新子彈位置
bullets.update()
# 刪除已消失的子彈
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets))
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
# alien_invasion.py 補充傳遞實參aliens
--snip--
gf.update_bullets(aliens, bullets) # 更新子彈狀態(tài)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
?新增的這行代碼遍歷編組bullets中的每顆子彈,再遍歷編組aliens中的每個外星人。每當有子彈和外星人的rect重疊時,groupcollide()就在它返回的字典中添加一個鍵值對。兩個實參True告訴Pygame刪除發(fā)生碰撞的子彈和外星人。(要模擬能夠穿行到屏幕頂端的高能子彈——消滅它擊中的每個外星人,可將第一個布爾實參設(shè)置為False,并讓第二個布爾實參為True。這樣被擊中的外星人將消失,但所有的子彈都始終有效,直到抵達屏幕頂端后消失。)
此時運行游戲,被擊中的外星人將消失。如下圖(其中一部分外星人已被擊落):
?> 為測試創(chuàng)建大子彈
只需通過運行這個游戲就可以測試其很多功能,但有些功能在正常情況下測試起來比較煩瑣。例如,要測試代碼能否正確地處理外星人編組為空的情形,需花長時間將屏幕上的外星人都擊落。
測試有些功能時,可以修改游戲的某些設(shè)置,以便專注于游戲的特定方面。例如,可以縮小屏幕以減少需要擊落的外星人數(shù)量,也可以提高子彈的速度,以便能夠在單位時間內(nèi)發(fā)射大量子彈。 測試這個游戲時,我喜歡做的一項修改是增大子彈的尺寸,使其在擊中外星人后依然有效。
請嘗試將bullet_width設(shè)置為300,看看將所有外星人都射殺有多快! 類似這樣的修改可提高測試效率,還可能激發(fā)出如何賦予玩家更大威力的思想火花。(完成測試后,別忘了將設(shè)置恢復(fù)正常。)
> 生成新的外星人群
要在外星人群被消滅后又顯示一群外星人,首先需要檢查編組aliens是否為空。如果為空, 就調(diào)用create_fleet()。我們將在update_bullets()中執(zhí)行這種檢查,因為外星人都是在這里被消滅的:
# game_functions.py
--snip--
def update_bullets(ai_settings, screen, ship, aliens, bullets):
--snip--
if len(aliens) == 0:
# 刪除現(xiàn)有的子彈并新建一群外星人
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
# alien_invasion.py 更新其中對update_bullets()的調(diào)用,補充參數(shù)
--snip--
while True:
--snip--
gf.update_bullets(ai_settings, screen, ship, aliens, bullets) # 更新子彈狀態(tài)
--snip--
檢查到aliens為空時,我們用bullets.empty() 刪除現(xiàn)有的所有子彈
其意義在于以免殘留子彈攻擊到了下一輪次外星人
在去掉該語句后的測試過程中,殘留的子彈直接攻擊了第三排的外星人
現(xiàn)在,當前外星人群消滅干凈后,將立即出現(xiàn)一個新的外星人群。
> 提高子彈的速度
現(xiàn)在嘗試在游戲中射殺外星人,可能發(fā)現(xiàn)子彈的速度比以前慢,這是因為在每次循環(huán)中 ,Pygame需要做的工作變多了。想提高子彈的速度,可調(diào)整 settings.py 中 bullet_speed_factor的值。這里將其從1增大為3:
# settings.py
--snip--
# 子彈設(shè)置
self.bullet_speed_factor = 3 # 子彈速度
> 重構(gòu)update_bullets( )
重構(gòu)update_bullets(),使其不再完成那么多任務(wù)。(新建函數(shù)check_bullet_alien_collisions()來檢測子彈和外星人之間的碰撞,以及在整群外星人都被消滅干凈時采取相應(yīng)的措施。這避免了update_bullets()太長,簡化了后續(xù)的開發(fā)工作)
# game_functions.py
--snip--
def update_bullets(ai_settings, screen, ship, aliens, bullets):
"""更新子彈的位置,并刪除已消失的子彈"""
# 更新子彈位置
bullets.update()
# 刪除已消失的子彈
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# print(len(bullets))
check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
"""響應(yīng)子彈和外星人的碰撞"""
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
# 刪除現(xiàn)有的子彈并新建一群外星人
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
> 結(jié)束游戲
如果玩家沒能在足夠短的時間內(nèi)將整群外星人都消滅干凈,且有外星人撞到了飛船或抵達屏幕底端時,飛船將被摧毀。與此同時,我們還限制了可供玩家使用的飛船數(shù)。
> 檢測外星人和飛船碰撞
我們首先檢查外星人和飛船之間的碰撞,以便外星人撞上飛船時我們能夠作出合適的響應(yīng)。
我們在更新每個外星人的位置后立即檢測外星人和飛船之間的碰撞。
# game_functions.py
--snip--
def update_aliens(ai_settings, ship, aliens):
"""檢查是否有外星人位于屏幕邊緣,并更新整群外星人的位置"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
# 檢測外星人和飛船之間的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
print("Ship hit!!!")
# alien_invasion.py 更新調(diào)用函數(shù)參數(shù)
--snip--
while True:
--snip--
gf.update_aliens(ai_settings, ship, aliens) # 更新外星人狀態(tài)
gf.update_screen(ai_settings, screen, ship, aliens, bullets) # 更新屏幕
?方法spritecollideany()接受兩個實參:一個精靈和一個編組。它檢查編組是否有成員與精靈發(fā)生了碰撞,并在找到與精靈發(fā)生了碰撞的成員后就停止遍歷編組。在這里,它遍歷編組 aliens,并返回它找到的第一個與飛船發(fā)生了碰撞的外星人。(有外星人撞到飛船時,需要執(zhí)行的任務(wù)很多:需要刪除余下的所有外星人和子彈,讓飛船重新居中,以及創(chuàng)建一群新的外星人。編寫完成這些任務(wù)的代碼前,需要確定檢測外星人和飛船碰撞的方法是否可行。而為確定這一點,最簡單的方式是編寫一條print語句。)
現(xiàn)在運行游戲,就能看到每當外星人撞到飛船時,終端窗口都顯示該print語句:
> 響應(yīng)外星人和飛船碰撞
首先我們需要將一開始玩家擁有的飛船數(shù)存儲在settings.py的ship_limit中:
# settings.py
--snip--
# 飛船的設(shè)置
self.ship_speed_factor = 1.5
self.ship_limit = 3
--snip--
現(xiàn)在需要確定外星人與飛船發(fā)生碰撞時,該做些什么。我們不銷毀ship實例并創(chuàng)建一個新的ship實例,而是通過跟蹤游戲的統(tǒng)計信息來記錄飛船被撞了多少次(跟蹤統(tǒng)計信息還有助于記分)。 下面來編寫一個用于跟蹤游戲統(tǒng)計信息的新類——GameStats,并將其保存為文件game_stats.py:
# game_stats.py
class Gamestats():
"""跟蹤游戲的統(tǒng)計信息"""
def __init__(self, ai_settings):
"""初始化統(tǒng)計信息"""
self.ai_settings = ai_settings
self.reset_stats()
def reset_stats(self):
"""初始化在游戲運行期間可能變化的統(tǒng)計信息"""
self.ships_left = self.ai_settings.ship_limit
Q: 為什么在reset_stats()中初始化大部分統(tǒng)計信息,而非在__init__() 中直接初始化它們?
A: 在這個游戲運行期間,我們只創(chuàng)建一個GameStats實例,但每當玩家開始新游戲時,需要重置一些統(tǒng)計信息。我們在__init__()中調(diào)用方法reset_stats(),這樣創(chuàng)建GameStats實例時將妥善地設(shè)置這些統(tǒng)計信息,同時在玩家開始新游戲時也能調(diào)用reset_stats()。
我們還需對alien_invasion.py做些修改,以創(chuàng)建一個GameStats實例:
# alien_invasion.py
--snip--
from game_stats import Gamestats
def run_game():
--snip--
# 創(chuàng)建一個用于存儲游戲統(tǒng)計信息的實例
stats = Gamestats(ai_settings)
--snip--
有外星人撞到飛船時,我們將余下的飛船數(shù)減1,創(chuàng)建一群新的外星人,并將飛船重新放置到屏幕底端中央(我們還將讓游戲暫停一段時間,讓玩家在新外星人群出現(xiàn)前注意到發(fā)生了碰撞):
# game_functions.py
--snip--
from time import sleep
--snip--
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
"""響應(yīng)被外星人撞到的飛船"""
# 將ship_left減1
stats.ships_left -= 1
# 清空外星人列表和子彈列表
aliens.empty()
bullets.empty()
# 創(chuàng)建一群新的外星人,并將飛船放到屏幕底部中央
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship() # !新建函數(shù)于ship中
# 暫停
sleep(0.5)
def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
--snip--
# 檢測外星人和飛船之間的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
# alien_invasion.py 補充參數(shù)
--snip--
gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets) # 更新外星人狀態(tài)
--snip--
# ship.py 補充函數(shù)
--snip--
def center_ship(self):
"""讓飛船在屏幕上居中"""
self.center = self.screen_rect.centerx # 將飛船的屬性center設(shè)置為屏幕中心的x坐標
注意:我們根本沒有創(chuàng)建多艘飛船,在整個游戲運行期間,我們只創(chuàng)建了一個飛船實例,并在該飛船被撞到時將其居中,統(tǒng)計信息ships_left讓我們知道飛船是否用完。
?此時運行這個游戲,射殺幾次外星人,并讓一個外星人撞到飛船。游戲暫停后,將出現(xiàn)一群新的外星人,而飛船將在屏幕底端居中。但可以通過打印統(tǒng)計信息ships_left知,此時我們并未對其作出一定的限制反應(yīng),因此當3個飛船耗盡時,仍會繼續(xù)生成新飛船(-1)在屏幕底部居中。
> 有外星人到達屏幕底端
如果有外星人到達屏幕底端,我們將像有外星人撞到飛船那樣作出響應(yīng)。請?zhí)砑右粋€執(zhí)行這項任務(wù)的新函數(shù),并將其加到函數(shù)update_aliens():
# game_functions.py
--snip--
def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):
"""檢查是否有外星人到達了屏幕底端"""
screen_rect = screen.get_rect()
for alien in aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
# 像飛船被撞到一樣處理
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
break
def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
--snip--
# 檢查是否有外星人到達屏幕底端
check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
?現(xiàn)在運行游戲,每當有外星人撞到飛船或抵達屏幕底端時,都將出現(xiàn)一群新的外星人。
> 游戲結(jié)束
前面提到了,游戲現(xiàn)在不會結(jié)束,只是ship_left不斷變成更小的負數(shù)。下面在GameStats中添加一個作為標志的屬性game_active,以便玩家的飛船用完后結(jié)束游戲:
# game_stats.py
--snip--
def __init__(self, ai_settings):
--snip--
# 游戲剛啟動時處于活動狀態(tài)
self.game_active = True
并在ship_hit()中添加代碼,在玩家的飛船都用完后將game_active設(shè)置為False:
# game_functions.py
--snip--
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
"""響應(yīng)被外星人撞到的飛船"""
if stats.ships_left > 0: # 若還有飛船
stats.ships_left -= 1
--snip--
else: # 若飛船用完,將狀態(tài)置為False
stats.game_active = False
> 確定應(yīng)運行游戲的哪些部分
在主循環(huán)中,在任何情況下都需要調(diào)用check_events(),即便游戲處于非活動狀態(tài)時亦如此。 例如,我們需要知道玩家是否按了Q鍵以退出游戲,或單擊關(guān)閉窗口的按鈕。我們還需要不斷更新屏幕,以便在等待玩家是否選擇開始新游戲時能夠修改屏幕。其他的函數(shù)僅在游戲處于活動狀態(tài)時才需要調(diào)用,因為游戲處于非活動狀態(tài)時,我們不用更新游戲元素的位置。
# alien_invasion.py
--snip--
while True:
gf.check_events(ai_settings, screen, ship, bullets) # 檢查事件
if stats.game_active:
ship.update() # 更新飛船狀態(tài)
gf.update_bullets(ai_settings, screen, ship, aliens, bullets) # 更新子彈狀態(tài)
gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets) # 更新外星人狀態(tài)
gf.update_screen(ai_settings, screen, ship, aliens, bullets) # 更新屏幕
現(xiàn)在運行游戲,會發(fā)現(xiàn)它將在飛船用完后停止不動。
> 記分
我們將添加一個Play按鈕,用于根據(jù)需要啟動游戲以及在游戲結(jié)束后重啟游戲。 我們還將修改這個游戲,使其在玩家的等級提高時加快節(jié)奏,并實現(xiàn)一個記分系統(tǒng)。
> 添加Play按鈕
添加一個play按鈕,它在游戲開始前出現(xiàn),并在游戲結(jié)束后再次出現(xiàn),讓玩家能開始新游戲。
之前我們的游戲在玩家運行alien_invasion.py時就已經(jīng)開始了,現(xiàn)在我們對其進行修改,使游戲一開始處于非活動狀態(tài),并提示玩家單擊Play按鈕來開始游戲。
# game_stats.py
--snip--
def __init__(self, ai_settings):
--snip--
# 游戲剛啟動時處于活動狀態(tài)
self.game_active = False
> 創(chuàng)建Botton類
由于Pygame沒有內(nèi)置創(chuàng)建按鈕的方法,我們創(chuàng)建一個Button類,用于創(chuàng)建帶標簽的實心矩形。
# button.py
import pygame.font
class Button():
def __init__(self, ai_settings, screen, msg):
"""初始化按鈕的屬性"""
self.screen = screen
self.screen_rect = screen.get_rect()
# 設(shè)置按鈕的尺寸和其他屬性
self.width, self.height = 200, 50
self.button_color = (0, 255, 0) # 亮綠色
self.text_color = (255, 255, 255) # 白色
self.font = pygame.font.SysFont(None, 48)
# 創(chuàng)建按鈕的rect對象,并使其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 按鈕的標簽只需創(chuàng)建一次
self.prep_msg(msg)
- 導入模塊pygame.font,它讓Pygame能夠?qū)⑽谋句秩镜狡聊簧稀?/span>
- 方法__init__() 接受參數(shù)msg,msg是要在按鈕中顯示的文本。
- 使用 pygame.font.SysFont() 函數(shù)從系統(tǒng)內(nèi)加載字體。實參None讓Pygame使用默認字體,而48 指定了文本的字號。
- Pygame通過將你要顯示的字符串渲染為圖像來處理文本(調(diào)用prep_msg())
# button.py
--snip--
def prep_msg(self, msg):
"""將msg渲染為圖像,并使其在按鈕上居中"""
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
pygame.font.Font.render()
功能:在新Surface上繪制文本
屬性:render(text, antialias, color, background=None) -> Surface
- 創(chuàng)建一個新Surface,并在其上呈現(xiàn)指定的文本。pygame不提供直接在現(xiàn)有surface上繪制文本的方法:若要創(chuàng)建文本的圖像(Surface)必須使用 Font.render(),然后將此圖像blit到另一個surface上。
- 文本只能是單獨一行:換行符不能呈現(xiàn)??兆址?'x00') 會引發(fā)TypeError。Unicode和字符(byte)字符串都可以。對于Unicode字符串,只能識別UCS-2字符('u0001' to 'uFFFF')。任何更大的值都會引發(fā)UnicodeError。對于字符字符串,假定采用 LATIN1 編碼??逛忼X參數(shù)(antialias)是布爾值:如果為真,字符將具有平滑的邊。顏色參數(shù)是文本的顏色[例如:(0,0,255)表示藍色]??蛇x的背景參數(shù)是用于文本背景的顏色。如果沒有傳遞背景,文本外部的區(qū)域?qū)⑹峭该鞯摹?/li>
- 返回的Surface應(yīng)保存文本所需尺寸。(與Font.size()一致)。如果為文本傳遞空字符串,則將返回零像素寬和高的空白surface。
- 根據(jù)背景和抗鋸齒使用的類型,返回不同類型的曲面。出于性能原因,最好知道將使用哪種類型的圖像。如果不使用抗鋸齒,則返回圖像將始終是帶有雙調(diào)色板的8-bit圖像。如果背景是透明的,則設(shè)置colorkey。抗鋸齒圖像被渲染為24-bit RGB圖像。如果背景是透明的,將包括像素alpha。
?創(chuàng)建方法draw_button(),通過調(diào)用它將這個按鈕顯示到屏幕上。
- 調(diào)用screen.fill()來繪制代表按鈕的矩形
- 調(diào)用screen.blit()在屏幕上繪制文本圖像
# button.py
def draw_button(self):
# 繪制一個用顏色填充的按鈕,再繪制文本
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
?> 在屏幕上繪制按鈕
我們使用Button類來創(chuàng)建一個Play按鈕:
# alien_invasion.py
--snip--
from button import Button
def run_game():
--snip--
pygame.display.set_caption("Alien Invasion") # 設(shè)置窗口標題
# 創(chuàng)建Play按鈕
play_button = Button(ai_settings, screen, "Play")
--snip--
接下來,修改update_screen(),以便活動處于非活動狀態(tài)時顯示Play按鈕,并將play_button傳遞給update_screen(),以便能夠在屏幕更新時顯示按鈕:
# alien_invasion.py
--snip--
gf.update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button) # 更新屏幕
# game_functions.py
def update_screen(ai_settings, screen, stats, ship, aliens, bullets, play_button):
--snip--
# 如果游戲處于非活動狀態(tài),就繪制Play按鈕
# 為了讓Play按鈕位于所有屏幕元素上面,最后才繪制它
if not stats.game_active:
play_button.draw_button()
# 讓最近繪制的屏幕可見
pygame.display.flip()
現(xiàn)在運行游戲,將在屏幕中央看見一個Play按鈕:
?> 開始游戲
為在玩家單擊Play按鈕時開始新游戲,我們添加監(jiān)視與這個按鈕相關(guān)的鼠標事件:
# game_functions.py
--snip--
def check_events(ai_settings, screen, stats, play_button, ship, bullets):
# 響應(yīng)按鍵和鼠標事件
--snip--
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(stats, play_button, mouse_x, mouse_y)
def check_play_button(stats, play_button, mouse_x, mouse_y):
"""在玩家單擊Play按鈕時開始新游戲"""
if play_button.rect.collidepoint(mouse_x, mouse_y):
stats.game_active = True
# alien_invasion.py 更新實參
gf.check_events(ai_settings, screen, stats, play_button, ship, bullets)
玩家單擊屏幕時,Pygame將檢測到一個MOUSEBUTTONDOWN事件,為了讓游戲在玩家用鼠標單擊Play按鈕時作出響應(yīng)。我們使用了pygame.mouse. get_pos(),它返回一個元組,其中包含玩家單擊時鼠標的x和y坐標。我們將這些值傳遞給函數(shù)check_play_button(),其使用collidepoint()檢查鼠標單擊位置是否在Play按鈕的rect內(nèi),在則將game_active設(shè)置為True。
此時,點擊按鈕能開始游戲。游戲結(jié)束后,Play按鈕會重新顯示。
> 重置游戲
前面只處理了玩家第一次單擊Play按鈕的情況,而沒有處理游戲結(jié)束的情況,因為沒有重置導致游戲結(jié)束的條件。為在玩家每次單擊Play按鈕時都重置游戲,需要重置統(tǒng)計信息,刪除現(xiàn)有的外星人和子彈,創(chuàng)建一群新的外星人,并讓飛船居中:
# game_functions.py 更新參數(shù)
def check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets):
--snap--
check_play_button(ai_settings, screen,stats, play_button, ship, aliens, bullets, mouse_x, mouse_y)
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕時開始新游戲"""
if play_button.rect.collidepoint(mouse_x, mouse_y):
# 重置游戲統(tǒng)計信息
stats.reset_stats()
stats.game_active = True
# 清空外星人列表和子彈列表
aliens.empty()
bullets.empty()
# 創(chuàng)建一群新的外星人,并讓飛船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
# alien_invasion.py 更新參數(shù)
--snip--
while True:
gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) # 檢查事件
現(xiàn)在,每當玩家單擊Play按鈕時,這個游戲都將正確地重置。
> 將Play按鈕切換到非活動狀態(tài)
當前Play按鈕還存在一個問題,那就是即便Play按鈕不可見,玩家單擊其原來所在的區(qū)域時,游戲仍會作出響應(yīng)。游戲開始后,若玩家不小心單擊了Play按鈕原來所處的區(qū)域,游戲?qū)⒅匦麻_始。因此我們設(shè)計讓游戲僅在game_active為False時開始:
# game_functions.py
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕且游戲當前處于非活動狀態(tài)時開始新游戲"""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
# 重置游戲統(tǒng)計信息
--snip--
此時運行游戲,會發(fā)現(xiàn)開始游戲后單擊Play按鈕原所在區(qū)域沒有任何影響了。
> 隱藏光標
為讓玩家能夠開始游戲,我們要讓光標可見,但游戲開始后,光標只會添亂,因此我們將游戲開始后的光標隱藏(在游戲處于活動狀態(tài)時):
# game_functions.py
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕且游戲當前處于非活動狀態(tài)時開始新游戲"""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
# 隱藏光標
pygame.mouse.set_visible(False)
--snip--
pygame的mouse鼠標事件:通過向set_visible()傳遞False,在光標位于窗口時將其隱藏。
游戲結(jié)束后,我們將重新顯示光標,讓玩家能夠單擊Play按鈕來開始新游戲:
# game_functions.py
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
"""響應(yīng)被外星人撞到的飛船"""
if stats.ships_left > 0:
--snip--
else:
stats.game_active = False
pygame.mouse.set_visible(True)
> 提高等級
每當玩家將屏幕上的外星人都消滅干凈后,加快游戲的節(jié)奏
> 修改速度設(shè)置
我們首先重新組織Settings類,將游戲設(shè)置劃分為靜態(tài)的和動態(tài)的兩組:
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
"""初始化游戲的設(shè)置"""
# 屏幕設(shè)置 900 600
self.screen_width = 900
self.screen_height = 600
self.bg_color = (230, 230, 230)
# 飛船的設(shè)置
self.ship_speed_factor = 1.5
self.ship_limit = 3
# 子彈設(shè)置
self.bullet_speed_factor = 3 # 子彈速度
self.bullet_width = 3 # 寬
self.bullet_height = 15 # 高
self.bullet_color = 60, 60, 60 # 顏色(深灰)
self.bullets_allowed = 3 # 允許的最大子彈數(shù)
# 外星人設(shè)置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10 # 向下移動速度
# 以什么樣的速度加快游戲節(jié)奏
self.speedup_scale = 1.1
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
"""初始化隨游戲進行而變化的設(shè)置"""
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 3
self.alien_speed_factor = 1
# fleet_direction為1表示向右移,為-1表示向左移
self.fleet_direction = 1
def increase_speed(self):
"""提高速度設(shè)置"""
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed_factor *= self.speedup_scale
# game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
"""響應(yīng)子彈和外星人的碰撞"""
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
# 刪除現(xiàn)有的子彈,加快游戲節(jié)奏并新建一群外星人
bullets.empty()
ai_settings.increase_speed()
create_fleet(ai_settings, screen, ship, aliens)
?我們在整群外星人都被消滅后調(diào)用increase_speed()來加快游戲的節(jié)奏,再創(chuàng)建一群新的外星人。
> 重置速度
每當玩家開始新游戲時,我們都需將發(fā)生了變化的設(shè)置重置為初始值,否則新游戲開始時,速度設(shè)置將是前一次游戲增加了的值:
# game_functions.py
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕且游戲當前處于非活動狀態(tài)時開始新游戲"""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
# 重置游戲設(shè)置
ai_settings.initialize_dynamic_settings()
--snip--
> 記分
實現(xiàn)一個記分系統(tǒng),以實時地跟蹤玩家的得分,并顯示最高得分,當前等級和余下的飛船數(shù)。
我們在GameStats中添加一個score屬性,為在每次開始游戲時都重置得分,我們在reset_stats()中初始化score:
# game_stats.py
class Gamestats():
--snip--
def reset_stats(self):
"""初始化在游戲運行期間可能變化的統(tǒng)計信息"""
self.ships_left = self.ai_settings.ship_limit
self.score = 0 # 玩家得分
> 顯示得分
為在屏幕上顯示得分,我們首先創(chuàng)建一個新類Scoreboard:
# scoreboard.py
import pygame.font
class Scoreboard():
"""顯示得分信息的類"""
def __init__(self, ai_settings, screen, stats):
"""初始化顯示得分涉及的屬性"""
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
# 顯示得分信息時使用的字體設(shè)置
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
# 準備初始得分圖像
self.prep_score()
def prep_score(self):
"""將得分轉(zhuǎn)換為一幅渲染的圖像"""
score_str = str(self.stats.score) # 將數(shù)字值轉(zhuǎn)換為字符串再傳遞
self.score_image = self.font.render(score_str, True, self.text_color,
self.ai_settings.bg_color)
# 將得分放在屏幕右上角
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def show_score(self):
"""在屏幕上顯示得分"""
self.screen.blit(self.score_image, self.score_rect)
為確保得分始終錨定在屏幕右邊,我們創(chuàng)建了一個名為score_rect的rect,讓其右邊緣與屏幕右邊緣相距20像素,并讓其上邊緣與屏幕上邊緣也相距20像素。
pygame.surface.blit( )方法
旨在將一個圖像繪制到另一個圖像上
其主要格式:blit(source,dest=None,special_flags=0)
將source參數(shù)指定的Surface對象繪制到該對象上。dest參數(shù)指定繪制的位置,其值可以是source的左上角坐標,如果傳入一個rect對象給dest,那么blit()會使用它的左上角坐標。
> 創(chuàng)建記分牌
我們在alien_invasion.py中創(chuàng)建一個Scoreboard實例:
# alien_invasion.py
--snip--
from scoreboard import Scoreboard
def run_game():
--snip--
# 創(chuàng)建一個用于存儲游戲統(tǒng)計信息的實例,并創(chuàng)建記分牌
stats = Gamestats(ai_settings)
sb = Scoreboard(ai_settings, screen, stats)
--snip--
為顯示得分,我們修改update_screen() 并在alien_invasion.py中補充實參:
# game_functions.py
def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
--snip--
# 顯示得分
sb.show_score()
# 如果游戲處于非活動狀態(tài),就繪制Play按鈕
# 為了讓Play按鈕位于所有屏幕元素上面,最后才繪制它
if not stats.game_active:
play_button.draw_button()
# 讓最近繪制的屏幕可見
pygame.display.flip()
# alien_invasion.py
--snip--
gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button) # 更新屏幕
--snip--
現(xiàn)在運行游戲,將在屏幕右上角看到0:
> 在外星人被消滅時更新得分
為在屏幕上實時顯示得分,每當有外星人被擊中時都需更新stats.score的值,再調(diào)用prep_score()更新得分圖像。但在此之前,我們需要指定玩家每擊落一個外星人都將得到多少個點。隨著游戲的進行,我們將提高每個外星人值的點數(shù)。為確保每次開始新游戲時這個值都會被重置,我們在initialize_dynamic_settings()中設(shè)置它。
# settings.py
--snip--
def initialize_dynamic_settings(self):
"""初始化隨游戲進行而變化的設(shè)置"""
--snip--
# 記分
self.alien_points = 50
在check_bullet_alien_collisions()中,每當有外星人被擊落時,都更新得分:
# game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
"""響應(yīng)子彈和外星人的碰撞"""
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions: # 檢查是否有返回值
stats.score += ai_settings.alien_points
sb.prep_score()
--snip--
# 更新實參
def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
"""更新子彈的位置,并刪除已消失的子彈"""
--snip--
check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)
# alien_invasion.py 更新實參
--snip--
gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) # 更新子彈狀態(tài)
--snip--
有子彈撞到外星人時,Pygame返回一個字典(collisions)。我們檢查這個字典是否存在,如果存在,就將得分加上一個外星人值的點數(shù)。接下來,我們調(diào)用 prep_score()來創(chuàng)建一幅顯示最新得分的新圖像。(更新圖像后由update_screen調(diào)用blit()繪制新的得分表)
現(xiàn)在運行游戲,會發(fā)現(xiàn)擊落外星人時,得分將不斷增加:
> 將消滅的每個外星人的點數(shù)都計入得分
當前,我們的代碼可能遺漏了一些被消滅的外星人。例如,如果在一次循環(huán)中有兩顆子彈射中了外星人,或者因子彈更寬而同時擊中了多個外星人,玩家將只能得到一個被消滅的外星人的點數(shù)。為修復(fù)這種問題,我們來調(diào)整檢測子彈和外星人碰撞的方式。
在check_bullet_alien_collisions()中,與外星人碰撞的子彈都是字典collisions中的一個鍵(key);而與每顆子彈相關(guān)的值都是一個列表(value),其中包含該子彈撞到的外星人。我們遍歷字典collisions,確保將消滅的每個外星人的點數(shù)都記入得分:
# game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
"""響應(yīng)子彈和外星人的碰撞"""
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions: # 檢查是否有返回值
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
--snip--
將子彈寬度調(diào)大核實結(jié)果是否正確:(圖為子彈兩次打中共8個外星人得分)
> 提高點數(shù)
玩家等級提升時,外星人的點數(shù)也應(yīng)相應(yīng)提高。
# settings.py
class Settings:
"""存儲《外星人入侵》的所有設(shè)置的類"""
def __init__(self):
--snip--
# 以什么樣的速度加快游戲節(jié)奏
self.speedup_scale = 1.1
# 外星人點數(shù)的提高速度
self.score_scale = 1.5
self.initialize_dynamic_settings()
def increase_speed(self):
"""提高速度設(shè)置和外星人點數(shù)"""
# 提高速度設(shè)置
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed_factor *= self.speedup_scale
# 提高外星人點數(shù)
self.alien_points = int(self.alien_points * self.score_scale)
print(self.alien_points)
此時運行游戲,通過print語句檢測等級提高后點數(shù)的變化:
確認完效果后,記得將print語句刪去,以免影響游戲性能。
> 將得分圓整
大多數(shù)街機風格的射擊游戲都將得分顯示為10的整數(shù)倍,下面讓我們的記分系統(tǒng)遵循這個原則。我們還將設(shè)置得分的格式,在大數(shù)字中添加用逗號表示的千位分隔符。
# scoreboard.py
--snip--
def prep_score(self):
"""將得分轉(zhuǎn)換為一幅渲染的圖像"""
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color,
self.ai_settings.bg_color)
--snip--
函數(shù)round()通常讓小數(shù)精確到小數(shù)點后多少位,其中小數(shù)位數(shù)是由第二個實參指定的。如果將第二個實參指定為負數(shù),round()將圓整到最近的10、100、1000等整數(shù)倍。
?注意 在Python 2.7中,round()總是返回一個小數(shù)值,因此我們使用int()來確保報告的得分為 整數(shù)。如果你使用的是Python 3,可省略對int()的調(diào)用。
下面使用了一個字符串格式設(shè)置指令,它讓Python將數(shù)值轉(zhuǎn)換為字符串時在其中插入逗號, 例如,輸出1,000,000而不是1000000。
> 最高得分
跟蹤并顯示最高得分,給玩家提供要超越的目標。鑒于在任何情況下都不會重置最高得分,我們將其存儲在__init__()中:
# game_stats.py
class Gamestats():
"""跟蹤游戲的統(tǒng)計信息"""
def __init__(self, ai_settings):
--snip--
# 在任何情況下都不應(yīng)重置最高得分
self.high_score = 0
?編寫方法準備包含最高得分的圖像以及圖像的顯示:
# scoreboard.py
import pygame.font
class Scoreboard():
"""顯示得分信息的類"""
def __init__(self, ai_settings, screen, stats):
--snip--
# 準備包含最高得分和當前得分的圖像
self.prep_score()
self.prep_high_score()
def prep_high_score(self):
"""將得分最高轉(zhuǎn)換為渲染的圖像"""
high_score = int(round(self.stats.high_score, -1))
high_score_str = "{:,}".format(high_score)
self.high_score_image = self.font.render(high_score_str, True,
self.text_color, self.ai_settings.bg_color)
#將最高得分放在屏幕頂部中央
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.screen_rect.top
def show_score(self):
"""在屏幕上顯示當前得分和最高得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
?為檢測是否有新的最高得分誕生,我們添加新函數(shù)check_high_score(),它使用stats來比較當前得分和最高得分,并在必要時使用sb來修改最高得分圖像:
# game_functions.py
def check_high_score(stats, sb):
"""檢查是否誕生了最高得分"""
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
每當有外星人被消滅,都需要在更新得分后調(diào)用 check_high_score():
# game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
"""響應(yīng)子彈和外星人的碰撞"""
# 檢查是否有子彈擊中了外星人(擊中則刪除相應(yīng)的子彈和外星人)
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions: # 檢查是否有返回值
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
check_high_score(stats, sb)
此時兩次運行游戲,第一次時,當前得分就是最高得分,因此兩處均顯示為當前得分。第二次時,可以明顯看到最高得分與當前得分的區(qū)別。
> 顯示等級
先添加一個表示等級的屬性,為了每次開始新游戲時都重置等級,我們在reset_stats()初始化:
# game_stats.py
def reset_stats(self):
"""初始化在游戲運行期間可能變化的統(tǒng)計信息"""
self.ships_left = self.ai_settings.ship_limit
self.score = 0 # 玩家得分
self.level = 1 # 玩家等級
我們調(diào)用新方法prep_level(),讓其能夠在當前得分下方顯示當前等級,并更新show_score():
# scoreboard.py
import pygame.font
class Scoreboard():
"""顯示得分信息的類"""
def __init__(self, ai_settings, screen, stats):
--snip--
# 準備包含最高得分和當前得分的圖像
self.prep_score()
self.prep_high_score()
self.prep_level()
def prep_level(self):
"""將等級轉(zhuǎn)換為渲染的圖像"""
self.level_image = self.font.render(str(self.stats.level), True,
self.text_color, self.ai_settings.bg_color)
# 將等級放在當前得分下方
self.level_rect = self.level_image.get_rect()
self.level_rect.right = self.score_rect.right
self.level_rect.top = self.score_rect.bottom + 10
def show_score(self):
"""在屏幕上顯示當前得分和最高得分"""
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.screen.blit(self.level_image,self.level_rect)
在整群外星人被消滅時,我們設(shè)置其提升一個等級:
# game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
--snip--
if len(aliens) == 0:
# 刪除現(xiàn)有的子彈,加快游戲節(jié)奏
bullets.empty()
ai_settings.increase_speed()
# 提高等級
stats.level += 1
sb.prep_level()
# 新建一群外星人
create_fleet(ai_settings, screen, ship, aliens)
為確保開始新游戲時更新記分和等級圖像,在按鈕Play被單擊時觸發(fā)重置:
# game_functions.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕且游戲當前處于非活動狀態(tài)時開始新游戲"""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
--snip--
# 重置記分牌圖像
sb.prep_score()
sb.prep_high_score()
sb.prep_level()
--snip--
# 更新參數(shù)(新增參數(shù)sb)
def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
# 響應(yīng)按鍵和鼠標事件
for event in pygame.event.get(): # 訪問python檢測到的事件
--snip--
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)
# alien_invasion.py 更新參數(shù)(新增參數(shù)sb)
while True:
gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets) # 檢查事件
?此時運行游戲如下:
注:在一些經(jīng)典游戲中,得分帶標簽,如Score、HighScore和Level。我們沒有顯示這些標簽, 因為開始玩這款游戲后,每個數(shù)字的含義將一目了然。要包含這些標簽,只需在Scoreboard 中調(diào)用font.render()前,將它們添加到得分字符串中即可。
> 顯示余下的飛船數(shù)
最后,我們來顯示玩家還有多少艘飛船,但使用圖形而不是數(shù)字。為此,我們在屏幕左上角繪制飛船圖像來指出還余下多少艘飛船,就像眾多經(jīng)典的街機游戲那樣。
首先,需要讓Ship繼承Sprite,以便能夠創(chuàng)建飛船編組:
# ship.py
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, ai_settings, screen): # 后者指定了要將飛船繪往哪
"""初始化飛船并設(shè)置其初始位置"""
super(Ship, self).__init__()
--snip--
接下來,我們修改Scoreboard,在其中創(chuàng)建一個可供顯示的飛船編組self.ships,并通過循環(huán)向其內(nèi)存儲飛船實例。為在屏幕上顯示飛船,我們對編組調(diào)用draw(),Pygame將繪制每艘飛船:
# scoreboard.py
import pygame.font
from pygame.sprite import Group
from ship import Ship
class Scoreboard():
"""顯示得分信息的類"""
def __init__(self, ai_settings, screen, stats):
--snip--
def prep_ships(self):
"""顯示還剩下多少艘飛船"""
self.ships = Group()
for ship_number in range(self.stats.ships_left):
ship = Ship(self.ai_settings, self.screen)
ship.rect.x = 10 + ship_number * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
def show_score(self):
--snip--
# 繪制飛船 ( 對編組調(diào)用draw,Pygame將繪制每一艘飛船)
self.ships.draw(self.screen)
為在游戲開始時讓玩家知道他有多少艘飛船,我們在開始游戲時調(diào)用prep_ships():
# game_functions.py
def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
"""在玩家單擊Play按鈕且游戲當前處于非活動狀態(tài)時開始新游戲"""
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
--snip--
# 重置記分牌圖像
sb.prep_score()
sb.prep_high_score()
sb.prep_level()
sb.prep_ships()
還需在飛船被外星人撞到時調(diào)用prep_ships(),從而在玩家損失一艘飛船時更新飛船圖像:
# game_functions.py
def ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets):
"""響應(yīng)被外星人撞到的飛船"""
if stats.ships_left > 0:
# 將ship_left減1
stats.ships_left -= 1
# print(stats.ships_left)
# 更新記分牌
sb.prep_ships()
--snip--
# 更新參數(shù)(新增參數(shù)sb)
def update_aliens(ai_settings, stats, screen, sb, ship, aliens, bullets):
--snip--
# 檢測外星人和飛船之間的碰撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets)
# 檢查是否有外星人到達屏幕底端
check_aliens_bottom(ai_settings, stats, screen, sb, ship, aliens, bullets)
def check_aliens_bottom(ai_settings, stats, screen, sb, ship, aliens, bullets):
"""檢查是否有外星人到達了屏幕底端"""
screen_rect = screen.get_rect()
for alien in aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
# 像飛船被撞到一樣處理
ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets)
break
# alien_invasion.py
while True:
--snip--
if stats.game_active:
--snip--
gf.update_aliens(ai_settings, stats, screen, sb, ship, aliens, bullets) # 更新外星人狀態(tài)
此時運行游戲:
?嘗試go die 一次:文章來源:http://www.zghlxwxcb.cn/news/detail-484598.html
> End
最終呈現(xiàn)結(jié)果如下(此處調(diào)整了high_score的高度):文章來源地址http://www.zghlxwxcb.cn/news/detail-484598.html
到了這里,關(guān)于Python Project- Alien_invasion(外星人入侵)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!