Python遊戲開發之精靈和精靈組

1. 基本概念

接下來介紹兩個pygame中提供的高級類, 精靈和精靈組.

在介紹這兩個類之前, 先來共同回顧一下到目前為止掌握的遊戲開發套路.

在遊戲初始化,需要加載一下遊戲中所有的圖像, 然後呢,在遊戲循環中,需要針對每張圖像來編寫代碼、修改圖像的位置,並且需要用screen對象來調用一下blit 方法,把所有變化位置的圖像重新做一個繪制.

那現在假設開發的遊戲,需要處理100張圖像,意味著遊戲循環內部的代碼就會變得非常的繁瑣.

因為需要在遊戲循環中針對每張圖像的位置變化,編寫代碼,然後再讓screen對象調用blit 方法依次繪制每個圖像,這樣的代碼呢,就顯得太繁瑣瞭.

那怎麼樣有效的解決這個問題呢?pygame就提供瞭兩個高級類,一個是精靈,一個是精靈組.

精靈這個詞匯聽起來有些奇怪,可以把精靈理解成一個對象,在一個精靈對象中有兩個重要的屬性,一個是精靈要顯示的圖像數據,一個是精靈要把這個圖像顯示在屏幕上的位置.

一句話講,包含瞭圖像數據和顯示位置的對象,就可以把它叫做精靈,同時呢,在精靈類中啊,還提供瞭一個非常重要的方法update,

在開發時, 可以根據不同的遊戲角色派生出不同的遊戲子類, 然後在每個子類中根據各自的需求分別重寫各自的update 方法, 在update 方法中, 專門來處理一下當前這個遊戲角色位置變化的代碼, 這個就是update方法的作用. 

之所以要派生子類, 是因為不同的遊戲角色在屏幕上的運動方式是不一樣的,.

一個精靈類, 提供瞭一個image屬性, 提供瞭一個rect 屬性,並且提供瞭一個重要的update方法.

這個就是精靈類最重要的兩個屬性和一個方法.

那接下來看一下精靈組,從字面上來看,精靈組是一個包含瞭多個精靈的對象,在創建精靈組的時候可以使用多值參數的方式,一次性把精靈組中包含的所有精靈傳入到精靈組內部.

當有瞭精靈組之後,當創建瞭一個精靈組之後,在編寫遊戲循環代碼時,隻需要在遊戲循環中,讓精靈組調用兩個方法,第一個方法update,第二個方法draw.

這兩個方法有什麼作用呢?當讓精靈組 調用update方法時,精靈組,就會讓組中所有的精靈,各自調用各自的update方法,精靈組調用一次update 方法,精靈組中所有的精靈就會各自調用各自的update方法.

各自都用各自的update方法,就可以根據遊戲的需求,各自修改各自不同的位置,當所有的精靈更新完位置之後,再讓精靈組調一下draw 方法,同時呢,把屏幕對象當作參數傳遞給 draw 方法, draw 方法會把精靈組中所有精靈的image繪制到屏幕上每一個精靈對應的rect 位置。

在精靈中包含有兩個重要的屬性,一個是精靈要顯示的圖像數據,一個是精靈在屏幕上的位置,而在遊戲循環中,讓精靈組調用一下draw 方法,就可以一次性的把精靈組所有的精靈圖像,各自繪制到不同的位置上.

這個就是使用精靈組之後,遊戲循環的代碼能夠得到大大的改善.

但是務必要註意哦,當讓精靈組調用瞭draw 方法之後,如果希望在屏幕上看到最終的繪制結果,同樣是需要調用一下display模塊兒提供的update的方法,因為啊,隻有調用瞭update的方法,才能夠在屏幕上看到最終的繪制結果。

簡單介紹瞭一下pygame中提供的兩個高級類,一個是精靈,一個是精靈組.

在精靈類中封裝瞭兩個重要的屬性,一個是精靈顯示的圖像數據image ,一個是精靈在屏幕上對應的顯示位置rect ,同時,每個精靈都各自有自己不同的update 方法,在開發時可以派生一個精靈的子類,根據遊戲角色不同,重寫update的方法,在update 方法內部,針對當前這個精靈角色編寫代碼、修改精靈的位置就可以,當精靈建立完成,就可以建立一個精靈組,一個精靈組是可以包含多個精靈的,當有瞭精靈組之後再編寫遊戲循環的代碼,隻需要讓精靈組調用update的方法,update 方法就可以讓所有的精靈同時更新位置,然後呢,再調一下draw 方法,draw 方法就會把更新位置之後的精靈繪制到屏幕對應的位置,這個就是精靈和精靈組的作用.

