八、Python基礎(綜合演練:飛機大戰)
目錄:
- 八、Python基礎(綜合演練:飛機大戰)
- 一、模塊
- 1.利用 pip 安裝 pygame 模塊
- 2.圖片素材下載
- 二、pygame 模塊初識
- 1.游戲的初始化和退出
- 2.pygame 中的游戲坐標系
- 矩形區域物件名 = pygame.Rect(x, y, width, height)
- 3.創建游戲主視窗
- 游戲主視窗物件名 = pygame.display.set_mode(resulution=(0, 0), flags=0, depth=0)
- 4.游戲主視窗上影像的繪制
- 圖片物件名 = pygame.image.load(圖片所在路徑)
- 游戲主視窗物件名.blit(圖片物件名, 矩形區域物件或(x, y)坐標)
- pygame.display.update()
- 5.階段小結(一):英雄飛機顯示
- 6.擴展
- 三、游戲主功能
- 1.影片實作原理——幀 Frame
- 2.游戲回圈
- 3.游戲時鐘
- 時鐘物件名 = pygame.time.Clock()
- 時鐘物件名.tick(幀)
- 4.英雄飛機的簡單影片顯示
- 5.英雄飛機的正確影片顯示
- 6.英雄飛機的動作影片顯示
- 7.在游戲回圈中監聽事件
- 四、pygame高級類:精靈與精靈組
- 1.精靈與精靈組簡介
- 2.派生精靈子類
- 3.使用精靈和精靈組創建敵機
- 五、游戲框架的搭建
- 1.檔案分配
- 2.游戲方式——背景的輪換滾動
- 3.飛機大戰核心框架
- 3.敵機定時與隨機出場設計
- pygame.time.set_time(eventid, millisecond)
- 4.英雄飛機和子彈發射設計
- 5.碰撞檢測——“子彈與敵機”和“英雄飛機與敵機”之間
- pygame.sprite.groupcollide()
- pygame.sprite.spritecollide()
- 5.源代碼
- (1)plane_sprites.py 檔案:
- (2)plane_main.py 檔案:
- 上一篇文章
一、模塊
1.利用 pip 安裝 pygame 模塊
Windows系統下的安裝參考如下文章:
- Windows作業系統下安裝pip and 安裝pygame
注:應在PyCharm的系統解釋器的 Scripts 目錄安裝了 pygame 再新建工程
Linux系統下的安裝:
安裝pygame
sudo pip3 install pygame
驗證安裝(aliens是一個內置小游戲)
python3 -m pygame.examples.aliens
2.圖片素材下載
圖片素材直接在下述鏈接下載即可:
- 素材下載(百度網盤)
(同時感謝素材的分享者)
二、pygame 模塊初識
1.游戲的初始化和退出
| 方法 | 含義 |
|---|---|
| pygame.init() | 匯入并初始化所有pygame模塊,使用其他模塊之前,必須先呼叫init方法 |
| pygame.quit() | 卸載所有pygame模塊,在游戲結束之前呼叫 |
2.pygame 中的游戲坐標系
- 原點在左上角 O(0,0)
- x軸水平方向向右,逐漸增加
- y軸豎直方向向下,逐漸增加

- 在游戲中,所有可見的元素都是以上述矩形區域來描述位置的
- 在 游戲視窗(坐標系)中,要描述一個矩形區域應該有四要素:(x,y) 和 (width,height),其中 (x,y) 描述的是矩形區域左上角(原點)所在的坐標,而 (width,height) 則控制矩形區域的大小
在pygame中提供了一個類 pygame.Rect,其用于描述矩形區域:
矩形區域物件名 = pygame.Rect(x, y, width, height)
創建一個矩形區域的物件
其中:x, y, width, height 均屬于物件屬性,可以通過物件名來訪問
另有一個 size 的元組屬性:它是一個元組——(width, height),可以直接訪問到 width 和 height,可以作為引數值來傳遞到 pygame.display.set_mode(…) 中,之后會講到
例:在坐標系原點創建一個width=100,height=140的 plane 物件
import pygame
pygame.init()
plane = pygame.Rect(0, 0, 100, 140)
print("飛機所在位置:(%d,%d),飛機的大小:(%d,%d)" % (plane.x, plane.y, plane.width, plane.height))
pygame.quit()

第6行代碼更改為下述代碼也可以:
print("飛機所在位置:(%d,%d)" % (plane.x, plane.y), "飛機的大小:(%d,%d)" % plane.size)

3.創建游戲主視窗
在pygame中提供了一個模塊 pygame.display,其用于創建、管理游戲主視窗:
| 方法 | 含義 |
|---|---|
| pygame.display.set_mode() | 初始化游戲顯示視窗 |
| pygame.display.update | 重繪顯示游戲視窗 |
游戲主視窗物件名 = pygame.display.set_mode(resulution=(0, 0), flags=0, depth=0)
創建一個游戲顯示視窗的物件
其中:resoluthon, flags, depth均屬于物件屬性
resolution 是一個元組,需要傳遞一個元組,它指定螢屏的寬和高,預設時默認值為整個螢屏的大小,這個元組可以是 pygame.Rect(…) 函式回傳的矩形區域物件的 size 屬性 ——它是個元組
flags 指定螢屏的附加選項,例如是否全屏等,默認不需要傳遞
depth 表示顏色的位數,默認自動匹配
回傳值 游戲的主視窗,游戲的元素都需要被繪制到游戲的主視窗上
例:創建一個 (480,700) 的游戲主視窗
import pygame
import time
"""視窗大小:(480,700)"""
window = pygame.display.set_mode((480, 700))
"""系統休眠5秒"""
time.sleep(5)
pygame.quit()

4.游戲主視窗上影像的繪制
在游戲中,能夠看到的游戲元素大多數是影像,影像檔案初始是保存在磁盤上的,如果需要使用,第一步就需要把影像加載到記憶體中,一般要在螢屏上看到某一個影像的內容,需要安裝下述三個步驟來進行:
- 使用 pygame.image.load() 加載影像的資料
- 使用 游戲主視窗 物件來呼叫 blit 方法將影像繪制到指定的位置
- 使用 pygame.display.update() 方法更新整個螢屏的顯示

圖片物件名 = pygame.image.load(圖片所在路徑)
把影像打開并把資料加載到記憶體中,其中路徑可以是圖片相對源代碼所在 .py檔案 的 相對路徑 或 絕對路徑,回傳圖片物件
游戲主視窗物件名.blit(圖片物件名, 矩形區域物件或(x, y)坐標)
在游戲主視窗中繪制影像
矩形區域物件 圖片物件在游戲主視窗中矩形區域原點對應的位置,一般所設定的這個矩形區域與圖片的大小一致,以此來讓圖片對應這片矩形區域——圖片負責影片的實作,矩形區域負責游戲的邏輯控制
(x, y) 坐標元組,即圖片物件在游戲主視窗的位置pygame.display.update()
重繪顯示游戲主視窗
提示:要想在螢屏上看到繪制的結果,就一定要呼叫 pygame.display.update() 方法
例:我們嘗試將飛機大戰的背景影像 background.png 檔案加載到游戲主視窗上

