《Python編程:從入門到實踐》筆記。
本章主要是對上一篇的繼續,添加「外星人」,「外星人」與飛船的交互。
開發較大的項目時,進入每一個開發階段前回顧一下開發計劃,搞清楚接下來要經過代碼實現哪些功能相當重要。本篇將設計一下內容:python
但願各位上一篇的代碼沒有刪掉。在開始新的代碼前,咱們先在前面的check_keydown_events()
函數中添加「經過快捷鍵Q結束遊戲」的代碼:編程
def check_keydown_event(event, ship, ai_settings, screen, bullets): -- snip -- elif event.key == pygame.K_q: sys.exit()
首先咱們須要編寫一個外星人Alien
類。新建alien.py
模塊,在其中加入以下代碼:微信
import pygame from pygame.sprite import Sprite class Alien(Sprite): """表示單個外星人的類""" def __init__(self, ai_settings, screen): """初始化外星人並設置其起始位置""" super(Alien, self).__init__() self.screen = screen self.ai_settings = ai_settings # 加載外星人圖像,並設置其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)
它和Bullet
類同樣繼承自Sprite
類。如今開始建立多行外星人。ide
首先在game_functions.py
模塊中添加create_fleet()
函數用於建立外星艦隊:函數
def create_fleet(ai_settings, screen, ship, aliens): """建立外星艦隊""" alien = Alien(ai_settings, screen) # 計算每行能放多少個 number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) # 計算能放多少行 number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height) # 嵌套循環建立外星艦隊 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)
而後咱們依次補充下面三個函數(注意各個函數的參數),這三個函數也位於game_functions.py
中:測試
get_number_aliens_x()
: 計算一行能放多少個外星人網站
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
get_number_rows()
: 計算能放多少行外星人idea
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
create_alien()
: 建立外星人spa
def create_alien(ai_settings, screen, aliens, alien_number, row_number): """建立一個外星人並將其放在當前行""" alien = Alien(ai_settings, screen) # 下面就是根據上面的公式計算每個外星人在窗口中的位置(這是左上角的座標) alien.x = alien.rect.width * (1 + 2 * alien_number) alien.rect.x = alien.x alien.rect.y = alien.rect.height * (1 + 2 * row_number) aliens.add(alien)
如今咱們還須要修改update_screen()
函數:.net
def update_screen(ai_settings, screen, ship, bullets, aliens): -- snip -- # 繪製外星人,放在繪製子彈的代碼後面,讓外星人的圖像覆蓋掉子彈的圖像 aliens.draw(screen) -- snip --
注意,該函數增長了一個參數aliens
,這是個Group
對象,因此代碼中的draw()
方法也跟前一篇中的bullets.update()
方法同樣,一行代碼更新全部對象。
在主程序中添加建立外星人的代碼:
def run_game(): -- snip -- gf.create_fleet(ai_settings, screen, ship, aliens) while True: -- snip -- # 比以前代碼多傳入了一個aliens參數 gf.update_screen(ai_settings, screen, ship, bullets, aliens) -- snip --
如今咱們執行程序將會獲得以下結果:
咱們將讓外星艦隊在窗體中向右移動,撞到屏幕邊緣後下以必定距離降低,再沿反方向移動,直到外星人被消滅,或外星人撞上飛船,或有外星人到達窗體底部。
class Settings: def __init__(self): -- snip -- self.fleet_drop_speed = 10 # 外星艦隊方向標誌:1向右,-1向左 # 也能夠用如left, right之類的標誌,但這樣會很麻煩 self.fleet_direction = 1
咱們須要在Alien
類中添加兩個方法,一個用於檢測窗體邊緣,一個用於更新Alien
對象:
class Alien(Sprite): -- snip -- def check_edges(self): """若是外星人位於屏幕邊緣則返回True""" screen_rect = self.screen.get_rect() return self.rect.right >= screen_rect.right or self.rect.left <= 0 def update(self): """向右移動外星人""" # 之後這樣的方式會用的不少 self.x += self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction self.rect.x = self.x
若是使用文本值來控制方向,那就須要添加if-else
語句來檢測艦隊移動方向。鑑於只有兩個可能的方向,這裏使用-1
和1
來表示,這樣更容易改變外星人對象的座標。
首先,咱們在該模塊中添加一個更新外星艦隊的函數update_aliens()
:
def update_aliens(ai_settings, aliens): """檢查是否有外星人位於屏幕邊緣,並更新外星艦隊中全部外星人的位置""" check_fleet_edges(ai_settings, aliens) aliens.update() # 「一鍵更新」
check_fleet_edges()
函數用於檢測艦隊是否碰到了窗體邊緣,代碼以下:
def check_fleet_edges(ai_settings, aliens): """有外星人到達邊緣時採起相應的措施""" # 檢測艦隊中每個外星人是否碰到了窗體邊緣 for alien in aliens.sprites(): if alien.check_edges(): change_fleet_direction(ai_settings, aliens) break
change_fleet_direction()
函數用於改變艦隊的移動方向,以及讓艦隊向下移動,代碼以下:
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
上面三個函數就是在game_functions.py
中的全部變更。
在該模塊中咱們只須要在while
循環中添加一行代碼:
# 開始遊戲的主循環 while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) # 添加對外星艦隊的修改 gf.update_aliens(ai_settings, aliens) gf.update_screen(ai_settings, screen, ship, bullets, aliens)
最後運行主程序,獲得以下效果:
截了一張靜態圖,實際是動態的。
對於當前的程序,若是發射子彈,子彈將穿過外星人,而不是擊殺,下面咱們繼續完善該項目,使其能擊殺外星人。而要實現這一點,關鍵就是要檢測到子彈圖像與外星人圖像是否重疊,重疊了則表示擊中。
爲什麼檢測子彈與衛星人的碰撞,咱們須要修改update_bullets()
函數,這裏咱們增長了update_bullets()
的參數,還調用了一個新函數:
def update_bullets(bullets, aliens, ship, screen, ai_settings): -- snip -- check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)
函數check_bullet_alien_collisions()
用於檢測子彈與外星人的碰撞,當外星人被消滅光時,清空現有子彈,並生成新的外星艦隊,它的代碼以下:
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets): """檢測是否有子彈擊中了外星人,若是有,就刪除相應的子彈和外星人""" # 下一篇中咱們將用該變量實現分數統計 collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) #若是外星人被消滅光,則生成新的外星人艦隊 if len(aliens) == 0: # 刪除現有的子彈並建立新的艦隊 bullets.empty() create_fleet(ai_settings, screen, ship, aliens)
sprite.groupcollide()
方法用於檢測對象之間的碰撞,它將bullets
中的每一個子彈的rect
與aliens
中的每一個外星人的rect
進行比較,並返回一個字典。該字典以第一個參數中的對象爲鍵,以第二個參數中的鍵爲值,在這裏,以bullets
中發生了碰撞的bullet
爲鍵,它的值爲與之碰撞的alien
(不是aliens
)!第三個參數表示是否刪除第一個參數中發生了碰撞的對象,而四個參數表示是否刪除第二個參數中發生了碰撞的對象。
只須要修改調用update_bullets()
函數的那行代碼便可,增長几個參數:
gf.update_bullets(bullets, aliens, ship, screen, ai_settings)
基礎功能基本完成。
對於上述代碼,咱們可能須要測試當消滅完外星人後,新的艦隊是否能被正確建立等,若是咱們以如今遊戲的設定,即子彈速度爲1,子彈寬度爲3,那測試起來將會很痛苦。此時,咱們能夠修改修改遊戲的參數,好比將子彈寬度修改成300,子彈速度修改成3,這樣就至關於對遊戲進行了快進,此時代碼的運行效果以下:
不過最後記得將參數修改回去。
接下來咱們實現外星人碰到飛船,外星人抵達窗體底部,飛船數用光致使遊戲結束的代碼。
首先咱們建立一個用於存儲遊戲信息的GameStats
類,存放在game_stats.py
文件中:
class GameStats: """跟蹤遊戲的統計信息""" def __init__(self, ai_settings): """初始化統計信息""" # 用於控制遊戲啓動與否 self.game_active = True self.ai_settings = ai_settings self.reset_stats() def reset_stats(self): """初始化在遊戲運行期間可能變化的統計信息""" # 重置飛船數 self.ships_left = self.ai_settings.ship_limit
從上述代碼能夠看出,咱們須要在settings.py
中添加一項表示「飛船數」的信息:
class Settings: def __init__(self): """初始化遊戲的設置""" # 屏幕設置 -- snip -- # 設置飛船數量限制 self.ship_limit = 3 -- snip --
咱們在更新每一個外星人的位置後當即檢測外星人和飛船之間的碰撞,隨後再檢查外星人是否到達了窗體底部。修改update_aliens()
函數,使用sprite
中的spritecollideany()
方法來檢測碰撞:將第二參數中的每個元素與第一個參數比較,檢測是否碰撞,返回第二個參數中第一個發生碰撞的對象,若是沒有發生碰撞則返回None
:
# 增長了參數和碰撞檢測 def update_aliens(ai_settings, aliens, ship, screen, bullets, stats): -- snip -- # 檢測外星人和飛船之間的碰撞 if pygame.sprite.spritecollideany(ship, aliens): ship_hit(ai_settings, stats, screen, ship, aliens, bullets) check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
爲此咱們須要增長兩個函數:
ship_hit()
:當外星人與飛船發生碰撞時,調用次函數
-- snip -- from time import sleep def ship_hit(ai_settings, stats, screen, ship, aliens, bullets): """響應被外星人撞到的飛船""" # 將ship_left減1 if stats.ships_left > 0: stats.ships_left -= 1 # 清空外星人列表和子彈列表 aliens.empty() bullets.empty() # 建立一羣新的外星人,並將飛船恢復到初始位置 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship() # 暫停 sleep(0.5) else: stats.game_active = False
從上面的代碼還能夠看出,咱們還須要在Ship
類中添加一個center_ship()
方法:
def center_ship(self): """讓飛船在屏幕上居中""" self.center = self.screen_rect.centerx
check_aliens_bottom()
: 當飛船到達窗體底部時調用次函數
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
修改遊戲的循環部分:
# 開始遊戲的主循環 while True: gf.check_events(ai_settings, screen, ship, bullets) # 決定程序運行時該執行的部分 if stats.game_active: ship.update() gf.update_bullets(bullets, aliens, ship, screen, ai_settings) gf.update_aliens(ai_settings, aliens, ship, screen, bullets, stats) gf.update_screen(ai_settings, screen, ship, bullets, aliens)
在主循環中,任何狀況下都須要調用check_events()
,即便遊戲處於非活動狀態;還須要不斷更新屏幕,以便在等待玩家是否選擇從新開始遊戲時可以修改屏幕;其餘函數僅在遊戲處於活動狀態時太須要調用。
本篇講述了:
game_active
來判斷遊戲是否結束。下一篇中,同時也是本項目的最後一篇,咱們將:
迎你們關注個人微信公衆號"代碼港" & 我的網站 www.vpointer.net ~