主頁 > 後端開發 > 八、Python基礎(綜合演練:飛機大戰)

八、Python基礎(綜合演練:飛機大戰)

2021-02-19 16:58:13 後端開發

八、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.pngme2.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()

在這里插入圖片描述
我們使用 PyCharmPS 打開圖片,發現英雄飛機圖片的背景是一系列灰白相間的小格子,這些灰白相間的小格子代表英雄飛機是透明背景
在這里插入圖片描述

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.pngme2.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.Spritepygame.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 來對橫坐標進行調整

  • 精靈和精靈組的原生內置方法都是基于 imagerect 這兩個名字來呼叫的屬性的,因此,如果不按這兩個名字來賦予屬性,那么精靈和精靈組的內置方法將不可用,精靈就變成具空殼(重要

  • 精靈需要派生子類,這是因為原生精靈的 __init__() 方法沒有定義 self.imageself.rect——當然,這個肯定是留給開發者來寫的,精靈用什么影像,大小如何,自然是由開發者來決定

  • 我們需要通過派生類來重寫 __init__() 方法,來傳遞 self.imageself.rect 需要的引數,不過在此之前我們需要用 super().init() 呼叫一下精靈的初始化方法,因為里面有定義其他內容,否則直接重寫方法會覆寫掉(重要

  • image 是精靈影像的圖片, rect一般通過self.image.get_rect() 方法獲得,只要我們得到了 image,那么 rect 也自然得到

  • 其次,精靈規定了一個固有方法update(),但沒有內容,需要在派生類中重寫,這個方法用來寫精靈的運動,通過 self.rect.xself.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 繼承自 pygamesprite 模塊的 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_RIGHTK_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.height2 的目的是加寬子彈之間的間距,增強辨析度
    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 方法:
  • group1dokill1 對應;group2dokill2 對應
  • dokill1dokill2 都是布爾型別,從詞面意思上很容易就能理解——它們就是決定 當兩個精靈組的精靈之間發生碰撞時,是否執行 kill() 方法移出精靈組
  • collided 是用于計算碰撞的回呼函式,如果不指定該值,則所有精靈組必須要含有 rect 屬性

pygame.sprite.spritecollide()

某個精靈和指定精靈組之間的碰撞檢測

  • spritecollide(sprite, group, dokill, collided = None) —> Sprite_list 方法:
  • spritegroup 分別指的是 精靈精靈組
  • 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

上一篇:Python turtle 庫 自學2

下一篇:詳細爬蟲:爬取華師教務系統

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more