import pygame
import time
pygame.init()
"""創建游戲主視窗"""
window = pygame.display.set_mode((480, 700))
"""加載圖片到記憶體"""
background = pygame.image.load("./游戲素材/background.png")
"""在游戲主視窗中顯示圖片"""
window.blit(background, (0, 0))
"""重繪顯示"""
pygame.display.update()
"""系統休眠5秒"""
time.sleep(5)
pygame.quit()

提示:我們可以通過 blit() 方法將游戲主視窗的所有的影像布置完成后,再使用 pygame.display.update() 重繪游戲主視窗即可
5.階段小結(一):英雄飛機顯示
需求:把英雄飛機 me1.png 或 me2.png 檔案顯示在游戲主視窗的背景上
import pygame
import time
pygame.init()
"""創建游戲主視窗"""
window = pygame.display.set_mode((480, 700))
"""加載需要的圖片"""
background = pygame.image.load("./游戲素材/background.png")
me1 = pygame.image.load("./游戲素材/me1.png")
"""繪制背景和飛機"""
window.blit(background, (0, 0))
window.blit(me1, (189, 574))
"""重繪顯示"""
pygame.display.update()
time.sleep(5)
pygame.quit()

我們使用 PyCharm 或 PS 打開圖片,發現英雄飛機圖片的背景是一系列灰白相間的小格子,這些灰白相間的小格子代表英雄飛機是透明背景:

6.擴展
本文只針對 pygame 模塊中一些簡單的功能進行介紹,實際上 pygame 還擁有很多強大的功能,若有興趣學習的可以參考下述文章:
- Python游戲工具包—Pygame最常用的15個模塊詳解(附pdf版本)
- pygame官網
- pygame官網檔案
三、游戲主功能
1.影片實作原理——幀 Frame
我們知道,視頻是一幀一幀地播放的,其實質是由一幀一幀變化的影像的動態重繪顯示來呈現運動的效果,其利用了肉眼的視覺暫留原理,因此我們可以利用計算機對多張圖片的快速重繪顯示,即可達到影片的效果
- 一般在電腦上每秒繪制60次,就能夠達到非常連續高品質的影片效果,每次繪制的結果被稱為 幀 Frame
- 每秒播放影像的次數被稱為 每秒傳輸幀數 FPS(Frames Per Second)),這就是游戲中的 fps
2.游戲回圈
通常,游戲回圈意味著游戲的正式開始

游戲回圈的作用:
- 保證游戲不會直接退出
- 變化影像位置——達到影片效果
每隔1/60秒移動一下所有影像的位置
呼叫 pygame.display.update() 重繪顯示 - 檢測用戶的互動——鍵盤、滑鼠等外設
3.游戲時鐘
在Python中,while True: 回圈的速度可達每秒鐘數十萬次,而我們對影像幀率的要求不需要這么高,pygame 中有一個時鐘類 pygame.time.Clock 可以非常方便地設定游戲主視窗的繪制速度——重繪幀率
時鐘物件名 = pygame.time.Clock()
創建一個時鐘物件
時鐘物件名.tick(幀)
可以設定時鐘的幀率,相當于在呼叫 tick() 方法的位置處暫停 1/幀 秒的時間,以此來實作固定幀率的重繪
例:
import pygame
pygame.init()
clock = pygame.time.Clock()
i = 0
while True:
clock.tick(1)
print(i)
i += 1
pygame.quit()

這里的0、1、2、3、4是每秒鐘增加一個的
4.英雄飛機的簡單影片顯示
注:由于 blit() 方法可以傳遞 坐標元組 或者 矩形區域物件,因此在創建了一個矩形區域物件后,我們就可以直接用矩形區域對象來作為影片播放時圖片的位置,讓圖片位置與矩形區域位置一一對應
例:利用 Rect 的矩形區域在y軸上的移動來實作飛機的移動
import pygame
pygame.init()
"""創建游戲主視窗"""
window = pygame.display.set_mode((480, 700))
"""加載需要的圖片"""
background = pygame.image.load("./游戲素材/background.png")
me1 = pygame.image.load("./游戲素材/me1.png")
"""創建一個矩形區域物件,設定初始位置"""
hero_plane = pygame.Rect(189, 574, 102, 126)
"""設定一個時鐘"""
clock = pygame.time.Clock()
"""繪制背景"""
window.blit(background, (0, 0))
while True:
window.blit(me1, hero_plane)
"""重繪顯示"""
pygame.display.update()
hero_plane.y -= 50
clock.tick(1)
pygame.quit()

5.英雄飛機的正確影片顯示
注:由于在同一張背景下繪制的圖片不會消失,因此如果想要在一張背景下實作英雄飛機的影片式的變化,可以在游戲主視窗中再繪制一張背景,把原來的影像覆寫掉,再繪制英雄飛機圖片,這樣就可以實作英雄飛機的影片式變化
以上述代碼為基礎,只需把window.blit(background, (0, 0))放在while True:中即可實作
while True:
"""繪制背景"""
window.blit(background, (0, 0))
window.blit(me1, hero_plane)
"""重繪顯示"""
pygame.display.update()
hero_plane.y -= 50
clock.tick(1)

注:這里飛機會飛出螢屏,而我們只需給矩形區域的位置做簡單的判斷即可實作飛機周而復始的運動;另外,我們調整一下幀率和飛機的移動距離,實作影片的連貫播放
if hero_plane.y + hero_plane.height <= 0:
hero_plane.y = 700
else:
hero_plane.y -= 1
clock.tick(240)

6.英雄飛機的動作影片顯示
利用兩張或多張不同的圖片在同一位置的顯示,來完成英雄飛機的動作——噴氣
例:利用 me1.png 和 me2.png 的不同來重繪顯示英雄飛機的變化
注:由于在同一個背景下繪制的影像不會消失,因此要想在背景下顯示不同的影響,則需要繪制新的背景覆寫原背景
import pygame
pygame.init()
"""創建游戲主視窗"""
window = pygame.display.set_mode((480, 700))
"""加載需要的圖片"""
background = pygame.image.load("./游戲素材/background.png")
me1 = pygame.image.load("./游戲素材/me1.png")
me2 = pygame.image.load("./游戲素材/me2.png")
"""設定一個時鐘"""
clock = pygame.time.Clock()
while True:
"""繪制背景和飛機"""
window.blit(background, (0, 0))
window.blit(me2, (189, 574))
"""重繪顯示"""
pygame.display.update()
clock.tick(5)
"""重新繪制背景,以覆寫原來的影像"""
window.blit(background, (0, 0))
window.blit(me1, (189, 574))
"""重繪顯示"""
pygame.display.update()
clock.tick(5)