2. 自定義精靈子類需求分析

接下來就做一下派生精靈子類的演練,先共同來明確一下派生精靈子類這個演練的需求與步驟,第一步啊,要在項目中建立一個plane_sprites.py 的python 文件.

從這一小節開始,就要正式的進入到面向對象程序開發瞭,而在使用面向對象程序開發時,給每個模塊起名都需要有一個意識,就是起一個稍微正式一些的名字,而在這一小節派生出來的精靈子類會用在後續的飛機大戰實戰中, 所以啊,給他起一個稍微正式一些的名字,飛機精靈,然後呢,在這個模塊中建立一個Game_Sprites類,讓這個遊戲精靈類繼承自pygame提供的精靈類pygame.sprites.Sprites .

在這裡提示一下,在使用pygame時,第一個點後面通常是模塊的名稱,而第二個點後面才是類的名稱,回顧一下,在給類起名時,首字母應該大寫,所以啊,pygame提供的精靈類準確的名稱是點兒sprite點兒Sprite.

第一個sprite是模塊的名稱,第二個Sprite才是類的名稱.

現在要重點強調一個話題,在使用面向對象開發時,如果某一個類的父類不是object這個基類,也就是開發的這個類,不是繼承自object基類,那麼,在這個類的初始化方法中, 一定要先調用一下父類的初始化方法,現在要開發的遊戲精靈類是繼承自pygame的精靈類,那麼試想一下,在這個父類的初始化方法中,是不是很有可能已經提前封裝瞭一部分代碼,那麼,如果在子類的初始化方法中不調用父類的初始化方法,就不能夠享受到父類中原本封裝的代碼.

所以啊,為瞭保證父類原本封裝的初始化代碼能夠正常的被執行,在開發時一定要記住,如果開發的子類不是繼承自object這個基類,那麼,在初始化方法中一定都要主動調用一下父類的初始化方法.

來看一下遊戲精靈類的需求,在遊戲精靈類中啊,封裝三個屬性,分別是image, rect 和speed ,speed這個單詞翻譯過來是速度的意思,現在要做的是飛機大戰的遊戲.

飛機大戰意味著屏幕上的每一個精靈都各自擁有不同的飛行速度,所以在這一小節的演練中,就在遊戲精靈這個類中封裝一個速度的屬性.

要給對象定義屬性應該在初始化方法中定義,那現在再強調一下,這一小節要編寫的精靈子類是繼承自pygame的精靈類的,所以啊,必須要在初始化方法中主動調用一下父類的初始化方法,隻有這樣才能夠保證父類已經封裝好的初始化代碼能夠被正常的執行,那現在來看一下初始化方法的參數.

第一個參數是image_name,第二個參數是speed ,有瞭image_name這個參數,就可以在初始化方法中通過image.load來加載出圖像,當圖像加載出來之後,怎麼樣指定圖像的rect 屬性呢?在這裡分享的小技巧,在pygame的image對象,提供瞭一個get_rect()方法,

讓圖像調用一下這個方法就會返回一個矩形對象, 矩形對象的x, y都是0,但是矩形對象的寬和高就是剛剛加載出來的圖像的寬和高.

通過get_rect() 這個方法,就能夠在初始化方法內部非常方便的指令一下圖像精靈的初始位置,然後呢,再使用speed 這個參數來指定一下遊戲精靈的初始速度,如果在調用時不指定速度,就使用默認的1 來設置一下遊戲精靈的速度.

這個就是遊戲精靈中,要封裝的三個屬性,圖像,位置以及速度,那接下來再來看一下update這個方法,在這一小節演練中,Update這個方法,讓圖像精靈的y值跟速度進行相加,要修改y值會讓遊戲精靈在屏幕的垂直方向進行運動,這個就是接下來要派生的精靈子類的需求.

一句話講封裝三個屬性,圖像, 位置以及速度, 重寫一個update的方法,在update方法中,讓遊戲精靈在屏幕上做垂直方向的運動.

3. 派生精靈子類代碼實現

