Python學習之路11-武裝飛船

VPointer發表於2019-02-16

《Python程式設計:從入門到實踐》筆記。

本章主要學習如何使用pygame編寫一個簡單的小飛機打外星人的遊戲,由於本人對用python寫遊戲並不是特別感興趣,所以主要是看程式碼的構建和一些程式設計規範,程式碼會有所簡略。

1. 準備工作

Python標準庫中並沒有自帶pygame模組,所以需要自行安裝,可以在控制檯(Windows下是cmd)上使用命令列安裝:pip install pygame。如果你是用的PyCharm,也可以在設定中安裝:

Python學習之路11-武裝飛船

點選右邊的加號,在彈出的視窗中輸入pygame,然後安裝即可。

Python學習之路11-武裝飛船

該專案中需要使用一些書中的圖片,這些圖片都可以在 http://www.ituring.com.cn/book/1861 中下載到。

2. 遊戲基本內容

首先需要新建一個專案,筆者取名為 "alien_invasion" ,並在該專案的根目錄下新建一個 images 資料夾,用於存放專案中用到的圖片。在本節中,我們將先建立4個檔案:

alien_invasion.py:遊戲主程式

settings.py:遊戲的配置檔案

game_functions.py:存放遊戲的控制函式,比如響應滑鼠、鍵盤等

ship.py:飛船類

2.1 alien_invasion.py模組:

該模組經過重構後的程式碼如下:

import pygame

import game_functions as gf
from settings import Settings
from ship import Ship

def run_game():
    # 初始化遊戲並建立一個螢幕物件
    pygame.init()   # 初始化背景設定,讓pygame能正常工作
    ai_settings = Settings()  # 例項化一個遊戲配置類
    # 返回一個遊戲視窗
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption("Alien Invasion")   # 給這個遊戲視窗設定一個標題

    ship = Ship(ai_settings, screen)   # 例項化一個飛船類,傳入了引數ai_settings和螢幕物件screen

    # 開始遊戲的主迴圈
    while True:
        gf.check_events(ship)  # 用於響應遊戲事件
        ship.update()   # 更新飛船狀態
        gf.update_screen(ai_settings, screen, ship)  # 重繪screen

run_game()
複製程式碼

①程式碼第1行匯入pygame模組,它包含開發遊戲所需的基本功能;

②程式碼3到5行匯入的是自行編寫且經過重構的模組;

③第9行程式碼執行遊戲的初始化工作,比如初始化遊戲背景等;

④第10行例項化一個遊戲配置類,用於配置遊戲引數,該類的具體實現見本篇後面的內容;

⑤程式碼第12-13行用於生成一個名為screen的顯示視窗,長寬從配置物件ai_settings中讀出;display.set_mode()返回的是一個surface,在pygame中,surface是螢幕的一部分,用於顯示遊戲元素,這裡的screen表示的是整個遊戲視窗。我們啟用遊戲的迴圈後,每經過一次迴圈pygame都將重繪這個screen

⑥程式碼第20行的check_events()函式用於響應遊戲中發生的時間,比如滑鼠,鍵盤,關閉視窗等。

⑦程式碼第21行用於更新飛船的資訊,如飛船位置

⑧最後一行用於啟動遊戲,即初始化遊戲,並開始主迴圈。

2.2 settings.py模組

該檔案主要是遊戲的配置資訊,存放遊戲的各種引數。

class Settings:
    """儲存《外星人入侵》的所有設定的類"""

    def __init__(self):
        """初始化遊戲的設定"""
        # 螢幕設定
        self.screen_width = 1200   # 遊戲視窗寬度
        self.screen_height = 800   # 遊戲視窗高度
        self.bg_color = (230, 230, 230)  # 遊戲背景顏色
        self.ship_speed_factor = 1.5  # 飛船的移動速度
複製程式碼

這裡故意將飛船的速度設定為浮點數,也可以是整數。在設定遊戲元素的位置時,如果直接使用浮點數,則只會擷取整數部分。

2.3 ship.py模組

該模組描述了一個飛船類的基本內容:

import pygame

class Ship:
    def __init__(self, ai_settings, screen):
        """初始化飛機並設定其初始位置"""
        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

        # 自定義一個能儲存浮點數的臨時變數,x座標
        self.center = float(self.rect.centerx)
		
        # 標誌,用於表示是否正在向某個方向移動
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根據移動標誌調整飛船的位置"""
        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

        # 用臨時變數更新rect的centerx,擷取擷取整數部分
        self.rect.centerx = self.center

    def blitme(self):
        """在指定位置繪製飛船"""
        self.screen.blit(self.image, self.rect)
複製程式碼

__init__()中的self.center屬性,程式碼將self.rect.centerx即飛船的中心x座標轉換成浮點數,並將其儲存在self.center中。之所以轉換成浮點數,是因為在settings.py檔案中,我們將飛船移動速度設定成了浮點數。

self.moving_rightself.moving_left標誌,用於表示飛船是否正在移動,用於實現飛船在不鬆開按鍵下連續移動。

udpate()方法,用於增減飛船的中心位置x座標(因為飛船隻能在底部移動,所以不用改y座標),並防止飛船移動出遊戲視窗。

④重寫了blitme()函式,用於繪製飛船

2.4 game_functions.py模組

該模組主要是集中處理遊戲中發生的各種事件。

import sys
import pygame

def check_keydown_event(event, ship):
    """響應按下按鍵"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
    if event.key == pygame.K_LEFT:
        ship.moving_left = True

def check_keyup_event(event, ship):
    """響應鬆開按鍵"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    if event.key == pygame.K_LEFT:
        ship.moving_left = False

def check_events(ship):
    """響應按鍵和滑鼠事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_event(event, ship)

        elif event.type == pygame.KEYUP:
            check_keyup_event(event, ship)

def update_screen(ai_settings, screen, ship):
    """更新螢幕上的影象,並切換到新螢幕"""
    # 每次迴圈時都重繪螢幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()

    # 讓最近繪製的螢幕可見
    pygame.display.flip()
複製程式碼

①在pygame中,用K_RIGHTK_LEFT表示方向按鍵,其實鍵盤上每個鍵在pygame中都有所對於,以K_開頭。check_keydown_event()函式和check_keyup_event()函式都是對下面的check_event()的進一步簡化,這兩個函式的程式碼均可以放在check_event()中,但這樣程式碼將會很臃腫,結構不清晰。

check_event()函式用於監聽遊戲的事件,比如pygame.QUIT,它表示遊戲推出事件;pygame.KEYDOWNpygame.KEYUP分別表示鍵盤按下與鬆開事件。本次大迴圈中(外層的while迴圈)發生的所有事件都儲存在pygame.event中,我們使用get()方法獲得這些事件。

③在update_screen()函式中,我們使用screenfill()方法填充窗體的背景色,呼叫blitme()方法來在窗體中繪製飛船,最後,呼叫pygame.display.flip()方法讓最近的繪製在窗體中可見。

2.5 執行遊戲

現在我們執行alien_invasion.py檔案,我們將得到如下窗體:

Python學習之路11-武裝飛船

目前功能還比較簡單,只能實現飛船的左右移動。

3. 新增射擊功能

為了新增射擊功能,需要先新增一個子彈類。

3.1 Bullet.py

import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):  # 使用精靈
    """一個對飛船發射的子彈進行管理的類"""

    def __init__(self, ai_settings, screen, ship):
        """在飛船所處的位置建立一個子彈物件"""
        super(Bullet, self).__init__()
        self.screen = screen

        # 在(0,0)處建立一個表示子彈的矩形,再設定正確的位置
        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           # 從飛機的頂部射出

        # 儲存用浮點數表示的子彈位置,因為子彈只在y軸上運動,所以不需要x座標
        self.y = float(self.rect.y)

        self.color = ai_settings.bullet_color   # 子彈顏色
        self.speed_factor = ai_settings.bullet_speed_factor   # 子彈速度

    def update(self):
        """向上移動子彈"""
        # 更新表示子彈位置的浮點數值
        self.y -= self.speed_factor
        # 更新表示子彈的rect的位置
        self.rect.y = self.y

    def draw_bullet(self):
        """在螢幕上繪製子彈"""
        pygame.draw.rect(self.screen, self.color, self.rect)
複製程式碼