7.在游戲回圈中監聽事件
事件 event,即游戲啟動后,用戶針對游戲所做的互動操作,如:按下鍵盤,點擊滑鼠
Python中提供了一個 pygame.event.get() 方法可以獲得用戶當前所做的動作的事件串列,用戶可以在同一時間做很多事件,該方法的回傳值即為游戲主視窗上發生的事件串列
例如在上面的例子中添加如下代碼可以監聽事件:
event_list = pygame.event.get()
if len(event_list) > 0:
print(event_list)

例:監聽用戶 × 退出 游戲界面
"""游戲回圈"""
while True:
"""設定螢屏重繪幀率"""
clock.tick(60)
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("退出游戲...")
pygame.quit()
"""直接退出游戲"""
exit()
提示:這段代碼非常的固定,幾乎所有的pygame游戲都大同小異
四、pygame高級類:精靈與精靈組
1.精靈與精靈組簡介
在上述的開發中,影像加載、位置變化、繪制影像都需要我們自己撰寫代碼來分別進行處理,而為了簡化這些開發步驟,pygame提供了兩個類:
- pygame.sprite.Sprite——用于創建 【 具有影像資料屬性:image屬性 和精靈位置屬性:rect屬性 的物件——精靈 】 的類
- pygame.sprite.Group——精靈組是包含了多個精靈物件的類
2.派生精靈子類
pygame.sprite.Sprite 和 pygame.sprite.Group 自帶的方法如下圖所示:

-
精靈規定兩個固有實體屬性:
self.image屬性 為影像,一般通過 pygame.image.load(圖片路徑) 來加載獲得
self.rect屬性 為在游戲主視窗上的位置,一般通過 self.image.get_rect() 獲得 -
pygame.image.load(圖片路徑) 方法中,圖片路徑可以是絕對路徑,也可以是相對路徑,用于將圖片加載到記憶體中,回傳一個影像物件,一般應將圖片放在當前工程檔案下
-
self.image.get_rect() 方法會回傳同 pygame.Rect(0, 0, 影像寬, 影像高) 類似的物件,注意:所在坐標系的位置為(0, 0),大小為 self.image 的大小,坐標位置可以通過其回傳值物件的屬性 x 來對橫坐標進行調整
-
精靈和精靈組的原生內置方法都是基于 image 和 rect 這兩個名字來呼叫的屬性的,因此,如果不按這兩個名字來賦予屬性,那么精靈和精靈組的內置方法將不可用,精靈就變成具空殼(重要)
-
精靈需要派生子類,這是因為原生精靈的 __init__() 方法沒有定義 self.image 和 self.rect——當然,這個肯定是留給開發者來寫的,精靈用什么影像,大小如何,自然是由開發者來決定
-
我們需要通過派生類來重寫 __init__() 方法,來傳遞 self.image 和 self.rect 需要的引數,不過在此之前我們需要用 super().init() 呼叫一下精靈的初始化方法,因為里面有定義其他內容,否則直接重寫方法會覆寫掉(重要)
-
image 是精靈影像的圖片, rect一般通過self.image.get_rect() 方法獲得,只要我們得到了 image,那么 rect 也自然得到
-
其次,精靈規定了一個固有方法:update(),但沒有內容,需要在派生類中重寫,這個方法用來寫精靈的運動,通過 self.rect.x 和 self.rect.y 來設計
-
在創建完精靈后,我們需要把同一派生類的精靈歸入一個精靈組,通過:
精靈組名 = pygame.sprite.Group(精靈1, 精靈2…精靈N) 來歸入同一個精靈組 -
當然,如果想要向已定義的精靈組中增加精靈,可以呼叫 精靈組名.add(精靈串列) 方法
-
當一個精靈陣亡了——即不需要再繪制在螢屏上了,則需要該精靈移出精靈組,呼叫精靈的 kill() 方法可以把精靈移出其所屬的精靈組,同時該精靈的記憶體會被釋放
-
對于同一個精靈組的精靈來說,精靈組名.update() 方法可以讓屬于該精靈組的所有精靈呼叫精靈的 update() 方法,即呼叫該方法后,該精靈組的所有精靈發生 update() 規定的運動,這也是為什么精靈里有一個固有方法 update() 的原因,是因為精靈組的 update() 方法是基于精靈的 update() 方法來寫的(重要)
-
對于同一個精靈組的精靈來說, draw(Surface) 方法可以讓屬于該精靈組的所有精靈繪制在游戲主視窗 Surface 上(重要)
-
draw(Surface) 中 Surface 傳遞的是游戲主視窗物件
-
圖中 screen 指的是游戲主視窗
-
呼叫 random 模塊可以實作隨機創建不同型別的敵機;隨機設定敵機不同的出場位置;隨機設定敵機不同的出場速度
創建游戲精靈類——派生于精靈類:
新建 plane_sprites.py 檔案,定義游戲精靈類 GameSprite 繼承自 pygame.sprite.Sprite