接下來就參照這張類圖共同實現一下遊戲精靈的子類開發,已經提前準備好瞭plane_sprites 這個模塊文件.

在這一小節要開發的遊戲精靈是繼承自pygame的精靈子類,所以啊,應該先在模塊中使用import關鍵字導入一下pygame這個模塊兒, 導入之後,就使用class關鍵字來給類起個名字,GameSprite,然後在小括號中指令一下遊戲精靈的父類,寫下pygame.sprite,

 第一個sprite是模塊的名稱,需要再敲一個點兒,然後輸入大寫的Sprite , 大寫的Sprite才是類的名稱.

 那現在就在類名下方增加一個文檔註釋, 寫一下飛機大戰遊戲精靈, 文檔註釋增加完成, 再來看一下類圖,

 在遊戲精靈類GameSprite中, 需要封裝三個屬性, 既然要定義對象的屬性, 就應該先實現一下初始化方法.

 在初始化方法的參數中, 需要傳入圖像的名稱image_name, 以及精靈的初始速度speed.

先使用def 關鍵字找到__init__ 這個初始化方法, 然後呢, 然後增加兩個形參. 初始化方法準備完成, 先敲一個pass 占位, 現在一個簡單的初始化方法就準備完成瞭.

pycharm 以深黃色的背景提示初始化方法沒有調用父類的初始化方法,之前提到過, 當在開發時某一個子類的父類不是object基類,一定要在初始化方法中主動調用一下父類的初始化方法.

那現在就增加兩個註釋, 第一步呢,應該調用父類的初始化方法,調用完成之後,再來定義對象的屬性,兩個步驟寫完, 要想調用父類的方法,應該有一個特殊的對象,可以讓super這個對象來調用一下父類的初始化方法.

當父類的初始化方法調用完成, 就可以在下方定義一下遊戲精靈的三個屬性, 分別是圖形, 位置和速度這三個屬性.

先使用self點 來定義第一個屬性image, 要想從一個圖像文件中加載數據,可以使用pygame點image,然後調用load 方法,把傳入的image_name 當做參數,傳遞給這個方法,這樣就可以把指定名稱的圖像加載到圖像屬性中瞭.

圖像屬性定義完成,緊接著定義一個rect 屬性,這個rect 屬性, 默認大小,如果要是圖像的大小, 可以讓image 調用一下get_rect 方法,get_rect()方法返回的大小就是剛剛加載出來的圖像大小,同時x和y都是零.

現在第2個屬性定義完成,再來定義第3個屬性速度,

self.speed 就直接把形參的速度傳遞過來,現在三個屬性定義完成.

就把pass這個占位符刪除一下,

三個屬性定義完成,再來看一下類圖, 在這一小節中,還需要重寫一下父類的update方法,在update方法中,讓遊戲的精靈在垂直方向上運動,那現在就使用def 關鍵字, 然後輸入一個update小括號.

update方法找到之後, 遊戲精靈需要在垂直方向上移動,那現在增加一個註釋,在屏幕的垂直方向上移動,要在屏幕的垂直方向上移動,就可以修改一下self.rect屬性的y 屬性,讓y屬性來加上當前的速度屬性,這樣相加之後意味著在創建對象時指定的速度不同,那麼遊戲精靈在屏幕上移動的速度也會不同.

代碼演進到這裡,就實現瞭一下遊戲精靈這個子類的代碼,讓遊戲精靈繼承自pygame 的精靈類,同時在初始化方法中,先調用瞭一下父類的初始化方法,然後呢,按照類圖的需求依次定義瞭三個屬性,圖像, 位置以及速度,並且重寫瞭一下父類的update方法,在update的方法中,對rect 的y值做瞭一個修改,讓y值加上速度,這樣呢就能夠實現讓遊戲精靈按照垂直方向進行移動瞭.

再強調一下,因為在開發子類的時候,如果子類的父類不是object這個基類, 一定要記住,在初始化方法中需要主動的調用父類的初始化方法,因為不主動調用父類的初始化方法就沒有辦法享受到父類中已經封裝好的初始化代碼瞭.
 

4. 創建敵機並且實現敵機動畫

接下來就使用剛剛派生的遊戲精靈這個類來創建一個低級精靈對象,並且實現一下敵機的動畫效果。在開始動手之前,先讓來明確一下這一小節的演練步驟.