首先我們需要匯入pygame模組以及其中的Sprite類(直譯的話叫做“精靈類”,然而這名字叫的真的很尷尬),它可以讓我們在後面方便批量處理相同型別的同一操作,子彈類繼承自Sprite類。該子彈類並沒有使用圖片,而是直接在screen上繪製矩形用於表示子彈。update()方法用於更新子彈的位置。pygame.draw.rect()用於在screen上繪製子彈。

3.2 修改settings.py

在該模組中新增子彈類的引數:

class Settings:
    def __init__(self):
        -- snip --
        # 子彈設定
        self.bullet_speed_factor = 1
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = 60, 60, 60
        # 表示視窗中最多允許存在的子彈數,當然你也可以將其去掉
        self.bullets_allowed = 3   
複製程式碼

3.3 修改game_functions.py

遊戲中我們按空格鍵發射子彈,併發射子彈的過程單獨寫在一個函式fire_bullet()中。為了響應空格鍵,需要修改check_event()函式和check_keydown_event()函式,前者只修改了引數,後者在判斷結構中新增了一個判斷。有了子彈類,那我們還需要在screen中繪製子彈,所以還需要修改update_screen()函式,而子彈自身資訊(比如子彈的移動)的修改則放在了一個新的函式update_bullets()中。

import sys
import pygame
from Bullet import Bullet

# 新增函式!
def fire_bullet(ai_settings, screen, ship, bullets):
    """如果還沒有到達限制,就發射一顆子彈"""
    # 建立新子彈,並將其加入到編組bullets中
    # 如果你想讓飛船能無限發射子彈,可以將判斷語句刪除
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

# 修改了引數!
def check_keydown_event(event, ship, ai_settings, screen, bullets):
    -- snip --
    # 按空格鍵發射子彈
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets)

# 修改了引數!
def check_events(ai_settings, screen, ship, bullets):
    """響應按鍵和滑鼠事件"""
    for event in pygame.event.get():
        -- snip -- 
        elif event.type == pygame.KEYDOWN:
            # 增加了引數
            check_keydown_event(event, ship, ai_settings, screen, bullets)
        -- snip --

# 修改了函式!
def update_screen(ai_settings, screen, ship, bullets):
    -- snip --
    # 繪製子彈
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    -- snip --

# 新增函式
def update_bullets(bullets):
    """更新子彈的位置,並刪除已消失的子彈"""
    # 更新子彈的位置
    # bullets是個Group物件,呼叫一次update()就會呼叫其中所有Sprite物件的update()
    # 相當於你不用自己寫for迴圈了
    bullets.update()

    # 刪除已消失的子彈
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
複製程式碼

當子彈從視窗中消失時,它並沒有從記憶體中消失,如果對於已經從螢幕中消失的子彈不做處理的話,時間一長,子彈數一多,光子彈一項的記憶體佔用就會越來越多(土豪請忽略),雖然只是線性增長,但作為一個合格的程式設計師,應該避免這種無謂的浪費。

3.4 修改alien_invation.py

最後,我們修改主程式,在其中新增一個pygame.sprite中的Group物件用於表示子彈集合,以及對該物件的操作程式碼。

import pygame
from pygame.sprite import Group   # 匯入一個新類

import game_functions as gf
from settings import Settings
from ship import Ship

def run_game():
    -- snip --
    bullets = Group()

    # 開始遊戲的主迴圈
    while True:
        gf.check_events(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, bullets)
複製程式碼

3.5執行新程式碼

以下是執行截圖:

Python學習之路11-武裝飛船

4. 小結

自此,我們建立了一個能開火的小飛機,在下一篇文章中我們將向其中新增外星人。

本篇中的程式碼都是經過了重構後的程式碼,但是,當我們自己在程式設計時,如果對某一框架還是小白,搞不清楚該如何組織程式碼,那就把所有程式碼都寫在一個或幾個檔案裡(雖然這種習慣很不好),也暫時不用考慮程式碼結構之類的問題,因為 你的任務是造東西,而不是寫漂亮程式碼,用精巧結構,用別人沒看過的語法。 兩者能兼備當然更好,但每個人都有當小白的時期,有一定熟練度後,再來考慮程式碼重構的問題。


迎大家關注我的微信公眾號"程式碼港" & 個人網站 www.vpointer.net ~

Python學習之路11-武裝飛船

相關文章