屬性:
- image 精靈的影像,使用 image_name 為圖片名通過 pygame.image.load(…) 來加載
- rect 精靈大小,默認使用影像大小
- speed 精靈移動速度,默認為1
方法:
- __init__ 初始化上述屬性值
- update 每次更新螢屏時在游戲回圈內呼叫,讓精靈的 self.rect.y += self.speed——向下移動
注:
- 如果一個類的父類不是 object
- 在重寫初始化方法 __init__ 時,一定要先 super().__init__(…) 呼叫一下原父類方法,防止原父類方法的內容被覆寫(比如:定義的 GameSprite 繼承自 pygame 中 sprite 模塊的 Sprite 類)
- 這樣才能保證父類中實作的 __init__ 代碼能夠被正常執行
plane_sprites.py 檔案的 代碼:
import pygame
class GameSprite(pygame.sprite.Sprite):
def __init__(self, image_name, speed=1):
"""呼叫父類的初始化方法"""
super().__init__()
"""定義屬性"""
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
def update(self):
"""精靈在游戲主視窗垂直方向下移動"""
self.rect.y += self.speed
3.使用精靈和精靈組創建敵機
敵機是游戲中會動的物件,可以作為精靈
需求:
- 使用上述派生的精靈類 Gamesprite 創建敵機精靈類,并實作敵機影片
- 即使用 plane_sprites.py 檔案中定義的類來進行創建
步驟:
- 使用 from 匯入 plane_sprites 模塊
from 匯入的模塊可以直接使用
import 匯入的模塊需要通過 模塊名. 來使用 - 在游戲初始化創建精靈物件和精靈組
- 在游戲回圈中讓精靈組分別呼叫 update() 和 draw(screen) 方法
職責:
(1) 精靈:
- 封裝影像 image、位置 rect 和速度 speed
- 提供 update() 方法,根據游戲需求,更新位置 rect
(2) 精靈組:
- 包含多個精靈物件
- update() 方法,讓精靈組中的所有精靈呼叫 update() 方法更新位置
- draw(screen) 方法,在 screen 上繪制精靈組中的所有精靈
import pygame
from plane_sprites import *
pygame.init()
"""創建游戲主視窗"""
window = pygame.display.set_mode((480, 700))
"""加載需要的圖片"""
background = pygame.image.load("./游戲素材/background.png")
"""創建敵機精靈和精靈組"""
enemy_plane1 = GameSprite("./游戲素材/enemy1.png", speed=3)
enemy_plane2 = GameSprite("./游戲素材/enemy2.png", speed=2)
enemy_plane3 = GameSprite("./游戲素材/enemy3_n1.png")
enemy_group = pygame.sprite.Group(enemy_plane1, enemy_plane2, enemy_plane3)
"""設定一個時鐘"""
clock = pygame.time.Clock()
while True:
"""繪制背景"""
window.blit(background, (0, 0))
"""繪制所有敵機精靈"""
enemy_group.draw(window)
enemy_group.update()
"""重繪顯示"""
pygame.display.update()
"""設定幀率"""
clock.tick(60)
pygame.quit()

注:image 中的 get_rect() 方法默認回傳 pygame.Rect(0, 0, 影像寬, 影像高) 的物件,其在坐標系中的位置為(0, 0),但其橫坐標可以通過其回傳值物件的屬性 x 來進行調節
五、游戲框架的搭建
目標:使用面向物件設計飛機大戰游戲類
1.檔案分配
根據需求,我們實際上只需創建2個 .py 檔案即可,plane_main.py 檔案作為飛機大戰游戲的主程式檔案, plane_sprites.py 檔案作為各個精靈類的定義檔案
plane_main.py :
- 封裝主游戲類
- 創建游戲物件
- 啟動游戲
plane_sprites.py:
- 封裝游戲中所有需要使用的精靈子類
- 提供游戲的相關工具
英雄飛機直接創建,敵機使用精靈與精靈組來創建,需求如下圖所示:


plane_sprites.py 檔案代碼:
import pygame
# 螢屏大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
class GameSprite(pygame.sprite.Sprite):
"""游戲主類——繼承自pygame.sprite.Sprite"""
def __init__(self, image_name, speed=1):
# 呼叫父類的初始化方法
super().__init__()
# 定義屬性
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
def update(self):
# 在螢屏的垂直方向x向下移動
self.rect.y += self.speed
注:為了代碼的可更改性(需求)與可閱讀性——后續修改代碼時只需修改常量即可,我們定義了矩形區域物件常量:
- SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
——該矩形區域物件用于代表游戲主視窗,因此我們后續將通過:
- pygame.display.set_mode(SCREEN_RECT.size) 來創建游戲主視窗
plane_main.py 檔案代碼:
from plane_sprites import *
class PlaneGame(object):
"""主游戲類"""
def __init__(self):
print("游戲正在初始化...")
# 創建游戲主視窗
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 創建游戲時鐘
self.clock = pygame.time.Clock()
# 呼叫私有方法,創建精靈和精靈組
self.__create_sprites()
def __create_sprites(self):
"""用于創建精靈和精靈組"""
pass
def start_game(self):
"""啟動游戲"""
# 游戲主回圈
while True:
# 設定重繪幀率為60
self.clock.tick(120)
# 事件監聽
self.__event_handler()
# 碰撞檢測
self.__check_collide()
# 位置更新
self.__update_sprites()
# 游戲主視窗重繪顯示
pygame.display.update()
def __check_collide(self):
"""碰撞檢測"""
pass
def __event_handler(self):
"""事件監聽"""
pass
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
@staticmethod
def __game_over():
# 結束游戲
print("游戲結束...")
pygame.quit()
exit()
if __name__ == '__main__':
# 初始化pygame
pygame.init()
# 創建游戲物件
game = PlaneGame()
# 啟動游戲
game.start_game()
2.游戲方式——背景的輪換滾動
雖然在 三、游戲主功能 5.英雄飛機的正確影片顯示 中,我們討論了游戲飛機向上移動的問題
但實際上,在許多跑酷類游戲的開發套路中,我們使用背景影像的運動來代替英雄的運動,那么英雄只需停留在螢屏的某一位置(可以左右移動),即可在視覺上產生英雄飛機正在向前移動的錯覺
思路構圖如圖所示:

- 創建兩個背景影像精靈,之所以使用精靈,是因為背景影像也像敵機精靈一樣移動
- 開始時,背景影像1和游戲主視窗完全重合,背景影像2在螢屏的正上方
- 兩背景影像將一起向下移動:self.rect.y += self.speed
- 當任意背景精靈的 rect.y >= 螢屏的深度 時說明當前背景精靈已經移動到螢屏下方,那么我們需要將 移動到螢屏下方的這張背景影像 設定到 游戲主視窗的正上方,即:rect.y = -rect.height
實作上述功能即可實作影像的連續滾動,在原 plane_sprites.py 檔案中,我們只定義了敵機精靈的向下移動,而螢屏精靈也可以使用這個向下移動方法,但需要增加螢屏影像的輪換滾動,就需要在原 GameSprite 類中派生一個子類,再重寫 update() 方法(這個方法擴展了對影像位置的判斷);其次,我們還需對方法 __init__(…) 進行擴展,即在創建背景影像精靈時,判斷創建的是不是背景影像2,如果是背景影像2,則應該設定其初始位置所在坐標應為游戲主視窗的正上方

plane_sprites.py 檔案新增代碼——BackGround 類:
class BackGround(GameSprite):
"""背景影像類,繼承自GameSprite"""
def __init__(self, is_alt=False):
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/background.png")
# 判斷是否為背景影像2,若是則改變初始坐標位置
if is_alt:
self.rect.y = -SCREEN_RECT.height
def update(self):
# 呼叫父類方法——向下移動
super().update()
if self.rect.y >= SCREEN_RECT.height:
self.rect.y = -SCREEN_RECT.height
3.飛機大戰核心框架
根據上述思想所得——類的繼承關系:

補全部分pass代碼和其他部分代碼后的代碼——主框架:
plane_sprites.py 檔案代碼:
import pygame
# 螢屏大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
class GameSprite(pygame.sprite.Sprite):
"""游戲主類——繼承自pygame.sprite.Sprite"""
def __init__(self, image_name, speed=1):
# 呼叫父類的初始化方法
super().__init__()
# 定義屬性
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
def update(self):
# 在螢屏的垂直方向x向下移動
self.rect.y += self.speed
class BackGround(GameSprite):
"""背景類,繼承自GameSprite"""
def __init__(self, is_alt=False):
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/background.png")
# 判斷是否為背景影像2,若是則改變初始坐標位置
if is_alt:
self.rect.y = -SCREEN_RECT.height
def update(self):
# 呼叫父類方法——向下移動
super().update()
if self.rect.y >= SCREEN_RECT.height:
self.rect.y = -SCREEN_RECT.height
plane_main.py 檔案代碼:
from plane_sprites import *
class PlaneGame(object):
"""主游戲類"""
def __init__(self):
print("游戲正在初始化...")
# 創建游戲主視窗
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 創建游戲時鐘
self.clock = pygame.time.Clock()
# 呼叫私有方法,創建精靈和精靈組
self.__create_sprites()
def __create_sprites(self):
# 創建背景精靈
background1 = BackGround()
background2 = BackGround(is_alt=True)
# 創建背景精靈組
self.background_group = pygame.sprite.Group(background1, background2)
def start_game(self):
"""啟動游戲"""
# 游戲主回圈
while True:
# 設定重繪幀率為60
self.clock.tick(120)
# 事件監聽
self.__event_handler()
# 碰撞檢測
self.__check_collide()
# 位置更新
self.__update_sprites()
# 游戲主視窗重繪顯示
pygame.display.update()
def __check_collide(self):
"""碰撞檢測"""
pass
def __event_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
@staticmethod
def __game_over():
# 結束游戲
print("游戲結束...")
pygame.quit()
exit()
if __name__ == '__main__':
# 初始化pygame
pygame.init()
# 創建游戲物件
game = PlaneGame()
# 啟動游戲
game.start_game()
3.敵機定時與隨機出場設計
約定:
- 游戲啟動后,每隔1秒會出現一架敵機——定時器
- 每駕敵機像螢屏下方飛行,飛行速度各不相同——隨機
- 每架敵機出現的水平位置也不盡相同——隨機
- 當敵機從游戲主視窗下方飛出時,不會再回到螢屏中——洗掉精靈物件
定時器:
在pygame中可以使用 pygame.time.set_time() 來添加定時器,所謂定時器,就是中斷——即每隔固定的一個時間段就發出一次信號
pygame.time.set_time(eventid, millisecond)
添加定時器,沒有回傳值
eventid 事件代號,需要基于常量 pygame.USEREVENT來指定,USEREVENT 是一個整數,再增加的事件可以使用 USEREVERT + 1 指定,以此類推
millisecond 單位:毫秒,即定時器的觸發時間間隔
提示:設定定時器后,定時器每隔一段時間就會發出一個事件——eventid,而這個事件是可以被pygame.event.get() 監聽到的,因此,我們只需在 for event in pygame.event.get() 中設定當監聽到 事件eventid 需要做的事情即可
pygame的定時器使用套路十分固定:
- 定義定時器常量——eventid
- 在初始化方法中,呼叫 set_timer 方法設定定時器事件
- 在游戲主回圈中,監聽定時器事件
在plane_sprites.py 檔案的頂部定義:
把 pygame.USEREVENT 記作 CREATE_ENEMY_EVENT事件
# 創建敵機的定時器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
在plane_main.py 檔案的 PlaneGame 類的 __init__() 中增加 定時創建敵機事件 的設定:
class PlaneGame(object):
"""主游戲類"""
def __init__(self):
print("游戲正在初始化...")
# 創建游戲主視窗
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 創建游戲時鐘
self.clock = pygame.time.Clock()
# 呼叫私有方法,創建精靈和精靈組
self.__create_sprites()
# 設定定時器事件——每隔1秒創建敵機
pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
在plane_main.py 檔案的 __event_handler() 方法中監聽該事件:
def __event_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
pass
設計 Enemy 類:
由于每駕敵機在游戲主視窗中向下方飛行,飛行速度各不相同,且每架敵機出現的水平位置也不盡相同;此外,當敵機飛出螢屏后,應該把對應的精靈從精靈組中洗掉,要實作這些特有的功能,就要在原 GameSprite 類中派生一個子類 enemy,擴展功能:

因此新的架構圖如圖所示:

為了實作隨機化,我們需要匯入 random 模塊
- 在 __init__(…) 方法中,我們首先抽取一個 1~3 的亂數,三個亂數分別代表創建三種不同型別的敵機——呼叫父類方法 super.__init__(…),分別傳入 ./游戲素材/enemy1.png
./游戲素材/enemy2.png、./游戲素材/enemy3_n1 來對應創建不同的敵機 - random.randrange([start], stop[, step]):從 [start, stop) 的區間上,間隔 step 大小來抽取亂數,如:random.randrange(10, 30, 2),相當于從 [10, 12, 14,…,26,28] 中抽取亂數
- self.rect.x = random.randrange(0, (SCREEN_RECT.width - self.rect.width), self.rect.width):其作用是在游戲主視窗中(位置不能越界),以敵機精靈的圖片大小為間隔地選擇位置
- self.speed = random.randint(1, 3) 則是隨機抽取出場速度
- self.rect.y = -self.rect.height 最后應記得設定敵機的出場位置為游戲主視窗的正上方
提示:self.rect 中有一個屬性:self.rect.bottom,實際上就是精靈影像的底部y軸的位置,即也可以設定 self.rect.bottom = 0,即為 self.rect.y = -self.rect.height 的意思
- 在 update() 方法中呼叫父類方法,并增加敵機精靈飛出游戲主視窗的判斷,當敵機精靈飛出游戲主視窗時,需要將其從精靈組中移出,并釋放記憶體,呼叫 self.kill() 方法即可,該方法會將精靈從其所屬精靈組中移除的同時釋放記憶體
在plane_sprites.py 檔案中新增代碼——Enemy 類:
import random
class Enemy(GameSprite):
"""敵機精靈類,繼承自GameSprite"""
def __init__(self):
# 隨機抽取敵機
number = random.randint(1, 3)
if number == 1:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy1.png")
# 隨機抽取出場位置
elif number == 2:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy2.png")
elif number == 3:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy3_n1.png")
# 隨機抽取出場位置
self.rect.x = random.randrange(0, (SCREEN_RECT.width - self.rect.width), self.rect.width)
# 隨機抽取出場速度
self.speed = random.randint(1, 3)
# 初始位置應該在游戲主視窗的上方
self.rect.y = -self.rect.height
def update(self):
# 呼叫父類方法——向下移動
super().update()
# 判斷是否飛出螢屏,是則移出精靈組釋放記憶體
if self.rect.y >= SCREEN_RECT.height:
self.kill()
- 首先在 __create_sprites(self) 中創建敵機精靈組,用于管理敵機精靈
在plane_main.py 檔案的 PlaneGame 類的 __create_sprites(self) 中創建敵機精靈組:
def __create_sprites(self):
# 創建背景精靈
background1 = BackGround()
background2 = BackGround(True)
# 創建背景精靈組
self.background_group = pygame.sprite.Group(background1, background2)
# 創建敵機精靈組
self.enemy_group = pygame.sprite.Group()
- 每當事件監聽方法監聽到 CREATE_ENEMY_EVENT 時,說明要創建敵機精靈了,我們呼叫 Enemy() 創建敵機精靈物件,由于敵機精靈組已經創建,只需通過 self.enemy_group.add() 讓新建的敵機精靈加入敵機精靈組即可
在plane_main.py 檔案的 __event_handler() 方法中監聽的定時器事件,將pass改為創建敵機,該事件就是用來間隔地創建敵機精靈的:
def __event_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
# 創建敵機精靈同時加入精靈組
self.enemy_group.add(Enemy())
- 別忘了在 __update_sprites() 方法中讓敵機精靈組呼叫 update() 更改位置 和 self.enemy_group.draw(self.screen) 重繪顯示
在 plane_main.py 檔案的 __update_sprites() 方法中的代碼如下:
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)

4.英雄飛機和子彈發射設計
英雄飛機類——Hero類 需求:
- 游戲開始后,游戲飛機應出現在游戲主視窗的正下方中間的位置,可以設定
self.rect.bottom = SCREEN_RECT.height - 英雄飛機每隔 0.5 秒發射一次子彈,每次三連發
- 英雄飛機需要通過鍵盤上的 ← 或 → 按鍵來左右移動,不用上下運動,可以通過 pygame.key.get_pressed() 來實作鍵盤的互動
子彈類——Bullet類 需求:
- 子彈從英雄飛機的正上方發射沿直線向上方飛行
- 同樣,飛出螢屏后,需要從精靈組中移除

英雄飛機——設計 Hero 類:
- 呼叫父類方法 super().__init__("./游戲素材/me1.png") ,傳入英雄飛機圖片
- 由于英雄飛機在豎直方向不動,因此要設定 self.speed = 0
- 設定其初始位置在游戲主視窗的正下方中央
- 定義 fire() 方法,用于發射子彈
- 重寫 update() 方法,由于英雄飛機不需要在豎直方向移動,因此不需要呼叫父類的 update() 方法,而需要重寫為水平方向的移動,但初始速度 self.speed 需要設定為0,否則英雄飛機會因為精靈組 update() 的呼叫而導致英雄飛機自己動了,而只有當鍵盤按下 左/右 移動的按鍵時,我們才更改 self.speed 的值,使得英雄飛機會因為精靈組 update() 的呼叫而產生移動,當按鍵沒有按下時,我們只需讓 self.speed 的值恢復為 0 即可
- 于是,由上述條件可知,因為我們后續要訪問英雄飛機的 speed 屬性,因此我們要把英雄飛機精靈創建成 PlaneGame類 的實體屬性,才能在 __create_sprites 方法的外部使用到英雄飛機物件(重要)
- 同時,要在 update() 方法中進行邊界限制,防止英雄飛機越出游戲主視窗的范圍
提示:self.rect 中有一個屬性:self.rect.centerx,實際上就是精靈影像中心的橫坐標的位置,可以設定 self.rect.centerx = SCREEN_RECT.centerx,即讓精靈影像屬于游戲主視窗的中心位置,同理,還有 self.rect.centery 影像中心的縱坐標屬性…
提示:self.rect 中有一個屬性:self.rect.right,實際上就是精靈影像右邊界的坐標;代碼中 elif self.rect.right > SCREEN_RECT.width: 就是判斷影像的右邊界是否越界,同理,還有 self.rect.left 左邊界屬性…
在plane_sprites.py 檔案的中新增代碼——Hero類,定義 fire() 英雄飛機發射子彈的方法——先用pass跳過:
class Hero(GameSprite):
"""英雄飛機類,繼承自GameSprite"""
def __init__(self):
# 設定速度為0
super().__init__("./游戲素材/me1.png", speed=0)
# 位于游戲主視窗的中央
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.height - 10
def update(self):
# 英雄飛機在水平方向移動且不能移出邊界
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right > SCREEN_RECT.width:
self.rect.right = SCREEN_RECT.width
else:
self.rect.x += self.speed
def fire(self):
"""英雄飛機發射子彈"""
pass
在plane_main.py 檔案的 __create_sprites 方法中增加英雄飛機物件的定義和英雄飛機精靈組的定義:
def __create_sprites(self):
# 創建背景精靈
background1 = BackGround()
background2 = BackGround(True)
# 創建英雄飛機精靈——作為屬性
self.hero_plane = Hero()
# 創建背景精靈組
self.background_group = pygame.sprite.Group(background1, background2)
# 創建游戲飛機精靈組
self.hero_group = pygame.sprite.Group(self.hero_plane)
# 創建敵機精靈組
self.enemy_group = pygame.sprite.Group()
在plane_main.py 檔案的 __update_sprites 方法中呼叫 update() 和 draw() 方法:
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
self.hero_group.update()
self.hero_group.draw(self.screen)
在pygame中針對鍵盤按鍵的捕獲:
首先使用 pygame.key.get_pressed() 回傳所有按鍵的元組,然后通過鍵盤常量——下標,判斷元組中某一個按鍵是否被按下——如果被按下,對應的數值為1,否則為0
鍵盤常量實際上是 pygame 內部定義的常量,實際上是一個整型數:
| pygame中的鍵盤常量 | 含義 |
|---|---|
| K_RIGHT | 鍵盤的 → 按鍵 |
| K_LEFT | 鍵盤的←按鍵 |
| K_UP | 鍵盤的 ↑ 按鍵 |
| K_DOWN | 鍵盤的 ↓ 按鍵 |
| K_a | 鍵盤的 a 按鍵 |
| … | … |
由于鍵盤常量實際上是在 pygame 內部定義一個整型數常量,因此可以直接使用 鍵盤常量 作為下標來在按鍵元組中找到對應的按鍵,當元組中某一按鍵對應的值為1時,則表示已按下
- 在事件監聽中通過 pygame.key.get_pressed() 獲得按鍵元組,keys_pressed[pygame.K_LEFT] 和 keys_pressed[pygame.K_RIGHT] 即為訪問元組中鍵盤按鍵 ←按鍵 和 →按鍵 的狀態
- 通過按鍵元組中的值的0、1來判斷按鍵的狀態,記住,當 K_RIGHT 和 K_LEFT 都沒有按下時,需要設定:self.hero_plane.speed = 0,防止因為精靈組 update() 的呼叫使得英雄飛機自己移動了
- 這里也體現了為什么要把英雄飛機精靈定義成實體屬性,因為要在 PlaneGame 中 __create_sprites 的外部通過 self.hero_plane.speed 才能呼叫英雄飛機的速度屬性
在plane_main.py 檔案的 __event_handler() 事件監聽方法中增加鍵盤按鍵判斷:
def __event_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
# 創建敵機精靈同時加入精靈組
self.enemy_group.add(Enemy())
# 按鍵判斷
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_RIGHT]:
self.hero_plane.speed = 1
if keys_pressed[pygame.K_LEFT]:
self.hero_plane.speed = -1
else:
# 當沒有按下左右方向鍵時,速度應該設定為0
self.hero_plane.speed = 0