首先使用from這個關鍵字,把plane_sprites這個模塊導入一下,然後呢,在遊戲初始化位置來創建一個敵機的精靈對象,並且創建一個敵機的精靈組對象.

當精靈對象和精靈組對象創建完成,就在遊戲循環中,讓精靈組對象分別調用一下update方法和draw 方法.

在開始演練之前,再強調一下精靈和精靈組這兩個對象的職責,精靈對象是負責封裝精靈要顯示的圖像以及精靈在屏幕上的位置,並且封裝一下精靈的運動速度, 同時精靈這個對象還要提供一個update方法,在update方法中根據遊戲的需求來更改自己的位置變化, 這個是精靈對象的職責.

那麼再看一下精靈組對象的職責,精靈組對象可以包含多個精靈對象,當精靈組對象建立完成,就可以在遊戲循環中讓精靈組對象來調用update 方法和draw 方法,update 方法會讓精靈組中所有的精靈各自調用各自的update方法.

調用之後,精靈組中所有的精靈位置都會發生變化, 當所有精靈的位置調整完畢,就讓精靈組再調用一下draw 方法,調用draw 方法之後就會把精靈組中所有的精靈全部繪制在screen這個屏幕對象上, 這個就是精靈和精靈組的職責.

明確瞭演練步驟,並且強調瞭一下精靈和精靈組的職責之後,現在就做一下代碼演練,因為現在看到的代碼是之前一個小節完成的監聽退出事件的代碼,先運行一下程序驗證一下現在的代碼執行效果.

遊戲啟動瞭,

現在點擊叉叉,可以退出遊戲.

這個是之前一個小節完成的代碼.

那在這一小結中,就在之前一個小節完成的代碼基礎上, 來實現一下敵機精靈的創建,並且實現一下敵機的動畫效果.

首先把光標放在第2行使用from這個關鍵字,從plane_sprites這個模塊來導入一下所有的內容,

導入之後, 在開發時使用import 導入模塊和from 導入模塊有什麼區別呀?使用import 導入模塊,在使用這個模塊時,必須要使用模塊點的方式來使用.

而使用from來導入模塊,在使用時可以直接使用模塊提供的工具,而不再需要輸入模塊的名稱.

現在飛機精靈的模塊導入之後,就把代碼向下方滾動,來找到遊戲循環,為瞭看清楚這一小節完成的代碼,在遊戲循環上方多增加幾個空行.

現在先增加一個單行註釋來明確一下這一小節要演練的重點. 在遊戲初始化位置應該創建敵機的精靈,創建完精靈之後, 還需要創建敵機的精靈組,精靈要在初始化位置創建, 精靈組同樣也要在初始化位置創建.

那現在就把光標放在31行,先給敵機的精靈起個名字叫做enemy ,然後使用上一小節派生的遊戲精靈類來創建一個精靈對象.

在創建精靈對象時, 第1個參數需要指定圖像的名稱,那現在先寫一個點表示當前目錄,然後寫一個image,那要加載哪一張圖像呢?就把images這個目錄展開,敵機的圖像是enemy1,那麼就在image 後面寫上enemy1.png ,圖像指定完成,一個敵機的精靈也創建完成.

那現在就來創建一下敵機的精靈組,同樣先給精靈組起個名字enemy_group,然後呢,使用pygame提供的sprite模塊中提供的Group類來創建一個精靈組,

在創建精靈組的時候,可以把多個精靈以多值參數的方式傳遞給精靈組, 那麼就把剛剛創建的敵機精靈傳遞給這個精靈組,現在敵機的精靈組也創建完成瞭.

那現在有瞭精靈,也有瞭精靈組, 但現在運行程序不能夠看到敵機的畫面.

因為要看到敵機的畫面,需要在遊戲循環中讓精靈組調用draw 方法.

來先運行驗證一下,遊戲啟動瞭,但是隻有英雄的飛機孤孤單單,並沒有敵機的身影,那現在先停止一下程序.

現在已經在遊戲初始化位置創建出來瞭敵機的精靈, 創建出來瞭敵機的精靈組,那接下來應該把代碼移動到遊戲循環內部,在遊戲循環中讓敵機的精靈組來調用兩個方法,那現在就找到update這個方法,然後多增加幾個空行,在這裡先增加一個註釋,讓精靈組調用兩個方法.

