Python學習之路12-外星人

《Python編程:從入門到實踐》筆記。
本章主要是對上一篇的繼續,添加「外星人」,「外星人」與飛船的交互。

1. 回顧項目

開發較大的項目時,進入每一個開發階段前回顧一下開發計劃,搞清楚接下來要經過代碼實現哪些功能相當重要。本篇將設計一下內容:python

  • 研究即有代碼,肯定實現新功能前是否須要重構代碼
  • 在屏幕左上角添加一個外星人,並指定合適的邊距
  • 根據第一個外星人的邊距和屏幕尺寸計算屏幕上可容納多少個外星人。編寫一個循環來填滿屏幕的上半部分
  • 讓外星艦隊向兩邊和下方移動,直到外星人被所有擊落,或有外星人撞到飛船,或有外星人抵達屏幕底部。若是全部外星人都被擊落,再建立一批外星人。若是有外星人撞到飛船或到達屏幕底部,則銷燬飛船並再建立一羣外星人。
  • 限制玩家可用的飛機數,消耗完則遊戲結束

但願各位上一篇的代碼沒有刪掉。在開始新的代碼前,咱們先在前面的check_keydown_events()函數中添加「經過快捷鍵Q結束遊戲」的代碼:編程

def check_keydown_event(event, ship, ai_settings, screen, bullets):
    -- snip --
    elif event.key == pygame.K_q:
        sys.exit()

2. 建立外星人

首先咱們須要編寫一個外星人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

2.1 修改game_functions.py模塊

首先在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()方法同樣,一行代碼更新全部對象。

2.2 修改alien_invasion.py模塊

在主程序中添加建立外星人的代碼:

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

如今咱們執行程序將會獲得以下結果:

圖片描述

3. 讓外星艦隊動起來

咱們將讓外星艦隊在窗體中向右移動,撞到屏幕邊緣後下以必定距離降低,再沿反方向移動,直到外星人被消滅,或外星人撞上飛船,或有外星人到達窗體底部。

3.1 補充settings.py模塊

class Settings:
    def __init__(self):
        -- snip --
        self.fleet_drop_speed = 10
        # 外星艦隊方向標誌:1向右,-1向左
        # 也能夠用如left, right之類的標誌,但這樣會很麻煩
        self.fleet_direction = 1

3.2 修改alien.py模塊

咱們須要在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語句來檢測艦隊移動方向。鑑於只有兩個可能的方向,這裏使用-11來表示,這樣更容易改變外星人對象的座標。

3.3 修改game_functions.py模塊

首先,咱們在該模塊中添加一個更新外星艦隊的函數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中的全部變更。

3.4 修改alien_invasion.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)

最後運行主程序,獲得以下效果:

圖片描述

截了一張靜態圖,實際是動態的。

4. 擊殺外星人

對於當前的程序,若是發射子彈,子彈將穿過外星人,而不是擊殺,下面咱們繼續完善該項目,使其能擊殺外星人。而要實現這一點,關鍵就是要檢測到子彈圖像與外星人圖像是否重疊,重疊了則表示擊中。

4.1 修改game_functions.py

爲什麼檢測子彈與衛星人的碰撞,咱們須要修改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中的每一個子彈的rectaliens中的每一個外星人的rect進行比較,並返回一個字典。該字典以第一個參數中的對象爲鍵,以第二個參數中的鍵爲值,在這裏,以bullets中發生了碰撞的bullet爲鍵,它的值爲與之碰撞的alien(不是aliens)!第三個參數表示是否刪除第一個參數中發生了碰撞的對象,而四個參數表示是否刪除第二個參數中發生了碰撞的對象。

4.2 修改alien_invasion.py

只須要修改調用update_bullets()函數的那行代碼便可,增長几個參數:

gf.update_bullets(bullets, aliens, ship, screen, ai_settings)

基礎功能基本完成。

4.3 測試技巧補充

對於上述代碼,咱們可能須要測試當消滅完外星人後,新的艦隊是否能被正確建立等,若是咱們以如今遊戲的設定,即子彈速度爲1,子彈寬度爲3,那測試起來將會很痛苦。此時,咱們能夠修改修改遊戲的參數,好比將子彈寬度修改成300,子彈速度修改成3,這樣就至關於對遊戲進行了快進,此時代碼的運行效果以下:

圖片描述

不過最後記得將參數修改回去。

5. 結束遊戲

接下來咱們實現外星人碰到飛船,外星人抵達窗體底部,飛船數用光致使遊戲結束的代碼。

5.1 建立GameStats類

首先咱們建立一個用於存儲遊戲信息的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

5.2 修改settings.py

從上述代碼能夠看出,咱們須要在settings.py中添加一項表示「飛船數」的信息:

class Settings:
    def __init__(self):
        """初始化遊戲的設置"""
        # 屏幕設置
        -- snip --
        # 設置飛船數量限制
        self.ship_limit = 3
        -- snip --

5.3 響應飛船與外星人的碰撞,修改game_functions.py

咱們在更新每一個外星人的位置後當即檢測外星人和飛船之間的碰撞,隨後再檢查外星人是否到達了窗體底部。修改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

5.4 修改主程序alien_invasion.py

修改遊戲的循環部分:

# 開始遊戲的主循環
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(),即便遊戲處於非活動狀態;還須要不斷更新屏幕,以便在等待玩家是否選擇從新開始遊戲時可以修改屏幕;其餘函數僅在遊戲處於活動狀態時太須要調用。

6. 小結

本篇講述了:

  • 如何在遊戲中添加大量相同的元素;
  • 如何用嵌套循環來建立元素網格;
  • 如何控制對象在屏幕上移動的方向以及響應事件;
  • 如何檢測和響應元素碰撞;
  • 如何在遊戲中跟蹤統計信息;
  • 如何使用標誌game_active來判斷遊戲是否結束。

下一篇中,同時也是本項目的最後一篇,咱們將:

  • 添加一個Play按鈕讓玩家可以開始遊戲,以及遊戲結束後再開始;
  • 每當玩家消滅一羣外星人後,加快遊戲節奏;
  • 添加一個分數系統。


迎你們關注個人微信公衆號"代碼港" & 我的網站 www.vpointer.net ~

相關文章
相關標籤/搜索