發射子彈:英雄飛機每隔 0.5 秒發射一次子彈,每次三連發,由其特性可知,可以使用定時器來實作這個功能
還是那套固定的定時器使用套路:
- 定義定時器常量——eventid
- 在初始化方法中,呼叫 set_timer 方法設定定時器事件
- 在游戲主回圈中,監聽定時器事件
在plane_sprites.py 檔案的頂部定義:
把 pygame.USEREVENT + 1 記作 HERO_FIRE_EVENT 事件
# 英雄飛機發射子彈的事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1
在plane_main.py 檔案的 PlaneGame 類的 __init__() 中增加 英雄飛機定時發射子彈事件 的設定:
class PlaneGame(object):
"""主游戲類"""
def __init__(self):
print("游戲正在初始化...")
# 創建游戲主視窗
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 創建游戲時鐘
self.clock = pygame.time.Clock()
# 呼叫私有方法,創建精靈和精靈組
self.__create_sprites()
# 設定定時器事件——每隔1秒創建敵機
pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
# 設定定時器事件——每隔0.5秒發射一次子彈
在plane_main.py 檔案的 __event_handler() 方法中監聽該事件——執行 fire() 發射子彈方法:
def __evnet_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
# 創建敵機精靈同時加入精靈組
self.enemy_group.add(Enemy())
elif event.type == HERO_FIRE_EVENT:
self.hero_plane.fire()
# 按鍵判斷
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_RIGHT]:
self.hero_plane.speed = 2
elif keys_pressed[pygame.K_LEFT]:
self.hero_plane.speed = -2
else:
# 當沒有按下左右方向鍵時,速度應該設定為0
self.hero_plane.speed = 0
子彈——設計 Bullet 類:
- 子彈向上移動,因此 self.rect.y 應該逐漸變小:self.rect.y -= speed,結合背景精靈的移動,具有更快的相對移動速度
- 游戲每隔 0.5 秒發射一次子彈,每次三連發,使用定時器來實作這個功能,這個設定跟敵機的定時出場設計十分類似,變為了子彈定時出場
以下操作與敵機 Enemy 類的設計十分相識:
在plane_sprites.py 檔案中新增代碼——Bullet 類:
class Bullet(GameSprite):
"""子彈類,繼承自GameSprite"""
def __init__(self):
super().__init__("./游戲素材/bullet1.png", speed=-2)
def update(self):
# 呼叫父類方法——向下移動
super().update()
# 判斷子彈是否飛出螢屏,是則釋放
if self.rect.bottom <= 0:
self.kill()
在plane_sprites.py 檔案的 Hero 類的 __init__(self) 中創建子彈精靈組:
- 為什么要在Hero 類中創建子彈精靈組,而不在 __create_sprites(self) 中創建? 因為英雄飛機想要開火需要呼叫 fire() 方法,而事件 HERO_FIRE_EVENT 的事件監聽是游戲中呼叫 fire() 來實作開火,因此實作子彈發射的動作應該是在 fire() 方法里,而發射子彈即創建子彈精靈,并加入精靈組,因此精靈組應該在 Hero 類的 __init__(self) 中創建(重要)
class Hero(GameSprite):
"""英雄飛機類,繼承自GameSprite"""
def __init__(self):
# 設定速度為0
super().__init__("./游戲素材/me1.png", speed=0)
# 位于游戲主視窗的中央
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.height - 10
# 創建子彈精靈組
self.bullet_group = pygame.sprite.Group()
在 plane_main.py 檔案的 __update_sprites() 方法中的代碼如下:
- 注意:bullet_group 精靈組是在 hero_plane 內部的 fire() 創建的,需要通過 hero_plane 來呼叫
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
self.hero_group.update()
self.hero_group.draw(self.screen)
self.hero_plane.bullet_group.update()
self.hero_plane.bullet_group.draw(self.screen)
在plane_sprites.py 檔案的 Hero 類的 fire() 中,實作子彈的發射和三連發功能:
- 需要在此創建精靈子彈精靈,并通過 self.bullet_group.add(…) 加入精靈組
- i = 0、1、2 時,分別對應不同高度子彈精靈的創建
- bullet.rect.y = self.rect.y - 2 * i * bullet.rect.height 是根據 i 的值來調整子彈的高度
- 2 * i * bullet.rect.height 中 2 的目的是加寬子彈之間的間距,增強辨析度
def fire(self):
"""英雄飛機發射子彈"""
for i in (0, 1, 2):
# 創建子彈精靈
bullet = Bullet()
# 設定子彈精靈的位置,應該與英雄飛機的正上方中央發射
bullet.rect.y = self.rect.y - 2 * i * bullet.rect.height
bullet.rect.centerx = self.rect.centerx
# 子彈精靈加入精靈組
self.bullet_group.add(bullet)
5.碰撞檢測——“子彈與敵機”和“英雄飛機與敵機”之間
在pygame提供了兩個非常方便的方法可以實作碰撞檢測
pygame.sprite.groupcollide()
兩個精靈組的精靈之間的碰撞檢測
- groupcollide(group1, group2, dokill1, dokill2, collided = None) —> Sprite_dict 方法:
- group1 與 dokill1 對應;group2 與 dokill2 對應
- dokill1 和 dokill2 都是布爾型別,從詞面意思上很容易就能理解——它們就是決定 “ 當兩個精靈組的精靈之間發生碰撞時,是否執行 kill() 方法移出精靈組 ”
- collided 是用于計算碰撞的回呼函式,如果不指定該值,則所有精靈組必須要含有 rect 屬性
pygame.sprite.spritecollide()
某個精靈和指定精靈組之間的碰撞檢測
- spritecollide(sprite, group, dokill, collided = None) —> Sprite_list 方法:
- sprite 和 group 分別指的是 精靈 和 精靈組
- dokill 用于決定 “ 當兩者發生碰撞時,是否執行 kill() 方法移出精靈組 ”
- Sprite_list 是回傳值,用于回傳與精靈發生碰撞的精靈組內的精靈的串列
在 plane_main.py 檔案的 __check_collide() 方法中的代碼如下:
def __check_collide(self):
"""碰撞檢測"""
# 子彈與敵機之間的碰撞檢測
pygame.sprite.groupcollide(self.enemy_group, self.hero_plane.bullet_group, True, True)
# 英雄飛機與敵機之間的碰撞檢測
enemy_list = pygame.sprite.spritecollide(self.hero_plane, self.enemy_group, True)
# 如果發生了碰撞,游戲結束
if len(enemy_list) > 0:
self.hero_plane.kill()
self.__game_over()
至此,飛機大戰完成
5.源代碼
(1)plane_sprites.py 檔案:
import random
import pygame
# 螢屏大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 創建敵機的事件
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄飛機發射子彈的事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1
class GameSprite(pygame.sprite.Sprite):
"""游戲主類——繼承自pygame.sprite.Sprite"""
def __init__(self, image_name, speed=1):
# 呼叫父類的初始化方法
super().__init__()
# 定義屬性
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
def update(self):
# 在螢屏的垂直方向向下移動
self.rect.y += self.speed
class BackGround(GameSprite):
"""背景類,繼承自GameSprite"""
def __init__(self, is_alt=False):
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/background.png")
# 判斷是否為背景影像2,若是則改變初始坐標位置
if is_alt:
self.rect.bottom = 0
def update(self):
# 呼叫父類方法——向下移動
super().update()
if self.rect.y >= SCREEN_RECT.height:
self.rect.bottom = 0
class Enemy(GameSprite):
"""敵機精靈類,繼承自GameSprite"""
def __init__(self):
# 隨機抽取敵機
number = random.randint(1, 3)
if number == 1:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy1.png")
# 隨機抽取出場位置
elif number == 2:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy2.png")
elif number == 3:
# 呼叫父類方法創建精靈物件
super().__init__("./游戲素材/enemy3_n1.png")
# 隨機抽取出場位置
self.rect.x = random.randrange(0, (SCREEN_RECT.width - self.rect.width), 1)
# 隨機抽取出場速度
self.speed = random.randint(1, 3)
# 初始位置應該在游戲主視窗的上方
self.rect.bottom = 0
def update(self):
# 呼叫父類方法——向下移動
super().update()
# 判斷是否飛出螢屏,是則釋放
if self.rect.y >= SCREEN_RECT.height:
self.kill()
class Hero(GameSprite):
"""英雄飛機類,繼承自GameSprite"""
def __init__(self):
# 設定速度為0
super().__init__("./游戲素材/me1.png", speed=0)
# 位于游戲主視窗的中央
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.height - 10
# 創建子彈精靈組
self.bullet_group = pygame.sprite.Group()
def update(self):
# 英雄飛機在水平方向移動且不能移出邊界
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right > SCREEN_RECT.width:
self.rect.right = SCREEN_RECT.width
else:
self.rect.x += self.speed
def fire(self):
"""英雄飛機發射子彈"""
for i in (0, 1, 2):
# 創建子彈精靈
bullet = Bullet()
# 設定子彈精靈的位置,應該與英雄飛機的正上方中央發射
bullet.rect.y = self.rect.y - 2 * i * bullet.rect.height
bullet.rect.centerx = self.rect.centerx
# 子彈精靈加入精靈組
self.bullet_group.add(bullet)
class Bullet(GameSprite):
"""子彈類,繼承自GameSprite"""
def __init__(self):
super().__init__("./游戲素材/bullet1.png", speed=-3)
def update(self):
# 呼叫父類方法——向下移動
super().update()
# 判斷子彈是否飛出螢屏,是則釋放
if self.rect.bottom <= 0:
self.kill()
(2)plane_main.py 檔案:
from plane_sprites import *
class PlaneGame(object):
"""主游戲類"""
def __init__(self):
print("游戲正在初始化...")
# 創建游戲主視窗
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 創建游戲時鐘
self.clock = pygame.time.Clock()
# 呼叫私有方法,創建精靈和精靈組
self.__create_sprites()
# 設定定時器事件——每隔1秒創建敵機
pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
# 設定定時器事件——每隔0.5秒發射一次子彈
pygame.time.set_timer(HERO_FIRE_EVENT, 500)
def __create_sprites(self):
# 創建背景精靈
background1 = BackGround()
background2 = BackGround(True)
# 創建英雄飛機精靈——作為屬性
self.hero_plane = Hero()
# 創建背景精靈組
self.background_group = pygame.sprite.Group(background1, background2)
# 創建游戲飛機精靈組
self.hero_group = pygame.sprite.Group(self.hero_plane)
# 創建敵機精靈組
self.enemy_group = pygame.sprite.Group()
def start_game(self):
"""啟動游戲"""
# 游戲主回圈
while True:
# 設定重繪幀率為60
self.clock.tick(120)
# 事件監聽
self.__event_handler()
# 碰撞檢測
self.__check_collide()
# 位置更新
self.__update_sprites()
# 游戲主視窗重繪顯示
pygame.display.update()
def __check_collide(self):
"""碰撞檢測"""
# 子彈與敵機之間的碰撞檢測
temp_dict = pygame.sprite.groupcollide(self.enemy_group, self.hero_plane.bullet_group, False, True)
# 英雄飛機與敵機之間的碰撞檢測
enemy_list = pygame.sprite.spritecollide(self.hero_plane, self.enemy_group, True)
# 如果發生了碰撞,游戲結束
if len(enemy_list) > 0:
self.hero_plane.kill()
self.__game_over()
def __event_handler(self):
"""事件監聽"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
# 創建敵機精靈同時加入精靈組
self.enemy_group.add(Enemy())
elif event.type == HERO_FIRE_EVENT:
self.hero_plane.fire()
# 按鍵判斷
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_RIGHT]:
self.hero_plane.speed = 2
elif keys_pressed[pygame.K_LEFT]:
self.hero_plane.speed = -2
else:
# 當沒有按下左右方向鍵時,速度應該設定為0
self.hero_plane.speed = 0
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
self.hero_group.update()
self.hero_group.draw(self.screen)
self.hero_plane.bullet_group.update()
self.hero_plane.bullet_group.draw(self.screen)
@staticmethod
def __game_over():
# 結束游戲
print("游戲結束...")
pygame.quit()
exit()
if __name__ == '__main__':
# 初始化pygame
pygame.init()
# 創建游戲物件
game = PlaneGame()
# 啟動游戲
game.start_game()
上一篇文章
- 七、Python基礎(例外、模塊、檔案操作)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/261069.html
標籤:python
下一篇:詳細爬蟲:爬取華師教務系統