讓精靈組需要調用哪兩個方法,第1個方法是update,第2個方法是draw.

update 方法可以讓精靈組中所有的精靈對象都執行一下update 方法,而draw 方法呢,會把精靈組中所有精靈的圖像繪制在屏幕上,那現在就使用enemy_group來調用一下update方法,enemy_group.update(), 調用完成,再讓enemy_group來調用一下draw 方法, enemy_group.draw(screen).

在調用draw 方法的時候,需要把屏幕對象傳遞給方法,因為精靈組需要知道把精靈組內部的精靈繪制到誰的身上,現在就選中draw 方法,並且把之前創建好的screen 對象傳遞給這個方法, 又增加瞭兩行代碼,運行一下程序,看看這一次能不能看到敵機的身影.

程序啟動瞭, 一個敵人的小飛機,從上向下再運動瞭,現在英雄已經不再孤單瞭.

代碼寫到這裡, 創建瞭一個精靈,創建瞭一個精靈組,就已經實現瞭敵機的精靈動畫.

那麼先來看一下遊戲循環內部的代碼,在遊戲循環中,隻是讓精靈組調用瞭一下update方法.

對比一下之前的英雄飛機,之前的英雄飛機,需要在遊戲循環中修改飛機的位置,並且判斷飛機的位置,

而有瞭精靈組之後,直接讓精靈組調用一個update 方法,調用之後精靈組就能夠讓所有的精靈更新位置.

寫一下註釋,讓組中的所有精靈更新位置,這個是update方法的好處.

那麼再來看一下draw 方法,之前在繪制圖像時都是讓screen 對象調用blit 方法,然後要指定一下,把這個圖像繪制到哪裡,

而使用精靈組隻需要調用一下draw 方法,並且把屏幕對象傳遞給draw 方法, 精靈組就會把內部的所有精靈全部繪制在屏幕上.

這個就是draw 方法的好處,再寫下註釋,在screen上繪制所有的精靈,這就是使用精靈和精靈組在開發遊戲時能夠大大簡化遊戲循環內部的代碼.

那現在再看一下之前創建精靈和精靈組的代碼,之前創建瞭一個敵機的精靈,把敵機的精靈添加到瞭精靈組中,添加之後, 現在運行程序, 屏幕上就有瞭一個敵人的飛機.

精靈組中可以包含多少個精靈?精靈組中可以包含多個精靈.

那既然可以包含多個精靈,就對這個代碼進行一個擴展,再來定一個敵機的對象,給他起個名字叫做enemy1,然後同樣使用GameSprite()來創建一個敵人的飛機,再指定一下圖像位置,image下的enemy1.png,指定完成,在創建遊戲精靈時,還可以指定一下精靈的速度.

那現在把第2架飛機的速度值乘2,這樣呢,可以跟第1架敵機加以區分,現在創建瞭兩架敵機的精靈,就可以把第2架敵機同樣也添加到敵機的精靈組中.

現在就以多值參數的方式再添加一個敵機, 代碼改造完成.

現在運行一下程序,看一下屏幕上是有幾個敵機.

遊戲啟動瞭,兩個敵人的小飛機從上向下持續飛行,並且飛行的速度各不相同.

再來對比一下現在的代碼,通過一個簡單的改造再創建一個敵機的精靈,把敵機的精靈添加到精靈組中, 不需要在對遊戲循環的代碼進行任何的修改, 輕輕松松就可以在遊戲中又增加瞭一個敵機的對象.

在這一小節,就使用之前一個小節也派生出來的遊戲精靈為創建瞭兩架敵機,並且實現瞭一下敵機的動畫效果.

在這一小節中,重點是要體會一下精靈和精靈組的職責,精靈這個對象負責封裝圖像, 位置以及速度並且提供一個update的方法,update 方法,會根據遊戲的需求修改精靈自己應該更新的位置,這個是精靈的職責,而精靈組負責包含多個精靈對象, 精靈組隻需要在遊戲循環中,分別調用一下update方法和draw 方法就可以瞭,update 方法可以更新組中所有精靈的位置,而draw 方法呢會把組中所有的精靈全部繪制在屏幕對象上.

總結

到此這篇關於Python遊戲開發之精靈和精靈組的文章就介紹到這瞭,更多相關Python精靈和精靈組內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: