2022年6月24日星期五

012 Python编程 從入門到實踐, 第二部分 專案 第 12 章 武裝飛船

 申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!



第二部分 專案

祝賀你!你現在已對 Python 有了足夠的認識,可以開始開發有意思的互動式專案了。通過動手開發專案,可學到新技能,並更深入地理解第一部分介紹的概念。

第二部分包含三個不同類型的專案,你可以選擇完成其中的任何專案或全部專案,完成這些專案的順序無關緊要。下面簡要地描述每個項目,幫助你決定首先去完成哪個項目。

外星人入侵:使用 Python 開發遊戲

在專案外星人入侵(第 12~14 章)中,你將使用 Pygame 包來開發一款 2D 遊戲,它在玩家每消滅一群向下移動的外星人後,都將玩家提高一個等級;而等級越高,遊戲的節奏越快,難度越大。完成這個項目後,你將獲得自己動手使用 Pygame 開發 2D 遊戲所需的技能。

數據視覺化

資料視覺化專案始於第 15 章,在這一章中,你將學習如何使用 matplotlib Pygal 來生成資料,以及根據這些資料創建實用而漂亮的圖表。第 16 章介紹如何從網上獲取資料,並將它們提供給視覺化包以創建天氣圖和世界人口地圖。最後,第 17 章介紹如何編寫自動下載資料並對其進行視覺化的程式。學習視覺化讓你可以探索資料採擷領域,這是當前在全球都非常吃香的技能。

Web 應用程式

“Web 應用程式專案(第 18~20 章)中,你將使用 Django 包來創建一個簡單的 Web 應用程式,它讓使用者能夠記錄任意多個一直在學習的主題。使用者將通過指定用戶名和密碼來創建帳戶,輸入主題,並編寫條目來記錄學習的內容。你還將學習如何部署應用程式,讓世界上的任何人都能夠訪問它。

完成這個項目後,你將能夠自己動手創建簡單的 Web 應用程式,並能夠深入學習其他有關如何使用 Django 開發應用程式的資料。

 

項目1 外星人入侵

 

12 章 武裝飛船

我們來開發一個遊戲吧!我們將使用Pygame,這是一組功能強大而有趣的模組,可用於管理圖形、動畫乃至聲音,讓你能夠更輕鬆地開發複雜的遊戲。通過使用Pygame來處理在螢幕上繪製圖像等任務,你不用考慮眾多煩瑣而艱難的編碼工作,而是將重點放在程式的高級邏輯上。

在本章中,你將安裝Pygame,再創建一艘能夠根據使用者輸入而左右移動和射擊的飛船。在接下來的兩章中,你將創建一群作為射殺目標的外星人,並做其他的改進,如限制可供玩家使用的飛船數以及添加記分牌。

從本章開始,你還將學習管理包含多個檔的項目。我們將重構很多代碼,以提高代碼的效率,並管理檔的內容,以確保專案組織有序。

創建遊戲是趣學語言的理想方式。看別人玩你編寫的遊戲讓你很有滿足感,而編寫簡單的遊戲有助於你明白專業級遊戲是怎麼編寫出來的。在閱讀本章的過程中,請動手輸入並運行代碼,以明白各個代碼塊對整個遊戲所做的貢獻,並嘗試不同的值和設置,這樣你將對如何改進遊戲的交互性有更深入的認識。

 

注意  遊戲《外星人入侵》將包含很多不同的檔,因此請在你的系統中新建一個資料夾,並將其命名為alien_invasion 。請務必將這個專案的所有檔都存儲到這個資料夾中,這樣相關的import語句才能正確地工作。

12.1 規劃專案

開發大型專案時,做好規劃後再動手編寫專案很重要。規劃可確保你不偏離軌道,從而提高專案成功的可能性。

下面來編寫有關遊戲《外星人入侵》的描述,其中雖然沒有涵蓋這款遊戲的所有細節,但能讓你清楚地知道該如何動手開發它。

在遊戲《外星人入侵》中,玩家控制著一艘最初出現在螢幕底部中央的飛船。玩家可以使用方向鍵左右移動飛船,還可使用空白鍵進行射擊。遊戲開始時,一群外星人出現在天空中,他們在螢幕中向下移動。玩家的任務是射殺這些外星人。玩家將所有外星人都消滅乾淨後,將出現一群新的外星人,他們移動的速度更快。只要有外星人撞到了玩家的飛船或到達了螢幕底部,玩家就損失一艘飛船。玩家損失三艘飛船後,遊戲結束。

在第一個開發階段,我們將創建一艘可左右移動的飛船,這艘飛船在使用者按空白鍵時能夠開火。設置好這種行為後,我們就能夠將注意力轉向外星人,並提高這款遊戲的可玩性。

12.2 安裝Pygame

開始編碼前,先來安裝Pygame。下面介紹如何在LinuxOS XMicrosoft Windows中安裝Pygame

如果你使用的是Linux系統和Python 3,或者是OS X系統,就需要使用pip來安裝Pygamepip是一個負責為你下載並安裝Python包的程式。接下來的幾小節介紹如何使用pip來安裝Python包。

如果你使用的是Linux系統和Python 2.7,或者是Windows,就無需使用pip來安裝Pygame;在這種情況下,請直接跳到12.2.2節或12.2.4節。

注意  接下來的部分包含在各種系統上安裝pip的說明,因為資料視覺化專案和Web應用程式專案都需要pip。這些說明也可在https://www.nostarch.com/pythoncrashcourse/ 線上資源中找到。如果安裝時遇到麻煩,看看線上說明是否管用。

12.2.1 使用pip安裝Python

大多數較新的Python版本都自帶pip,因此首先可檢查系統是否已經安裝了pip。在Python 3中,pip有時被稱為pip3

1. LinuxOS X系統中檢查是否安裝了pip

打開一個終端視窗,並執行如下命令:

 

  $ pip --version

pip 7.0.3 from /usr/local/lib/python3.5/dist-packages (python 3.5)

  $

 

 

 

 

 

 

 

 

如果你的系統只安裝了一個版本的Python,並看到了類似於上面的輸出,請跳到12.2.2節或12.2.3節。如果出現了錯誤消息,請嘗試將pip替換為pip3。如果這兩個版本都沒有安裝到你的系統中,請跳到安裝pip”

如果你的系統安裝了多個版本的Python,請核實pip關聯到了你使用的Python版本,如python 3.5(見)。如果pip關聯到了正確的Python版本,請跳到12.2.2節或12.2.3節。如果pip沒有關聯到正確的Python版本,請嘗試將pip替換為pip3。如果執行這兩個命令時,輸出都表明沒有關聯到正確的Python版本,請跳到安裝pip”

2. Windows系統中檢查是否安裝了pip

打開一個終端視窗,並執行如下命令:

 

  $ python -m pip --version

pip 7.0.3 from C:\Python35\lib\site-packages (python 3.5)

  $

 

 

 

 

 

 

 

 

如果你的系統只安裝了一個版本的Python,並看到了類似於上面的輸出,請跳到12.2.4節。如果出現了錯誤消息,請嘗試將pip替換為pip3。如果執行這兩個命令時都出現錯誤消息,請跳到安裝pip”

