早就知道pygame模組,就是沒怎麼深入研究過,恰逢這周未沒約到妹子,只能自己在家玩自己啦,一時興起,花了幾個小時寫了個打飛機程式。
很有意思,跟大家分享下。
先看一下專案結構
""" PlayPlane/ |-- bin/ | |-- main.py 程式執行主體程式 |-- config/ | |-- settings.py 程式配置(例如: 遊戲背景音樂的載入等) |-- material 程式素材放置(打飛機遊戲素材放置) |-- ... |-- src/ 程式主體模組存放 | |-- __init__.py | |-- bullet.py 我方飛機發射子彈實現程式碼存放 | |-- enemy.py 敵方飛機實現程式碼存放 | |-- plane.py 我方飛機實現程式碼存放 |-- manage.py 程式啟動檔案 |-- README.md """
再曬下專案成果圖
實現步驟
一、首先在 config/settings.py 中進行以下功能的實現
- 遊戲初始化
- 遊戲混音器初始化
- 背景音樂初始化
- 我方飛機掛了音樂
- 敵方飛機掛了音樂
- 子彈發射音樂
#! /usr/bin/env python # -*- coding: utf-8 -*- import pygame pygame.init() # 遊戲初始化 pygame.mixer.init() # 混音器初始化 # 遊戲背景音樂 pygame.mixer.music.load("material/sound/game_music.wav") pygame.mixer.music.set_volume(0.2) # 子彈發射音樂 bullet_sound = pygame.mixer.Sound("material/sound/bullet.wav") bullet_sound.set_volume(0.2) # 我方飛機掛了的音樂 me_down_sound = pygame.mixer.Sound("material/sound/game_over.wav") me_down_sound.set_volume(0.2) # 敵方飛機掛了的音樂 enemy1_down_sound = pygame.mixer.Sound("material/sound/enemy1_down.wav") enemy1_down_sound.set_volume(0.2) config/settings.py
注:遊戲素材滑動到文章底部點選連結即可下載
二、小試牛刀
飛機和子彈都是可移動的,那麼怎麼實現讓它們動起來呢(我方飛機可以玩家進行操控,敵機就是隨機性的出現,子彈暫由我方飛機發射)。
在Pygame中,所有移動物件都可看做是一個精靈(sprite),精靈之間能夠進行相互的互動通訊,例如如何讓碰撞檢測更加精準等等。
那麼先讓我們先在螢幕上製作一個遊戲板,根據 settings.py 配置,並讓它有聲音播放,首先我們在 bin/main.py 中這麼寫:
我們可以直接執行它,那麼我們會看到以下畫面,並且還會有激情的聲音吆!!!但是我們要將檔案配置為絕對路徑才可以執行,因為剛剛在settings中的載入的音樂檔案為相對路徑。
#! /usr/bin/env python # -*- coding: utf-8 -*- import sys from config.settings import * bg_size = 480, 852 # 初始化遊戲背景大小(寬, 高) screen = pygame.display.set_mode(bg_size) # 設定背景對話方塊 pygame.display.set_caption("飛機大戰") # 設定標題 background = pygame.image.load(os.path.join(BASE_DIR, "material/image/background.png")) # 載入背景圖片,並設定為不透明 def main(): pygame.mixer.music.play(loops=-1) # loops 對應的值為 -1 則音樂會無限迴圈播放 while True: # 繪製背景圖 screen.blit(background, (0, 0)) # 響應使用者的操作(一定要有響應的使用者操作) for event in pygame.event.get(): if event.type == 12: # 如果使用者按下螢幕上的關閉按鈕,觸發QUIT事件,程式退出 pygame.quit() sys.exit() # 再而我們將背景影象並輸出到螢幕上面 pygame.display.flip() if __name__ == '__main__': main()
接下來呢,我們將要製作我方飛機,敵方飛機和子彈如何讓它們展示在遊戲畫板上,繼而讓它們變得可移動起來,請看程式碼實現方案...
從遊戲畫板上新增飛機,首先我們應怎樣在螢幕上輸出飛機???
上述講過,pygame中的 sprite(精靈)可使一張圖片或者一個靜態物體動起來,那麼製作飛機需要考慮並做些什麼呢?
- 飛機的初始位置
- 通過按鍵 上下左右 來調控飛機的位置移動
- 飛機只能呆在製作的遊戲畫板中
- 飛機的速度
- 飛機死亡的載入
- 設定一個狀態標識飛機的存活
- 讓飛機具有動態的噴氣式效果
那麼如何實現以上的功能呢?接下來結合上述的示例程式碼我們先將我方飛機繪製到畫板上方,並且我們通過按鍵 J 判定我方飛機的存活狀態為死亡,繪製飛機的死亡畫面並重置飛機
""" 建立飛機 在pygame中, 所有可移動的物件均叫可看作一個精靈(sprite) 該類並實現了碰撞方法 spritecollide 我方飛機和敵方飛機指定掩膜屬性以及生存狀態標誌位 新增 self.mask 屬性(可以實現更精準的碰撞效果) """ # 倒入精靈模組, 使飛機可以動起來 import pygame class OurPlane(pygame.sprite.Sprite): def __init__(self, bg_size): super(OurPlane, self).__init__() # 確定我方飛機背景圖(有倆張,可以讓它們不停的切換,形成動態效果) self.image_one = pygame.image.load("material/image/hero1.png") self.image_two = pygame.image.load("material/image/hero2.png") # 獲取我方飛機的位置 self.rect = self.image_one.get_rect() # 本地化背景圖片的尺寸 self.width, self.height = bg_size[0], bg_size[1] # 獲取飛機影象的掩膜用以更加精確的碰撞檢測 self.mask = pygame.mask.from_surface(self.image_one) # 定義飛機初始化位置,底部預留60畫素 self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60) # 設定飛機移動速度 self.speed = 10 # 設定飛機存活狀態(True為存活, False為死亡) self.active = True # 載入飛機損毀圖片 self.destroy_images = [] self.destroy_images.extend( [ pygame.image.load("material/image/hero_blowup_n1.png"), pygame.image.load("material/image/hero_blowup_n2.png"), pygame.image.load("material/image/hero_blowup_n3.png"), pygame.image.load("material/image/hero_blowup_n4.png") ] ) def move_up(self): """ 飛機向上移動的操作函式,其餘移動函式方法類似 """ if self.rect.top > 0: # 如果飛機尚未移動出背景區域 self.rect.top -= self.speed else: # 若即將移動出背景區域,則及時糾正為背景邊緣位置 self.rect.top = 0 def move_down(self): """ 飛機向下移動 """ if self.rect.bottom < self.height - 60: self.rect.top += self.speed else: self.rect.bottom = self.height - 60 def move_left(self): """ 飛機向左移動 """ if self.rect.left > 0: self.rect.left -= self.speed else: self.rect.left = 0 def move_right(self): """ 飛機向右移動 """ if self.rect.right < self.width: self.rect.right += self.speed else: self.rect.right = self.width def reset(self): # 初始化飛機(飛機掛了, 初始化到初始位置) self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60) # 重置飛機的存活狀態 self.active = True
上面的程式碼寫了一個 我們的飛機 (OurPlane) 類,它初始化了一些屬性以及 上下左右 移動的方法和重置方法,接下來將要運用它展示到遊戲畫板上面
由於飛機是一直存在的,接下我們主程式 main 下面的死迴圈中這樣寫
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
def main(): pygame.mixer.music.play(loops=-1) # loops 對應的值為 -1 則音樂會無限迴圈播放 our_plane = OurPlane(bg_size) # 初始化 switch_image = False # 定義飛機的切圖效果標識 while True: # 繪製背景圖 screen.blit(background, (0, 0)) # 飛機狀態是存活 if our_plane.active: if switch_image: screen.blit(our_plane.image_one, our_plane.rect) else: screen.blit(our_plane.image_two, our_plane.rect) switch_image = not switch_image # 讓切圖狀態不停的變換 else: pass # 響應使用者的操作(一定要有響應的使用者操作) for event in pygame.event.get(): if event.type == 12: # 如果使用者按下螢幕上的關閉按鈕,觸發QUIT事件,程式退出 pygame.quit() sys.exit() # 再而我們將背景影象並輸出到螢幕上面 pygame.display.flip() if __name__ == '__main__': main()
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
def main(): pygame.mixer.music.play(loops=-1) # loops 對應的值為 -1 則音樂會無限迴圈播放 our_plane = OurPlane(bg_size) # 初始化 switch_image = False # 定義飛機的切圖效果標識 while True: # 繪製背景圖 screen.blit(background, (0, 0)) # 飛機狀態是存活 if our_plane.active: if switch_image: screen.blit(our_plane.image_one, our_plane.rect) else: screen.blit(our_plane.image_two, our_plane.rect) switch_image = not switch_image # 讓切圖狀態不停的變換 else: pass # 獲得使用者所有的鍵盤輸入序列(如果使用者通過鍵盤發出“向上”的指令,其他類似) key_pressed = pygame.key.get_pressed() if key_pressed[K_w] or key_pressed[K_UP]: our_plane.move_up() if key_pressed[K_s] or key_pressed[K_DOWN]: our_plane.move_down() if key_pressed[K_a] or key_pressed[K_LEFT]: our_plane.move_left() if key_pressed[K_d] or key_pressed[K_RIGHT]: our_plane.move_right() # 響應使用者的操作(一定要有響應的使用者操作) for event in pygame.event.get(): if event.type == 12: # 如果使用者按下螢幕上的關閉按鈕,觸發QUIT事件,程式退出 pygame.quit() sys.exit() # 再而我們將背景影象並輸出到螢幕上面 pygame.display.flip() if __name__ == '__main__': main()
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
def main(): pygame.mixer.music.play(loops=-1) # loops 對應的值為 -1 則音樂會無限迴圈播放 our_plane = OurPlane(bg_size) # 初始化 switch_image = False # 定義飛機的切圖效果標識 our_plane_destroy_index = 0 while True: # 繪製背景圖 screen.blit(background, (0, 0)) # 飛機狀態是存活(如果按鍵 為 J, 讓飛機死亡並繪製爆炸效果) if our_plane.active: if switch_image: screen.blit(our_plane.image_one, our_plane.rect) else: screen.blit(our_plane.image_two, our_plane.rect) switch_image = not switch_image # 讓切圖狀態不停的變換 else: """ 飛機死亡也是進行按順序的圖片切換, 那麼在死迴圈之外定義索引 """ me_destroy_index = (our_plane_destroy_index + 1) % 4 if me_destroy_index == 1: me_down_sound.play() # 爆炸聲音效果 our_plane.reset() # 初始化飛機 if our_plane_destroy_index >= len(our_plane.destroy_images): our_plane_destroy_index = 0 else: screen.blit(our_plane.destroy_images[our_plane_destroy_index], our_plane.rect) our_plane_destroy_index += 1 # 獲得使用者所有的鍵盤輸入序列(如果使用者通過鍵盤發出“向上”的指令,其他類似) key_pressed = pygame.key.get_pressed() if key_pressed[K_w] or key_pressed[K_UP]: our_plane.move_up() if key_pressed[K_s] or key_pressed[K_DOWN]: our_plane.move_down() if key_pressed[K_a] or key_pressed[K_LEFT]: our_plane.move_left() if key_pressed[K_d] or key_pressed[K_RIGHT]: our_plane.move_right() # 按鍵為 j 飛機更改存活標識 if key_pressed[K_j]: our_plane.active = False # 響應使用者的操作(一定要有響應的使用者操作) for event in pygame.event.get(): if event.type == 12: # 如果使用者按下螢幕上的關閉按鈕,觸發QUIT事件,程式退出 pygame.quit() sys.exit() # 再而我們將背景影象並輸出到螢幕上面 pygame.display.flip()
那麼上述的功能都已經實現了,接下來就開始真正的"打飛機"
三、接下來可以製作我方飛機,敵方戰機,子彈等,這些功能均在 src/ 目錄下實現
- 我方飛機根據按鍵上下左右進行移動,初始化位置,噴氣式圖片載入切換及重置效果等
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
#! /usr/bin/env python # -*- coding: utf-8 -*- """ 建立飛機 在pygame中, 所有可移動的物件均叫可看作一個精靈(sprite) 該類並實現了碰撞方法 spritecollide 我方飛機和敵方飛機指定掩膜屬性以及生存狀態標誌位 新增 self.mask 屬性(可以實現更精準的碰撞效果) """ # 倒入精靈模組, 使飛機可以動起來 import pygame class OurPlane(pygame.sprite.Sprite): def __init__(self, bg_size): super(OurPlane, self).__init__() # 確定我方飛機背景圖 self.image_one = pygame.image.load("material/image/hero1.png") self.image_two = pygame.image.load("material/image/hero2.png") # 獲取我方飛機的位置 self.rect = self.image_one.get_rect() # 本地化背景圖片的尺寸 self.width, self.height = bg_size[0], bg_size[1] # 獲取飛機影象的掩膜用以更加精確的碰撞檢測 self.mask = pygame.mask.from_surface(self.image_one) # 定義飛機初始化位置,底部預留60畫素 self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60) # 設定飛機移動速度 self.speed = 10 # 設定飛機存活狀態(True為存活, False為死亡) self.active = True # 載入飛機損毀圖片 self.destroy_images = [] self.destroy_images.extend( [ pygame.image.load("material/image/hero_blowup_n1.png"), pygame.image.load("material/image/hero_blowup_n2.png"), pygame.image.load("material/image/hero_blowup_n3.png"), pygame.image.load("material/image/hero_blowup_n4.png") ] ) def move_up(self): """ 飛機向上移動的操作函式,其餘移動函式方法類似 """ if self.rect.top > 0: # 如果飛機尚未移動出背景區域 self.rect.top -= self.speed else: # 若即將移動出背景區域,則及時糾正為背景邊緣位置 self.rect.top = 0 def move_down(self): if self.rect.bottom < self.height - 60: self.rect.top += self.speed else: self.rect.bottom = self.height - 60 def move_left(self): if self.rect.left > 0: self.rect.left -= self.speed else: self.rect.left = 0 def move_right(self): if self.rect.right < self.width: self.rect.right += self.speed else: self.rect.right = self.width def reset(self): # 初始化飛機(飛機掛了, 初始化到初始位置) self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60) self.active = True src/plane.py
- 敵方飛機隨機移動出現及重置(製作出我方飛機之後,敵機和子彈其實都是大同小異的)
-
#! /usr/bin/env python # -*- coding: utf-8 -*- """ 定義敵機 """ from random import randint import pygame class SmallEnemy(pygame.sprite.Sprite): """ 定義小飛機敵人 """ def __init__(self, bg_size): super(SmallEnemy, self).__init__() self.image = pygame.image.load("material/image/enemy1.png") self.rect = self.image.get_rect() self.width, self.height = bg_size[0], bg_size[1] self.mask = pygame.mask.from_surface(self.image) # 獲取飛機影象的掩膜用以更加精確的碰撞檢測 self.speed = 2 # 定義敵機出現的位置, 保證敵機不會在程式已開始就立即出現 self.rect.left, self.rect.top = ( randint(0, self.width - self.rect.width), randint(-5 * self.rect.height, -5), ) self.active = True # 載入飛機損毀圖片 self.destroy_images = [] self.destroy_images.extend( [ pygame.image.load("material/image/enemy1_down1.png"), pygame.image.load("material/image/enemy1_down2.png"), pygame.image.load("material/image/enemy1_down3.png"), pygame.image.load("material/image/enemy1_down4.png") ] ) def move(self): """ 定義敵機的移動函式 :return: """ if self.rect.top < self.height: self.rect.top += self.speed else: self.reset() def reset(self): """ 當敵機向下移動出螢幕時, 以及敵機死亡 :return: """ self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), randint(-5 * self.rect.height, 0)) self.active = True
- 子彈按照我方飛機正中上方發射及頻率調控,重置
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
#! /usr/bin/env python # -*- coding: utf-8 -*- """ 子彈的實現 """ import pygame class Bullet(pygame.sprite.Sprite): def __init__(self, position): super(Bullet, self).__init__() self.image = pygame.image.load("material/image/bullet1.png") self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.speed = 30 self.active = True self.mask = pygame.mask.from_surface(self.image) def move(self): """ 子彈移動, 超出螢幕範圍, 則設定死亡 :return: """ if self.rect.top < 0: self.active = False else: self.rect.top -= self.speed def reset(self, position): """ 復位函式 :param position: :return: """ self.rect.left, self.rect.top = position self.active = True src/bullet.py
在上面的內容中,使用物件導向的形式製作了遊戲中可移動的物件並繼承 pygame.sprite.Sprite
四、然後在 bin/main.py 中進行主體功能的實現
- 初始化背景圖及大小
- 我方飛機移動及發射子彈
- 敵方飛機移動
- 我方飛機和敵方飛機碰撞檢測
- 鍵盤按鍵監測效果
- 我方飛機和敵方飛機掛了效果繪製
![](https://i.iter01.com/images/86153736c3d440f2e7d2df2432d97112d2ef05f0cb7522cf2307c3de9447249a.gif)
import sys from pygame.locals import * from config.settings import * from src.plane import OurPlane from src.enemy import SmallEnemy from src.bullet import Bullet bg_size = 480, 852 # 初始化遊戲背景大小(寬, 高) screen = pygame.display.set_mode(bg_size) # 設定背景對話方塊 pygame.display.set_caption("飛機大戰") # 設定標題 background = pygame.image.load("material/image/background.png") # 載入背景圖片,並設定為不透明 # 獲取我方飛機 our_plane = OurPlane(bg_size) def add_small_enemies(group1, group2, num): """ 新增小型敵機 指定個敵機物件新增到精靈組(sprite.group) 引數group1、group2是兩個精靈組型別的形參,用以儲存多個精靈物件(敵機)。 需要注意的一點是group既然是特定的精靈組結構體,在向其內部新增精靈物件時需要呼叫其對應的成員函式add() :return: """ for i in range(num): small_enemy = SmallEnemy(bg_size) group1.add(small_enemy) group2.add(small_enemy) def main(): # 響應音樂 pygame.mixer.music.play(-1) # loops 接收該引數, -1 表示無限迴圈(預設迴圈播放一次) running = True switch_image = False # 切換飛機的標識位(使飛機具有噴氣式效果) delay = 60 # 對一些效果進行延遲, 效果更好一些 enemies = pygame.sprite.Group() # 生成敵方飛機組(一種精靈組用以儲存所有敵機精靈) small_enemies = pygame.sprite.Group() # 敵方小型飛機組(不同型號敵機建立不同的精靈組來儲存) add_small_enemies(small_enemies, enemies, 4) # 生成若干敵方小型飛機 # 定義子彈, 各種敵機和我方敵機的毀壞影象索引 bullet_index = 0 e1_destroy_index = 0 me_destroy_index = 0 # 定義子彈例項化個數 bullet1 = [] bullet_num = 6 for i in range(bullet_num): bullet1.append(Bullet(our_plane.rect.midtop)) while running: # 繪製背景圖 screen.blit(background, (0, 0)) # 微信的飛機貌似是噴氣式的, 那麼這個就涉及到一個幀數的問題 clock = pygame.time.Clock() clock.tick(60) # 繪製我方飛機的兩種不同的形式 if not delay % 3: switch_image = not switch_image for each in small_enemies: if each.active: # 隨機迴圈輸出小飛機敵機 for e in small_enemies: e.move() screen.blit(e.image, e.rect) else: if e1_destroy_index == 0: enemy1_down_sound.play() screen.blit(each.destroy_images[e1_destroy_index], each.rect) e1_destroy_index = (e1_destroy_index + 1) % 4 if e1_destroy_index == 0: each.reset() # 當我方飛機存活狀態, 正常展示 if our_plane.active: if switch_image: screen.blit(our_plane.image_one, our_plane.rect) else: screen.blit(our_plane.image_two, our_plane.rect) # 飛機存活的狀態下才可以發射子彈 if not (delay % 10): # 每十幀發射一顆移動的子彈 bullet_sound.play() bullets = bullet1 bullets[bullet_index].reset(our_plane.rect.midtop) bullet_index = (bullet_index + 1) % bullet_num for b in bullets: if b.active: # 只有啟用的子彈才可能擊中敵機 b.move() screen.blit(b.image, b.rect) enemies_hit = pygame.sprite.spritecollide(b, enemies, False, pygame.sprite.collide_mask) if enemies_hit: # 如果子彈擊中飛機 b.active = False # 子彈損毀 for e in enemies_hit: e.active = False # 小型敵機損毀 # 毀壞狀態繪製爆炸的場面 else: if not (delay % 3): screen.blit(our_plane.destroy_images[me_destroy_index], our_plane.rect) me_destroy_index = (me_destroy_index + 1) % 4 if me_destroy_index == 0: me_down_sound.play() our_plane.reset() # 呼叫 pygame 實現的碰撞方法 spritecollide (我方飛機如果和敵機碰撞, 更改飛機的存活屬性) enemies_down = pygame.sprite.spritecollide(our_plane, enemies, False, pygame.sprite.collide_mask) if enemies_down: our_plane.active = False for row in enemies: row.active = False # 響應使用者的操作 for event in pygame.event.get(): if event.type == 12: # 如果使用者按下螢幕上的關閉按鈕,觸發QUIT事件,程式退出 pygame.quit() sys.exit() if delay == 0: delay = 60 delay -= 1 # 獲得使用者所有的鍵盤輸入序列(如果使用者通過鍵盤發出“向上”的指令,其他類似) key_pressed = pygame.key.get_pressed() if key_pressed[K_w] or key_pressed[K_UP]: our_plane.move_up() if key_pressed[K_s] or key_pressed[K_DOWN]: our_plane.move_down() if key_pressed[K_a] or key_pressed[K_LEFT]: our_plane.move_left() if key_pressed[K_d] or key_pressed[K_RIGHT]: our_plane.move_right() # 繪製影象並輸出到螢幕上面 pygame.display.flip()
五、暢汗淋漓,一氣呵成打飛機
from bin.main import main if __name__ == '__main__': """ 環境: python3 + pygame running 起來就可以打飛機了O(∩_∩)O~. """ main()
最終效果!
完整專案程式碼 : https://github.com/triaquae/jerkoff
另外,本專案所用到的基礎知識視訊 已上傳至 路飛學城 ,需要者自取! http://luffy.oldboyedu.com/course/detail/python/5