如果你的系統安裝了多個版本的Python,請核實pip關聯到了你使用的Python版本,如python 3.5(見)。如果pip關聯到了正確的Python版本,請跳到12.2.4節。如果pip沒有關聯到正確的Python版本,請嘗試將pip替換為pip3。如果執行這兩個命令時都出現錯誤消息,請跳到安裝pip”

3. 安裝pip

要安裝pip,請訪問https://bootstrap.pypa.io/get-pip.py 。如果出現對話方塊,請選擇保存檔;如果get-pip.py的代碼出現在流覽器中,請將這些代碼複製並粘貼到文字編輯器中,再將檔保存為get-pip.py。將get-pip.py保存到電腦中後,你需要以管理員身份運行它,因為pip將在你的系統中安裝新包。

注意  如果你找不到get-pip.py,請訪問https://pip.pypa.io/ ,按一下左邊面板中的Installation,再按一下中間視窗中的連結get-pip.py

4. LinuxOS X系統中安裝pip

使用下面的命令以管理員身份運行get-pip.py

 

$ sudo python get-pip.py

 

 

 

 

 

 

 

 

注意  如果你啟動終端會話時使用的是命令python3 ,那麼在這裡應使用命令sudo python3 get-pip.py 

這個程式運行後,使用命令pip --version (或pip3 --version )確認正確地安裝了pip

5. Windows系統中安裝pip

使用下面的命令運行get-pip.py

 

$ python get-pip.py

 

 

 

 

 

 

 

 

如果你在終端中運行Python時使用的是另一個命令,也請使用這個命令來運行get-pip.py。例如,你可能需要使用命令python3 get-pip.py C:\Python35\python get-pip.py 

這個程式運行後,執行命令python -m pip --version 以確認成功地安裝了pip

12.2.2 在Linux系統中安裝Pygame

如果你使用的是Python 2.7,請使用包管理器來安裝Pygame。為此,打開一個終端視窗,並執行下面的命令,這將下載Pygame,並將其安裝到你的系統中:

 

$ sudo apt-get install python-pygame

 

 

 

 

 

 

 

 

執行如下命令,在終端會話中檢查安裝情況:

 

$ python

>>> import pygame

>>> 

 

 

 

 

 

 

 

 

如果沒有任何輸出,就說明Python導入了Pygame,你可以跳到12.3節。

如果你使用的是Python 3,就需要執行兩個步驟:安裝Pygame依賴的庫;下載並安裝Pygame

執行下面的命令來安裝Pygame依賴的庫(如果你開始終端會話時使用的是命令python3.5 ,請將python3-dev 替換為python3.5-dev ):

 

$ sudo apt-get install python3-dev mercurial

$ sudo apt-get install libsdl-image1.2-dev libsdl2-dev libsdl-ttf2.0-dev

 

 

 

 

 

 

 

 

這將安裝運行《外星人入侵》時需要的庫。如果你要啟用Pygame的一些高級功能,如添加聲音的功能,可安裝下面這些額外的庫:

 

$ sudo apt-get install libsdl-mixer1.2-dev libportmidi-dev

$ sudo apt-get install libswscale-dev libsmpeg-dev libavformat-dev libavcode-dev

$ sudo apt-get install python-numpy

 

 

 

 

 

 

 

 

接下來,執行下面的命令來安裝Pygame(如有必要,將pip 替換為pip3 ):

 

$ pip install --user hg+http://bitbucket.org/pygame/pygame

 

 

 

 

 

 

 

 

告知你Pygame找到了哪些庫後,輸出將暫停一段時間。請按回車鍵,即便有一些庫沒有找到。你將看到一條消息,說明成功地安裝了Pygame

要確認安裝成功,請啟動一個Python終端會話,並嘗試執行下面的命令來導入Pygame

 

$ python3

>>> import pygame

>>> 

 

 

 

 

 

 

 

 

如果導入成功,請跳到12.3節。

12.2.3 在OS X系統中安裝Pygame

要安裝Pygame依賴的有些包,需要Homebrew。如果你沒有安裝Homebrew,請參閱附錄A的說明。

為安裝Pygame依賴的庫,請執行下麵的命令:

 

$ brew install hg sdl sdl_image sdl_ttf

 

 

 

 

 

 

 

 

這將安裝運行遊戲《外星人入侵》所需的庫。每安裝一個庫後,輸出都會向上滾動。

如果你還想啟用較高級的功能,如在遊戲中包含聲音,可安裝下面兩個額外的庫:

 

$ brew install sdl_mixer portmidi

 

 

 

 

 

 

 

 

使用下面的命令來安裝Pygame(如果你運行的是Python 2.7,請將pip3 替換為pip ):

 

$ pip3 install --user hg+http://bitbucket.org/pygame/pygame

 

 

 

 

 

 

 

 

啟動一個Python終端會話,並導入Pygame以檢查安裝是否成功(如果你運行的是Python 2.7,請將python3 替換為python ):

 

$ python3

>>> import pygame

>>> 

 

 

 

 

 

 

 

 

如果導入成功,請跳到12.3節。

12.2.4 在Windows系統中安裝Pygame

Pygame項目託管在代碼分享網站Bitbucket中。要在Windows系統中安裝Pygame,請訪問https://bitbucket.org/pygame/pygame/downloads/ ,查找與你運行的Python版本匹配的Windows安裝程式。如果在Bitbucket上找不到合適的安裝程式,請去http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame 看看。

下載合適的檔後,如果它是.exe檔,就運行它。

如果該檔的副檔名為.whl,就將它複製到你的專案檔案夾中。再打開一個命令視窗,切換到該檔所在的資料夾,並使用pip來運行它:

 

> python -m pip install --user pygame-1.9.2a0-cp35-none-win32.whl

 

 

 

 

 

 

 

 

12.3 開始遊戲專案

現在來開始開發遊戲《外星人入侵》。首先創建一個空的Pygame視窗,供後面用來繪製遊戲元素,如飛船和外星人。我們還將讓這個遊戲回應用戶輸入、設置背景色以及載入飛船圖像。

12.3.1 創建Pygame視窗以及回應使用者輸入

首先,我們創建一個空的Pygame視窗。使用Pygame編寫的遊戲的基本結構如下:

alien_invasion.py

 

  import sys

 

  import pygame

 

  def run_game():

      # 初始化遊戲並創建一個螢幕物件

     pygame.init()

     screen = pygame.display.set_mode((1200, 800))

      pygame.display.set_caption("Alien Invasion")

 

      # 開始遊戲的主迴圈

     while True:

 

          # 監視鍵盤和滑鼠事件

         for event in pygame.event.get():

             if event.type == pygame.QUIT:

                  sys.exit()

 

          # 讓最近繪製的螢幕可見

         pygame.display.flip()

 

  run_game()

 

 

 

 

 

 

 

 

首先,我們導入了模組sys pygame 。模組pygame 包含開發遊戲所需的功能。玩家退出時,我們將使用模組sys 來退出遊戲。

遊戲《外星人入侵》的開頭是函數run_game() 處的代碼行pygame.init() 初始化背景設置,讓Pygame能夠正確地工作。在處,我們調用pygame.display.set_mode() 來創建一個名為screen 的顯示視窗,這個遊戲的所有圖形元素都將在其中繪製。實參(1200, 800) 是一個元組,指定了遊戲視窗的尺寸。通過將這些尺寸值傳遞給pygame.display.set_mode() ,我們創建了一個寬1200圖元、高800圖元的遊戲視窗(你可以根據自己的顯示器尺寸調整這些值)。

物件screen 是一個surface。在Pygame中,surface是螢幕的一部分,用於顯示遊戲元素。在這個遊戲中,每個元素(如外星人或飛船)都是一個surfacedisplay.set_mode() 返回的surface表示整個遊戲視窗。我們啟動遊戲的動畫迴圈後,每經過一次迴圈都將自動重繪這個surface

這個遊戲由一個while 迴圈(見)控制,其中包含一個事件迴圈以及管理螢幕更新的代碼。事件是用戶玩遊戲時執行的操作,如按鍵或移動滑鼠。為讓程式響應事件,我們編寫一個事件迴圈,以偵聽事件,並根據發生的事件執行相應的任務。處的for 迴圈就是一個事件迴圈。

為訪問Pygame檢測到的事件,我們使用方法pygame.event.get() 。所有鍵盤和滑鼠事件都將促使for 迴圈運行。在這個迴圈中,我們將編寫一系列的if 語句來檢測並響應特定的事件。例如,玩家按一下遊戲視窗的關閉按鈕時,將檢測到pygame.QUIT 事件,而我們調用sys.exit() 來退出遊戲(見

處調用了pygame.display.flip() ,命令Pygame讓最近繪製的螢幕可見。在這裡,它在每次執行while 迴圈時都繪製一個空螢幕,並擦去舊螢幕,使得只有新螢幕可見。在我們移動遊戲元素時,pygame.display.flip() 將不斷更新螢幕,以顯示元素的新位置,並在原來的位置隱藏元素,從而營造平滑移動的效果。

在這個基本的遊戲結構中,最後一行調用run_game() ,這將初始化遊戲並開始主迴圈。

如果此時運行這些代碼,你將看到一個空的Pygame視窗。

12.3.2 設置背景色

Pygame預設創建一個黑色螢幕,這太乏味了。下面來將背景設置為另一種顏色:

alien_invasion.py

 

  --snip--

  def run_game():

      --snip--

      pygame.display.set_caption("Alien Invasion")

 

      # 設置背景色

     bg_color = (230, 230, 230)

 

      # 開始遊戲主迴圈.

      while True:

 

          # 監聽鍵盤和滑鼠事件

          --snip--

 

          # 每次迴圈時都重繪螢幕

         screen.fill(bg_color)

 

          # 讓最近繪製的螢幕可見

          pygame.display.flip()

 

  run_game()

 

 

 

 

 

 

 

 

首先,我們創建了一種背景色,並將其存儲在bg_color 中(見)。該顏色只需指定一次,因此我們在進入主while 迴圈前定義它。

Pygame中,顏色是以RGB值指定的。這種顏色由紅色、綠色和藍色值組成,其中每個值的可能取值範圍都為0~255。顏色值(255, 0, 0)表示紅色,(0, 255, 0)表示綠色,而(0, 0, 255)表示藍色。通過組合不同的RGB值,可創建1600萬種顏色。在顏色值(230, 230, 230)中,紅色、藍色和綠色量相同,它將背景設置為一種淺灰色。

處,我們調用方法screen.fill() ,用背景色填充螢幕;這個方法只接受一個實參:一種顏色。

12.3.3 創建設置類

每次給遊戲添加新功能時,通常也將引入一些新設置。下面來編寫一個名為settings 的模組,其中包含一個名為Settings 的類,用於將所有設置存儲在一個地方,以免在代碼中到處添加設置。這樣,我們就能傳遞一個設置物件,而不是眾多不同的設置。另外,這讓函式呼叫更簡單,且在項目增大時修改遊戲的外觀更容易:要修改遊戲,只需修改settings.py中的一些值,而無需查找散佈在檔中的不同設置。

下麵是最初的Settings 類:

settings.py

 

class Settings():

    """存儲《外星人入侵》的所有設置的類"""

 

    def __init__(self):

        """初始化遊戲的設置"""

        # 螢幕設置

        self.screen_width = 1200

        self.screen_height = 800

        self.bg_color = (230, 230, 230)

 

 

 

 

 

 

 

 

為創建Settings 實例並使用它來訪問設置,將alien_invasion.py修改成下面這樣:

alien_invasion.py

 

  --snip--

  import pygame

 

  from settings import Settings

 

  def run_game():

      # 初始化pygame、設置和螢幕物件

      pygame.init()

     ai_settings = Settings()

     screen = pygame.display.set_mode(

          (ai_settings.screen_width, ai_settings.screen_height))

      pygame.display.set_caption("Alien Invasion")

 

      # 開始遊戲主迴圈

      while True:

          --snip--

          # 每次迴圈時都重繪螢幕

         screen.fill(ai_settings.bg_color)

 

          # 讓最近繪製的螢幕可見

          pygame.display.flip()

 

  run_game()

 

 

 

 

 

 

 

 

在主程序檔中,我們導入Settings 類,調用pygame.init() ,再創建一個Settings 實例,並將其存儲在變數ai_settings 中(見)。創建螢幕時(見),使用了ai_settings 的屬性screen_width screen_height ;接下來填充螢幕時,也使用了ai_settings 來訪問背景色(見

12.4 添加飛船圖像

下面將飛船加入到遊戲中。為了在螢幕上繪製玩家的飛船,我們將載入一幅圖像,再使用Pygame方法blit() 繪製它。

為遊戲選擇素材時,務必要注意許可。最安全、最不費錢的方式是使用http://pixabay.com/ 等網站提供的圖形,這些圖形無需許可,你可以對其進行修改。

在遊戲中幾乎可以使用任何類型的影像檔,但使用點陣圖(.bmp)檔最為簡單,因為Pygame默認載入點陣圖。雖然可配置Pygame以使用其他檔案類型,但有些檔案類型要求你在電腦上安裝相應的圖像庫。大多數圖像都為.jpg.png.gif格式,但可使用PhotoshopGIMPPaint等工具將其轉換為點陣圖。

選擇圖像時,要特別注意其背景色。請盡可能選擇背景透明的圖像,這樣可使用圖像編輯器將其背景設置為任何顏色。圖像的背景色與遊戲的背景色相同時,遊戲看起來最漂亮;你也可以將遊戲的背景色設置成與圖像的背景色相同。

就遊戲《外星人入侵》而言,你可以使用檔ship.bmp(如圖12-1所示),這個檔可在本書的配套資源(https://www.nostarch.com/pythoncrashcourse/ )中找到。這個檔的背景色與這個項目使用的設置相同。請在主專案檔案夾(alien_invasion)中新建一個資料夾,將其命名為images,並將檔ship.bmp保存到這個資料夾中。


12-1 遊戲《外星人入侵》中的飛船

12.4.1 創建Ship 

選擇用於表示飛船的圖像後,需要將其顯示到螢幕上。我們將創建一個名為ship 的模組,其中包含Ship 類,它負責管理飛船的大部分行為。

ship.py

 

  import pygame

 

  class Ship():

 

      def __init__(self, screen):

          """初始化飛船並設置其初始位置"""

          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

          self.rect.bottom = self.screen_rect.bottom

 

     def blitme(self):

          """在指定位置繪製飛船"""

          self.screen.blit(self.image, self.rect)

 

 

 

 

 

 

 

 

首先,我們導入了模組pygame Ship 的方法__init__() 接受兩個參數:引用self screen ,其中後者指定了要將飛船繪製到什麼地方。為載入圖像,我們調用了pygame.image.load() (見)。這個函數返回一個表示飛船的surface,而我們將這個surface存儲到了self.image 中。

載入圖像後,我們使用get_rect() 獲取相應surface的屬性rect (見)。Pygame的效率之所以如此高,一個原因是它讓你能夠像處理矩形(rect 物件)一樣處理遊戲元素,即便它們的形狀並非矩形。像處理矩形一樣處理遊戲元素之所以高效,是因為矩形是簡單的幾何形狀。這種做法的效果通常很好,遊戲玩家幾乎注意不到我們處理的不是遊戲元素的實際形狀。

處理rect 物件時,可使用矩形四角和中心的 x  y 座標。可通過設置這些值來指定矩形的位置。

要將遊戲元素居中,可設置相應rect 物件的屬性center centerx centery 。要讓遊戲元素與螢幕邊緣對齊,可使用屬性top bottom left right ;要調整遊戲元素的水準或垂直位置,可使用屬性x y ,它們分別是相應矩形左上角的 x  y 座標。這些屬性讓你無需去做遊戲開發人員原本需要手工完成的計算,你經常會用到這些屬性。

注意  在Pygame中,原點(0, 0)位於螢幕左上角,向右下方移動時,座標值將增大。在1200×800的螢幕上,原點位於左上角,而右下角的座標為(1200, 800)

我們將把飛船放在螢幕底部中央。為此,首先將表示螢幕的矩形存儲在self.screen_rect 中(見),再將self.rect.centerx (飛船中心的x 座標)設置為表示螢幕的矩形的屬性centerx (見),並將self.rect.bottom (飛船下邊緣的y 座標)設置為表示螢幕的矩形的屬性bottom Pygame將使用這些rect 屬性來放置飛船圖像,使其與螢幕下邊緣對齊並水準居中。

處,我們定義了方法blitme() ,它根據self.rect 指定的位置將圖像繪製到螢幕上。

12.4.2 在螢幕上繪製飛船

下麵來更新alien_invasion.py,使其創建一艘飛船,並調用其方法blitme()

alien_invasion.py

 

  --snip--

  from settings import Settings

  from ship import Ship

 

  def run_game():

      --snip--

      pygame.display.set_caption("Alien Invasion")

 

      # 創建一艘飛船

     ship = Ship(screen)

 

      # 開始遊戲主迴圈

      while True:

          --snip--

          # 每次迴圈時都重繪螢幕

          screen.fill(ai_settings.bg_color)

         ship.blitme()

 

          # 讓最近繪製的螢幕可見

          pygame.display.flip()

 

  run_game()

 

 

 

 

 

 

 

 

我們導入Ship 類,並在創建螢幕後創建一個名為ship Ship 實例。必須在主while 迴圈前面創建該實例(見),以免每次迴圈時都創建一艘飛船。填充背景後,我們調用ship.blitme() 將飛船繪製到螢幕上,確保它出現在背景前面(見

現在如果運行alien_invasion.py,將看到飛船位於空遊戲螢幕底部中央,如圖12-2所示。


12-2 遊戲《外星人入侵》螢幕底部中央有一艘飛船

12.5 重構:模組game_functions

在大型專案中,經常需要在添加新代碼前重構既有代碼。重構旨在簡化既有代碼的結構,使其更容易擴展。在本節中,我們將創建一個名為game_functions 的新模組,它將存儲大量讓遊戲《外星人入侵》運行的函數。通過創建模組game_functions ,可避免alien_invasion.py太長,並使其邏輯更容易理解。

12.5.1 函數check_events()

我們將首先把管理事件的代碼移到一個名為check_events() 的函數中,以簡化run_game() 並隔離事件管理迴圈。通過隔離事件迴圈,可將事件管理與遊戲的其他方面(如更新螢幕)分離。

check_events() 放在一個名為game_functions 的模組中:

game_functions.py

 

import sys

 

import pygame

 

def check_events():

    """回應按鍵和滑鼠事件"""

    for event in pygame.event.get():

        if event.type == pygame.QUIT:

            sys.exit()

 

 

 

 

 

 

 

 

這個模組中導入了事件檢查迴圈要使用的sys pygame 。當前,函數check_events() 不需要任何形參,其函數體複製了alien_invasion.py的事件迴圈。

下面來修改alien_invasion.py,使其導入模組game_functions ,並將事件迴圈替換為對函數check_events() 的調用:

alien_invasion.py

 

import pygame

 

from settings import Settings

from ship import Ship

import game_functions as gf

 

def run_game():

    --snip--

    # 開始遊戲主迴圈

    while True:

        gf.check_events()

 

        # 讓最近繪製的螢幕可見

        --snip--

 

 

 

 

 

 

 

 

在主程序檔中,不再需要直接導入sys ,因為當前只在模組game_functions 中使用了它。出於簡化的目的,我們給導入的模組game_functions 指定了別名gf 

12.5.2 函數update_screen()

為進一步簡化run_game() ,下面將更新螢幕的代碼移到一個名為update_screen() 的函數中,並將這個函數放在模組game_functions.py 中:

game_functions.py

 

--snip--

 

def check_events():

    --snip--

 

def update_screen(ai_settings, screen, ship):

    """更新螢幕上的圖像,並切換到新螢幕"""

    # 每次迴圈時都重繪螢幕

    screen.fill(ai_settings.bg_color)

    ship.blitme()

 

    # 讓最近繪製的螢幕可見

    pygame.display.flip()

 

 

 

 

 

 

 

 

新函數update_screen() 包含三個形參:ai_settings screen ship 。現在需要將alien_invasion.pywhile 迴圈中更新螢幕的代碼替換為對函數update_screen() 的調用:

alien_invasion.py

 

--snip--

    # 開始遊戲主迴圈

    while True:

        gf.check_events()

        gf.update_screen(ai_settings, screen, ship)

 

run_game()

 

 

 

 

 

 

 

 

這兩個函數讓while 迴圈更簡單,並讓後續開發更容易:在模組game_functions 而不是run_game() 中完成大部分工作。

鑒於我們一開始只想使用一個檔,因此沒有立刻引入模組game_functions 。這讓你能夠瞭解實際的開發過程:一開始將代碼編寫得盡可能簡單,並在項目越來越複雜時進行重構。

對代碼進行重構使其更容易擴展後,可以開始處理遊戲的動態方面了!

動手試一試

12-1 藍色天空 :創建一個背景為藍色的Pygame視窗。

12-2 遊戲角色 :找一幅你喜歡的遊戲角色點陣圖圖像或將一幅圖像轉換為點陣圖。創建一個類,將該角色繪製到螢幕中央,並將該圖像的背景色設置為螢幕背景色,或將螢幕背景色設置為該圖像的背景色。

12.6 駕駛飛船

下面來讓玩家能夠左右移動飛船。為此,我們將編寫代碼,在用戶按左或右方向鍵時作出回應。我們將首先專注於向右移動,再使用同樣的原理來控制向左移動。通過這樣做,你將學會如何控制螢幕圖像的移動。

12.6.1 回應按鍵

每當使用者按鍵時,都將在Pygame中註冊一個事件。事件都是通過方法pygame.event.get() 獲取的,因此在函數check_events() 中,我們需要指定要檢查哪些類型的事件。每次按鍵都被註冊為一個KEYDOWN 事件。

檢測到KEYDOWN 事件時,我們需要檢查按下的是否是特定的鍵。例如,如果按下的是右方向鍵,我們就增大飛船的rect.centerx 值,將飛船向右移動:

game_functions.py

 

  def check_events(ship):

      """回應按鍵和滑鼠事件"""

      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.rect.centerx += 1

 

 

 

 

 

 

 

 

我們在函數check_events() 中包含形參ship ,因為玩家按右方向鍵時,需要將飛船向右移動。在函數check_events() 內部,我們在事件迴圈中添加了一個elif 代碼塊,以便在Pygame 檢測到KEYDOWN 事件時作出回應(見)。我們讀取屬性event.key ,以檢查按下的是否是右方向鍵(pygame.K_RIGHT )(見)。如果按下的是右方向鍵,就將ship.rect.centerx 的值加1,從而將飛船向右移動(見

alien_invasion.py中,我們需要更新調用的check_events() 代碼,將ship 作為實參傳遞給它:

alien_invasion.py

 

    # 開始遊戲主迴圈

    while True:

        gf.check_events(ship)

        gf.update_screen(ai_settings, screen, ship)

 

 

 

 

 

 

 

 

如果現在運行alien_invasion.py,則每按右方向鍵一次,飛船都將向右移動1圖元。這是一個開端,但並非控制飛船的高效方式。下面來改進控制方式,允許持續移動。

12.6.2 允許不斷移動

玩家按住右方向鍵不放時,我們希望飛船不斷地向右移動,直到玩家鬆開為止。我們將讓遊戲檢測pygame.KEYUP 事件,以便玩家鬆開右方向鍵時我們能夠知道這一點;然後,我們將結合使用KEYDOWN KEYUP 事件,以及一個名為moving_right 的標誌來實現持續移動。

飛船不動時,標誌moving_right 將為False 。玩家按下右方向鍵時,我們將這個標誌設置為True ;而玩家鬆開時,我們將這個標誌重新設置為False 

飛船的屬性都由Ship 類控制,因此我們將給這個類添加一個名為moving_right 的屬性和一個名為update() 的方法。方法update() 檢查標誌moving_right 的狀態,如果這個標誌為True ,就調整飛船的位置。每當需要調整飛船的位置時,我們都調用這個方法。

下面是對Ship 類所做的修改:

ship.py

 

  class Ship():

 

      def __init__(self, screen):

          --snip--

          # 將每艘新飛船放在螢幕底部中央

          self.rect.centerx = self.screen_rect.centerx

          self.rect.bottom = self.screen_rect.bottom

 

          # 移動標誌

         self.moving_right = False

 

     def update(self):

          """根據移動標誌調整飛船的位置"""

          if self.moving_right:

              self.rect.centerx += 1

 

      def blitme(self):

          --snip--

 

 

 

 

 

 

 

 

在方法__init__() 中,我們添加了屬性self.moving_right ,並將其初始值設置為False (見)。接下來,我們添加了方法update() ,它在前述標誌為True 時向右移動飛船(見

下面來修改check_events() ,使其在玩家按下右方向鍵時將moving_right 設置為True ,並在玩家鬆開時將moving_right 設置為False 

game_functions.py

 

  def check_events(ship):

      """回應按鍵和滑鼠事件"""

      for event in pygame.event.get():

          --snip--

          elif event.type == pygame.KEYDOWN:

              if event.key == pygame.K_RIGHT:

                 ship.moving_right = True

 

         elif event.type == pygame.KEYUP:

              if event.key == pygame.K_RIGHT:

                  ship.moving_right = False

 

 

 

 

 

 

 

 

處,我們修改了遊戲在玩家按下右方向鍵時回應的方式:不直接調整飛船的位置,而只是將moving_right 設置為True 。在處,我們添加了一個新的elif 代碼塊,用於響應KEYUP 事件:玩家鬆開右方向鍵(K_RIGHT )時,我們將moving_right 設置為False 

最後,我們需要修改alien_invasion.py 中的while 迴圈,以便每次執行迴圈時都調用飛船的方法update() 

alien_invasion.py

 

    # 開始遊戲主迴圈

    while True:

        gf.check_events(ship)

        ship.update()

        gf.update_screen(ai_settings, screen, ship)

 

 

 

 

 

 

 

 

飛船的位置將在檢測到鍵盤事件後(但在更新螢幕前)更新。這樣,玩家輸入時,飛船的位置將更新,從而確保使用更新後的位置將飛船繪製到螢幕上。

如果你現在運行alien_invasion.py並按住右方向鍵,飛船將不斷地向右移動,直到你鬆開為止。

12.6.3 左右移動

飛船能夠不斷地向右移動後,添加向左移動的邏輯很容易。我們將再次修改Ship 類和函數check_events() 。下面顯示了對Ship 類的方法__init__() update() 所做的相關修改:

ship.py

 

    def __init__(self, screen):

        --snip--

        # 移動標誌

        self.moving_right = False

        self.moving_left = False

 

    def update(self):

        """根據移動標誌調整飛船的位置"""

        if self.moving_right:

            self.rect.centerx += 1

        if self.moving_left:

            self.rect.centerx -= 1

 

 

 

 

 

 

 

 

在方法__init__() 中,我們添加了標誌self.moving_left ;在方法update() 中,我們添加了一個if 代碼塊而不是elif 代碼塊,這樣如果玩家同時按下了左右方向鍵,將先增大飛船的rect.centerx 值,再降低這個值,即飛船的位置保持不變。如果使用一個elif 代碼塊來處理向左移動的情況,右方向鍵將始終處於優先地位。從向左移動切換到向右移動時,玩家可能同時按住左右方向鍵,在這種情況下,前面的做法讓移動更準確。

我們還需對check_events() 作兩方面的調整:

game_functions.py

 

def check_events(ship):

    """回應按鍵和滑鼠事件"""

    for event in pygame.event.get():

        --snip--

        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

 

 

 

 

 

 

 

 

如果因玩家按下K_LEFT 鍵而觸發了KEYDOWN 事件,我們就將moving_left 設置為True ;如果因玩家鬆開K_LEFT 而觸發了KEYUP 事件,我們就將moving_left 設置為False 。這裡之所以可以使用elif 代碼塊,是因為每個事件都只與一個鍵相關聯;如果玩家同時按下了左右方向鍵,將檢測到兩個不同的事件。

如果此時運行alien_invasion.py,將能夠不斷地左右移動飛船;如果你同時按左右方向鍵,飛船將紋絲不動。

下面來進一步優化飛船的移動方式:調整飛船的速度;限制飛船的移動距離,以免它移到螢幕外面去。

12.6.4 調整飛船的速度

當前,每次執行while 迴圈時,飛船最多移動1圖元,但我們可以在Settings 類中添加屬性ship_speed_factor ,用於控制飛船的速度。我們將根據這個屬性決定飛船在每次迴圈時最多移動多少距離。下面演示了如何在settings.py中添加這個新屬性:

settings.py

 

class Settings():

    """一個存儲遊戲《外星人入侵》的所有設置的類"""

 

    def __init__(self):

        --snip--

 

        # 飛船的設置

        self.ship_speed_factor = 1.5

 

 

 

 

 

 

 

 

我們將ship_speed_factor 的初始值設置成了1.5 。需要移動飛船時,我們將移動1.5圖元而不是1圖元。

通過將速度設置指定為小數值,可在後面加快遊戲的節奏時更細緻地控制飛船的速度。然而,rect centerx 等屬性只能存儲整數值,因此我們需要對Ship 類做些修改:

ship.py

 

  class Ship():

 

     def __init__(self, ai_settings, screen):

          """初始化飛船並設置其初始位置"""

          self.screen = screen

         self.ai_settings = ai_settings

          --snip--

 

          # 將每艘新飛船放在螢幕底部中央

          --snip--

 

          # 在飛船的屬性center中存儲小數值

         self.center = float(self.rect.centerx)

 

          # 移動標誌

          self.moving_right = False

          self.moving_left = False

 

      def update(self):

          """根據移動標誌調整飛船的位置"""

          # 更新飛船的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

 

          # 根據self.center更新rect對象

         self.rect.centerx = self.center

 

      def blitme(self):

          --snip--

 

 

 

 

 

 

 

 

處,我們在__init__() 的形參列表中添加了ai_settings ,讓飛船能夠獲取其速度設置。接下來,我們將形參ai_settings 的值存儲在一個屬性中,以便能夠在update() 中使用它(見)。鑒於現在調整飛船的位置時,將增加或減去一個單位為圖元的小數值,因此需要將位置存儲在一個能夠存儲小數值的變數中。可以使用小數來設置rect 的屬性,但rect 將只存儲這個值的整數部分。為準確地存儲飛船的位置,我們定義了一個可存儲小數值的新屬性self.center (見)。我們使用函數float() self.rect.centerx 的值轉換為小數,並將結果存儲到self.center 中。

現在在update() 中調整飛船的位置時,將self.center 的值增加或減去ai_settings.ship_speed_factor 的值(見)。更新self.center 後,我們再根據它來更新控制飛船位置的self.rect.centerx (見)。self.rect.centerx 將只存儲self.center 的整數部分,但對顯示飛船而言,這問題不大。

alien_invasion.py中創建Ship 實例時,需要傳入實參ai_settings 

alien_invasion.py

 

--snip--

def run_game():

    --snip--

    # 創建飛船

    ship = Ship(ai_settings, screen)

    --snip--

 

 

 

 

 

 

 

 

現在,只要ship_speed_factor 的值大於1,飛船的移動速度就會比以前更快。這有助於讓飛船的反應速度足夠快,能夠將外星人射下來,還讓我們能夠隨著遊戲的進行加快遊戲的節奏。

12.6.5 限制飛船的活動範圍

當前,如果玩家按住方向鍵的時間足夠長,飛船將移到螢幕外面,消失得無影無蹤。下面來修復這種問題,讓飛船到達螢幕邊緣後停止移動。為此,我們將修改Ship 類的方法update() 

ship.py

 

      def update(self):

          """根據移動標誌調整飛船的位置"""

          # 更新飛船的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

 

          # 根據self.center更新rect對象

          self.rect.centerx = self.center

 

 

 

 

 

 

 

 

上述代碼在修改self.center 的值之前檢查飛船的位置。self.rect.right 返回飛船外接矩形的右邊緣的 x 座標,如果這個值小於self.screen_rect.right 的值,就說明飛船未觸及螢幕右邊緣(見)。左邊緣的情況與此類似:如果rect 的左邊緣的 x 座標大於零,就說明飛船未觸及螢幕左邊緣(見)。這確保僅當飛船在螢幕內時,才調整self.center 的值。

如果此時運行alien_invasion.py,飛船將在觸及螢幕左邊緣或右邊緣後停止移動。

12.6.6 重構check_events()

隨著遊戲開發的進行,函數check_events() 將越來越長,我們將其部分代碼放在兩個函數中:一個處理KEYDOWN 事件,另一個處理KEYUP 事件:

game_functions.py

 

def check_keydown_events(event, ship):

    """回應按鍵"""

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

    """回應鬆開"""

    if event.key == pygame.K_RIGHT:

        ship.moving_right = False

    elif 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_events(event, ship)

        elif event.type == pygame.KEYUP:

            check_keyup_events(event, ship)

 

 

 

 

 

 

 

 

我們創建了兩個新函數:check_keydown_events() check_keyup_events() ,它們都包含形參event ship 。這兩個函數的代碼是從check_events() 中複製而來的,因此我們將函數check_events 中相應的代碼替換成了對這兩個函數的調用。現在,函數check_events() 更簡單,代碼結構更清晰。這樣,在其中回應其他玩家輸入時將更容易。

12.7 簡單回顧

下一節將添加射擊功能,這需要新增一個名為bullet.py的檔,並對一些既有檔進行修改。當前,我們有四個檔,其中包含很多類、函數和方法。添加其他功能之前,為讓你清楚這個專案的組織結構,先來回顧一下這些檔。

12.7.1 alien_invasion.py

主文件alien_invasion.py創建一系列整個遊戲都要用到的物件:存儲在ai_settings 中的設置、存儲在screen 中的主顯示surface以及一個飛船實例。文件alien_invasion.py還包含遊戲的主迴圈,這是一個調用check_events() ship.update() update_screen() while 迴圈。

要玩遊戲《外星人入侵》,只需運行檔alien_invasion.py。其他文件(settings.pygame_functions.pyship.py)包含的代碼被直接或間接地導入到這個檔中。

12.7.2 settings.py

文件settings.py包含Settings 類,這個類只包含方法__init__() ,它初始化控制遊戲外觀和飛船速度的屬性。

12.7.3 game_functions.py

文件game_functions.py包含一系列函數,遊戲的大部分工作都是由它們完成的。函數check_events() 檢測相關的事件,如按鍵和鬆開,並使用輔助函數check_keydown_events() check_keyup_events() 來處理這些事件。就目前而言,這些函數管理飛船的移動。模組game_functions 還包含函數update_screen() ,它用於在每次執行主迴圈時都重繪螢幕。

12.7.4 ship.py

文件ship.py包含Ship 類,這個類包含方法__init__() 、管理飛船位置的方法update() 以及在螢幕上繪製飛船的方法blitme() 。表示飛船的圖像存儲在資料夾images下的檔ship.bmp中。

動手試一試

12-3 火箭 :編寫一個遊戲,開始時螢幕中央有一個火箭,而玩家可使用四個方向鍵上下左右移動火箭。請務必確保火箭不會移到螢幕外面。

12-4 按鍵 :創建一個程式,顯示一個空螢幕。在事件迴圈中,每當檢測到pygame.KEYDOWN 事件時都列印屬性event.key 。運行這個程式,並按各種鍵,看看Pygame如何回應。

12.8 射擊

下面來添加射擊功能。我們將編寫玩家按空白鍵時發射子彈(小矩形)的代碼。子彈將在螢幕中向上穿行,抵達螢幕上邊緣後消失。

12.8.1 添加子彈設置

首先,更新settings.py,在其方法__init__() 末尾存儲新類Bullet 所需的值:

settings.py

 

    def __init__(self):

        --snip--

        # 子彈設置

        self.bullet_speed_factor = 1

        self.bullet_width = 3

        self.bullet_height = 15

        self.bullet_color = 60, 60, 60

 

 

 

 

 

 

 

 

這些設置創建寬3圖元、高15圖元的深灰色子彈。子彈的速度比飛船稍低。

12.8.2 創建Bullet 

下麵來創建存儲Bullet 類的檔bullet.py,其前半部分如下:

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

 

          #存儲用小數表示的子彈位置

         self.y = float(self.rect.y)

 

         self.color = ai_settings.bullet_color

          self.speed_factor = ai_settings.bullet_speed_factor

 

 

 

 

 

 

 

 

Bullet 類繼承了我們從模組pygame.sprite 中導入的Sprite 類。通過使用精靈,可將遊戲中相關的元素編組,進而同時操作編組中的所有元素。為創建子彈實例,需要向__init__() 傳遞ai_settings screen ship 實例,還調用了super() 來繼承Sprite 

注意  代碼super(Bullet, self).__init__() 使用了Python 2.7語法。這種語法也適用於Python 3,但你也可以將這行代碼簡寫為super().__init__() 

處,我們創建了子彈的屬性rect 。子彈並非基於圖像的,因此我們必須使用pygame.Rect() 類從空白開始創建一個矩形。創建這個類的實例時,必須提供矩形左上角的 x 座標和 y 座標,還有矩形的寬度和高度。我們在(0, 0)處創建這個矩形,但接下來的兩行代碼將其移到了正確的位置,因為子彈的初始位置取決於飛船當前的位置。子彈的寬度和高度是從ai_settings 中獲取的。

處,我們將子彈的centerx 設置為飛船的rect.centerx 。子彈應從飛船頂部射出,因此我們將表示子彈的rect top 屬性設置為飛船的rect top 屬性,讓子彈看起來像是從飛船中射出的(見

我們將子彈的 y 座標存儲為小數值,以便能夠微調子彈的速度(見)。在處,我們將子彈的顏色和速度設置分別存儲到self.color self.speed_factor 中。

下麵是bullet.py的第二部分——方法update() draw_bullet() 

bullet.py

 

      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)

 

 

 

 

 

 

 

 

方法update() 管理子彈的位置。發射出去後,子彈在螢幕中向上移動,這意味著y 座標將不斷減小,因此為更新子彈的位置,我們從self.y 中減去self.speed_factor 的值(見)。接下來,我們將self.rect.y 設置為self.y 的值(見)。屬性speed_factor 讓我們能夠隨著遊戲的進行或根據需要提高子彈的速度,以調整遊戲的行為。子彈發射後,其 x 座標始終不變,因此子彈將沿直線垂直地往上穿行。

需要繪製子彈時,我們調用draw_bullet() 。函數draw.rect() 使用存儲在self.color 中的顏色填充表示子彈的rect 佔據的螢幕部分(見

12.8.3 將子彈存儲到編組中

定義Bullet 類和必要的設置後,就可以編寫代碼了,在玩家每次按空白鍵時都射出一發子彈。首先,我們將在alien_invasion.py中創建一個編組(group),用於存儲所有有效的子彈,以便能夠管理發射出去的所有子彈。這個編組將是pygame.sprite.Group 類的一個實例;pygame.sprite.Group 類類似於列表,但提供了有助於開發遊戲的額外功能。在主迴圈中,我們將使用這個編組在螢幕上繪製子彈,以及更新每顆子彈的位置:

alien_invasion.py

 

  import pygame

  from pygame.sprite import Group

  --snip--

 

  def run_game():

      --snip--

      # 創建一艘飛船

      ship = Ship(ai_settings, screen)

      # 創建一個用於存儲子彈的編組

     bullets = Group()

 

      # 開始遊戲主迴圈

      while True:

          gf.check_events(ai_settings, screen, ship, bullets)

          ship.update()

         bullets.update()

          gf.update_screen(ai_settings, screen, ship, bullets)

 

  run_game()

 

 

 

 

 

 

 

 

我們導入了pygame.sprite 中的Group 類。在處,我們創建了一個Group 實例,並將其命名為bullets 。這個編組是在while 迴圈外面創建的,這樣就無需每次運行該迴圈時都創建一個新的子彈編組。

注意  如果在迴圈內部創建這樣的編組,遊戲運行時將創建數千個子彈編組,導致遊戲慢得像蝸牛。如果遊戲停滯不前,請仔細查看主while 迴圈中發生的情況。

我們將bullets 傳遞給了check_events() update_screen() 。在check_events() 中,需要在玩家按空白鍵時處理bullets ;而在update_screen() 中,需要更新要繪製到螢幕上的bullets 

當你對編組調用update() 時,編組將自動對其中的每個精靈調用update() ,因此代碼行bullets.update() 將為編組bullets 中的每顆子彈調用bullet.update() 

12.8.4 開火

game_functions.py中,我們需要修改check_keydown_events() ,以便在玩家按空白鍵時發射一顆子彈。我們無需修改check_keyup_events() ,因為玩家鬆開空白鍵時什麼都不會發生。我們還需修改update_screen() ,確保在調用flip() 前在螢幕上重繪每顆子彈。下麵是對game_functions.py所做的相關修改:

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:

          # 創建一顆子彈,並將其加入到編組bullets

          new_bullet = Bullet(ai_settings, screen, ship)

          bullets.add(new_bullet)

         --snip--

 

def check_events(ai_settings, screen, ship, bullets):

      """回應按鍵和滑鼠事件"""

      for event in pygame.event.get():

          --snip--

          elif event.type == pygame.KEYDOWN:

              check_keydown_events(event, ai_settings, screen, ship, bullets)

          --snip--

 

def update_screen(ai_settings, screen, ship, bullets):

      --snip--

      # 在飛船和外星人後面重繪所有子彈

     for bullet in bullets.sprites():

          bullet.draw_bullet()

      ship.blitme()

      --snip--

 

 

 

 

 

 

 

 

編組bulltes 傳遞給了check_keydown_events() (見)。玩家按空白鍵時,創建一顆新子彈(一個名為new_bullet Bullet 實例),並使用方法add() 將其加入到編組bullets 中(見);代bullets.add(new_bullet) 將新子彈存儲到編組bullets 中。

check_events() 的定義中,我們需要添加形參bullets (見);調用check_keydown_events() 時,我們也需要將bullets 作為實參傳遞給它。

處,我們給在螢幕上繪製子彈的update_screen() 添加了形參bullets 。方法bullets.sprites() 返回一個列表,其中包含編組bullets 中的所有精靈。為在螢幕上繪製發射的所有子彈,我們遍歷編組bullets 中的精靈,並對每個精靈都調用draw_bullet() (見

如果此時運行alien_invasion.py,將能夠左右移動飛船,並發射任意數量的子彈。子彈在螢幕上向上穿行,抵達螢幕頂部後消失,如圖12-3所示。可在settings.py中修改子彈的尺寸、顏色和速度。


12-3 飛船發射一系列子彈後的《外星人入侵》遊戲

12.8.5 刪除已消失的子彈

當前,子彈抵達螢幕頂端後消失,這僅僅是因為Pygame無法在螢幕外面繪製它們。這些子彈實際上依然存在,它們的 y 座標為負數,且越來越小。這是個問題,因為它們將繼續消耗記憶體和處理能力。

我們需要將這些已消失的子彈刪除,否則遊戲所做的無謂工作將越來越多,進而變得越來越慢。為此,我們需要檢測這樣的條件,即表示子彈的rect bottom 屬性為零,它表明子彈已穿過螢幕頂端:

alien_invasion.py

 

      # 開始遊戲主迴圈

      while True:

          gf.check_events(ai_settings, screen, ship, bullets)

          ship.update()

          bullets.update()

 

          # 刪除已消失的子彈

         for bullet in bullets.copy():

             if bullet.rect.bottom <= 0:

                  bullets.remove(bullet)

         print(len(bullets))

 

          gf.update_screen(ai_settings, screen, ship, bullets)

 

 

 

 

 

 

 

 

for 迴圈中,不應從列表或編組中刪除條目,因此必須遍歷編組的副本。我們使用了方法copy() 來設置for 迴圈(見),這讓我們能夠在迴圈中修改bullets 。我們檢查每顆子彈,看看它是否已從螢幕頂端消失(見)。如果是這樣,就將其從bullets 中刪除(見)。在處,我們使用了一條print 語句,以顯示當前還有多少顆子彈,從而核實已消失的子彈確實刪除了。

如果這些代碼沒有問題,我們發射子彈後查看終端視窗時,將發現隨著子彈一顆顆地在螢幕頂端消失,子彈數將逐漸降為零。運行這個遊戲並確認子彈已被刪除後,將這條print 語句刪除。如果你留下這條語句,遊戲的速度將大大降低,因為將輸出寫入到終端而花費的時間比將圖形繪製到遊戲視窗花費的時間還多。

12.8.6 限制子彈數量

很多射擊遊戲都對可同時出現在螢幕上的子彈數量進行限制,以鼓勵玩家有目標地射擊。下面在遊戲《外星人入侵》中作這樣的限制。

首先,在settings.py中存儲所允許的最大子彈數:

settings.py

 

        # 子彈設置

        self.bullet_width = 3

        self.bullet_height = 15

        self.bullet_color = 60, 60, 60

        self.bullets_allowed = 3

 

 

 

 

 

 

 

 

這將未消失的子彈數限制為3顆。在game_functions.pycheck_keydown_events() 中,我們在創建新子彈前檢查未消失的子彈數是否小於該設置:

game_functions.py

 

def check_keydown_events(event, ai_settings, screen, ship, bullets):

    --snip--

    elif event.key == pygame.K_SPACE:

        # 創建新子彈並將其加入到編組bullets

        if len(bullets) < ai_settings.bullets_allowed:

            new_bullet = Bullet(ai_settings, screen, ship)

            bullets.add(new_bullet)

 

 

 

 

 

 

 

 

玩家按空白鍵時,我們檢查bullets 的長度。如果len(bullets) 小於3,我們就創建一個新子彈;但如果已有3顆未消失的子彈,則玩家按空白鍵時什麼都不會發生。如果你現在運行這個遊戲,螢幕上最多只能有3顆子彈。

12.8.7 創建函數update_bullets()

編寫並檢查子彈管理代碼後,可將其移到模組game_functions 中,以讓主程序文件alien_invasion.py盡可能簡單。我們創建一個名為update_bullets() 的新函數,並將其添加到game_functions.py的末尾:

game_functions.py

 

def update_bullets(bullets):

   """更新子彈的位置,並刪除已消失的子彈"""

    # 更新子彈的位置

    bullets.update()

 

    # 刪除已消失的子彈

    for bullet in bullets.copy():

        if bullet.rect.bottom <= 0:

            bullets.remove(bullet)

 

 

 

 

 

 

 

 

update_bullets() 的代碼是從alien_invasion.py剪切並粘貼而來的,它只需要一個參數,即編組bullets 

alien_invasion.py中的while 迴圈又變得很簡單了:

alien_invasion.py

 

      # 開始遊戲主迴圈

      while True:

         gf.check_events(ai_settings, screen, ship, bullets)

         ship.update()

         gf.update_bullets(bullets)

         gf.update_screen(ai_settings, screen, ship, bullets)

 

 

 

 

 

 

 

 

我們讓主迴圈包含盡可能少的代碼,這樣只要看函數名就能迅速知道遊戲中發生的情況。主迴圈檢查玩家的輸入(見),然後更新飛船的位置(見)和所有未消失的子彈的位置(見)。接下來,我們使用更新後的位置來繪製新螢幕(見

12.8.8 創建函數fire_bullet()

下面將發射子彈的代碼移到一個獨立的函數中,這樣,在check_keydown_events() 中只需使用一行代碼來發射子彈,讓elif 代碼塊變得非常簡單:

game_functions.py

 

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

    """如果還沒有到達限制,就發射一顆子彈"""

    #創建新子彈,並將其加入到編組bullets

    if len(bullets) < ai_settings.bullets_allowed:

        new_bullet = Bullet(ai_settings, screen, ship)

        bullets.add(new_bullet)

 

 

 

 

 

 

 

 

函數fire_bullet() 只包含玩家按空白鍵時用於發射子彈的代碼;在check_keydown_events() 中,我們在玩家按空白鍵時調用fire_bullet() 

請再次運行alien_invasion.py,確認發射子彈時依然沒有錯誤。

動手試一試

12-5 側面射擊 :編寫一個遊戲,將一艘飛船放在螢幕左邊,並允許玩家上下移動飛船。在玩家按空白鍵時,讓飛船發射一顆在螢幕中向右穿行的子彈,並在子彈離開螢幕而消失後將其刪除。

12.9 小結

在本章中,你學習了:遊戲開發計畫的制定;使用Pygame編寫的遊戲的基本結構;如何設置背景色,以及如何將設置存儲在可供遊戲的各個部分訪問的獨立類中;如何在螢幕上繪製圖像,以及如何讓玩家控制遊戲元素的移動;如何創建自動移動的元素,如在螢幕中向上飛馳的子彈,以及如何刪除不再需要的物件;如何定期重構專案的代碼,為後續開發提供便利。

在第13章中,我們將在遊戲《外星人入侵》中添加外星人。在第13章結束時,你將能夠擊落外星人——但願是在他們撞到飛船前!

 

0 留言:

發佈留言