從零基於python構建飛機大戰
文章目錄
涉及知識點:
python基礎語法:分支,循環,基本數據類型,高級結構(列表,元組,字典),python面向對象知識,面向對象中封裝、繼承、多態的應用,普通2d遊戲的基本原理,pygame模塊的使用 設計層面:
面向對象的設計思路,面向對象分析,對於程序複用性的考慮,
有待完善點:多線程的引入
參考資料:think in python、 pygame官網文檔
#環境的構建 pygame簡介:Pygame是跨平臺Python模塊,專爲電子遊戲設計,包含圖像、聲音。建立在SDL基礎上,允許實時電子遊戲研發的模塊。 pygame的安裝: 使用命令 pip install pygame
最終效果
所需資料/源碼:鏈接:https://pan.baidu.com/s/1UdevAqz48AqE4uMW5_Copg
提取碼:ui7p
項目概述
本項目較好的鞏固了python基礎語法內容,在實現過程中充分的應用到了面向對象的編程思想,對於初學者而言是一個較爲不錯的訓練項目.本文注重描述實現過程中面向對象的思想的應用,對於pygame的部分細節描述較少,所以讀者可以根據自己所需進行鍼對性的閱讀。
前置知識
#本節主要敘述pygame中用於本次項目的一些內容,欲獲詳細信息請查閱官方文檔
遊戲初始化
#要使用 `pygame` 提供的所有功能之前,需要調用 `init` 方法 (編寫遊戲前必須進行初始化!)
`pygame.init()`導入並初始化所有 `pygame` 模塊,使用其他模塊之前,必須先調用 `init` 方法
創建遊戲主窗口
pygame
專門提供了一個 模塊pygame.display
用於創建、管理 遊戲窗口
方法 | 說明 |
---|---|
pygame.display.set_mode() |
初始化遊戲顯示窗口 |
pygame.display.update() |
刷新屏幕內容顯示,稍後使用 |
set_mode
方法
set_mode(resolution=(0,0), flags=0, depth=0) -> Surface
-
作用 —— 創建遊戲顯示窗口
-
參數
resolution
指定屏幕的寬
和高
,默認創建的窗口大小和屏幕大小一致flags
參數指定屏幕的附加選項,例如是否全屏等等,默認不需要傳遞depth
參數表示顏色的位數,默認自動匹配
-
返回值
- 暫時 可以理解爲 遊戲的屏幕,遊戲的元素 都需要被繪製到 遊戲的屏幕 上
-
注意:必須使用變量記錄
set_mode
方法的返回結果!因爲:後續所有的圖像繪製都基於這個返回結果
# 創建遊戲主窗口
screen = pygame.display.set_mode((480, 700))
簡單的遊戲循環
基本思路:讓程序進行無限循環( while True : ),若要進行退出遊戲,此處使用的是獲取到pygame提供的事件隊列來進行退出處理。
- 爲了做到遊戲程序啓動後,不會立即退出,通常會在遊戲程序中增加一個 遊戲循環
- 所謂 遊戲循環 就是一個 無限循環
- 在 創建遊戲窗口 代碼下方,增加一個無限循環
- 注意:遊戲窗口不需要重複創建
# 創建遊戲主窗口
screen = pygame.display.set_mode((480, 700))
# 遊戲循環
while True:
#獲取事件隊列,判斷是否是退出事件
for event in pygame.event.get():
#退出遊戲
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pass
#遊戲處理主要邏輯
繪製圖像
-
在遊戲中,能夠看到的 遊戲元素 大多都是 圖像
- 圖像文件 初始是保存在磁盤上的,如果需要使用,第一步 就需要 被加載到內存
-
要在屏幕上 看到某一個圖像的內容,需要按照三個步驟:
- 使用
pygame.image.load()
加載圖像的數據 - 使用 遊戲屏幕 對象,調用
blit
方法 將圖像繪製到指定位置 - 調用
pygame.display.update()
方法更新整個屏幕的顯示
基本邏輯
- 使用
"""
1. 加載圖像
2. 繪製圖像
3. 更新顯示 注意:要想在屏幕上看到繪製的結果,就一定要調用 `pygame.display.update()` 方法
"""
# 繪製背景圖像
# 1> 加載圖像
bg = pygame.image.load("./images/background.png")
# 2> 繪製在屏幕
screen.blit(bg, (0, 0)) # 後面(0,0)標定位置
# 3> 更新顯示
pygame.display.update()
精靈 和精靈組
爲了簡化開發步驟,pygame
提供了兩個類
pygame.sprite.Sprite
—— 存儲 圖像數據 image 和 位置 rect 的 對象pygame.sprite.Group
精靈
-
在遊戲開發中,通常把 顯示圖像的對象 叫做精靈
Sprite
-
精靈 需要 有 兩個重要的屬性
image
要顯示的圖像rect
圖像要顯示在屏幕的位置
-
默認的
update()
方法什麼事情也沒做- 子類可以重寫此方法,在每次刷新屏幕時,更新精靈位置
-
注意:
pygame.sprite.Sprite
並沒有提供image
和rect
兩個屬性- 需要程序員從
pygame.sprite.Sprite
派生子類 - 並在 子類 的 初始化方法 中,設置
image
和rect
屬性
- 需要程序員從
精靈組、
- 一個 精靈組 可以包含多個 精靈 對象
- 調用 精靈組 對象的
update()
方法- 可以 自動 調用 組內每一個精靈 的
update()
方法
- 可以 自動 調用 組內每一個精靈 的
- 調用 精靈組 對象的
draw(屏幕對象)
方法- 可以將 組內每一個精靈 的
image
繪製在rect
位置
- 可以將 組內每一個精靈 的
#派生精靈組
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, *args):
# 默認在垂直方向移動
self.rect.y += self.speed
注:引入精靈、精靈組概念是爲了更好管理遊戲中所需部件信息,類似於java中對象和容器的概念
類比於容器用來存放特定的對象信息,而精靈組就是用來管理精靈。
2d遊戲基本原理
- 跟 電影 的原理類似,遊戲中的動畫效果,本質上是 快速 的在屏幕上繪製 圖像
- 電影是將多張 靜止的電影膠片 連續、快速的播放,產生連貫的視覺效果!
- 一般在電腦上 每秒繪製 60 次,就能夠達到非常 連續 高品質 的動畫效果
- 每次繪製的結果被稱爲 幀 Frame (通俗來講就是重繪頻率)
總結
遊戲在開發中用到了加載媒體音樂、字體等功能,其本質同加載圖片邏輯一致,查閱官方文檔即可掌握基本使用。
開發本遊戲重點並不是pygame類庫的使用,重點在於遊戲整體遊戲邏輯的思考,各個對象模塊間的依賴性處理,對象抽象性設計,代碼後期維護性、複用性是重點關注點。
技術的更迭永遠大於學習的速度,重點並不是類庫的功能的學習,而在於業務整體邏輯性的思考,提升對於複雜問題抽象的能力,這纔是一個本項目核心所在。莫要本末倒置。
遊戲頁面的搭建
頁面搭建完成如下任務:
- 繪製背景圖像
- 加入遊戲主循環
- 處理遊戲事件
- 更新遊戲狀態
- 刷新屏幕內容
遊戲版本一
import pygame import sys import constants from game.plane import OurPlane, SmallEnemyPlane from game.war import PlaneWar def main(): pygame.init() width , height = 480,852 # screen = pygame.display.set_mode((width,height)) screen = pygame.display.set_mode((480, 700)) # 繪製背景圖像 bg = pygame.image.load("image/bg.png") while True : for event in pygame.event.get(): #退出遊戲 if event.type == pygame.QUIT: pygame.quit() sys.exit() #繪製 screen.blit(bg,bg.get_rect()) #更新內容 pygame.display.flip()
遊戲版本二
import pygame import sys import constants from game.war import PlaneWar def main(): """ 遊戲入口,main方法 """ # 聲明一個war對象,其內部構造會在後文敘述!!! war = PlaneWar() # 添加小型敵方飛機 war.add_small_enemies(6) # 運行遊戲 war.run_game()
#分析 如上兩份代碼那份寫的更好?(暫時對於版本二中war考慮) 如果有過面向對象設計的基礎知識,我想大部分人都會認爲第二段代更比較符合面向對象的設計思維 面向對象要求設計者面向解決問題進行建模,將對象當做一個解空間來看待,處理流程可以看做:向某個對象發送消息,這個對象便知道此消息目的然後執行一段程序代碼,進而完成某一項功能。此之謂面向對象。 至於代碼一,則像是傳統面向過程的設計思路,根據既定的順序完成某項功能。 #諸如如下代碼這樣採用硬編碼的方式提供路徑資源的應該在程序設計中盡最大可能避免 bg = pygame.image.load("image/bg.png") 原因很簡單: 你的代碼不可能“天衣無縫”,隨着代碼的演進,資源可能會發生改變。在開發階段你可以這樣任性,但是當代碼發佈至服務器端或交付用戶後,用戶不一定有着同你一樣的文件目錄形式,這是你必須面對的問題。 對於硬編碼的形式的代碼段只會加重日後維護難度,所以不應該提倡。
資源統一管理
將所需資源信息加載至一個常量類,便於信息維護
import os import pygame #定義一個常量類用於保存資源信息 # 項目的根目錄 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # 靜態文件的目錄 \\ / ASSETS_DIR = os.path.join(BASE_DIR, 'assets') # 背景圖片 BG_IMG = os.path.join(ASSETS_DIR, 'images/background.png') BG_IMG_OVER = os.path.join(ASSETS_DIR, 'images/game_over.png') # 標題圖片 IMG_GAME_TITLE = os.path.join(ASSETS_DIR, 'images/game_title.png') # 開始遊戲的按鈕 IMG_GAME_START_BTN = os.path.join(ASSETS_DIR, 'images/game_start.png') # 背景音樂 BG_MUSIC = os.path.join(ASSETS_DIR, 'sounds/game_bg_music.mp3') # 遊戲分數顏色 TEXT_SOCRE_COLOR = pygame.Color(255, 255, 0) # 擊中小型飛機添加10分 SCORE_SHOOT_SMALL = 10 # 遊戲結果存儲的文件地址 PLAY_RESULT_STORE_FILE = os.path.join(BASE_DIR, 'store/rest.txt') # 我方飛機的靜態資源 OUR_PLANE_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/hero1.png'), os.path.join(ASSETS_DIR, 'images/hero2.png') ] OUR_DESTROY_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/hero_broken_n1.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n2.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n3.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n4.png'), ] # 子彈圖片和發射聲音 BULLET_IMG = os.path.join(ASSETS_DIR, 'images/bullet1.png') BULLET_SHOOT_SOUND = os.path.join(ASSETS_DIR, 'sounds/bullet.wav') # 敵方小型飛機圖片及音效 SMALL_ENEMY_PLANE_IMG_LIST = [os.path.join(ASSETS_DIR, 'images/enemy1.png')] SMALL_ENEMY_DESTROY_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/enemy1_down1.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down2.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down3.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down4.png'), ] # 小型飛機墜毀時播放音樂 SMALL_ENEMY_PLANE_DOWN_SOUND = os.path.join(ASSETS_DIR, 'sounds/enemy1_down.wav')
#優化的代碼 #通過類.屬性的方式調用資源信息 bg = pygame.image.load(constants.BG_IMG) """ 可能會覺得這樣寫更加繁瑣,但這樣繁瑣所帶來的好處就是日後維護僅需修改一行代碼即可 孰輕孰重,經歷過的人會懂 """
遊戲關鍵類設計
總體概述:
本遊戲邏輯相對簡單,所以涉及到的類也相對較少,具體包括飛機類,子彈類,戰場類。
其中戰場類相對一個整合其他類的容器,主要完成遊戲邏輯功能。
飛機類
分析:
遊戲所需的任務需要我方英雄戰機和敵方戰機。
#誤區:不加思考直接創建兩個英雄戰機,敵方戰機類
這種設計的主要誤區在於忽略了繼承的特性,在書寫代碼時必定會有大量邏輯相似代碼重複出現。
解決策略:抽象出飛機共有特性,讓英雄飛機和敵方戰機繼承,所以有了上述的繼承圖示
#飛機基類 class Plane(pygame.sprite.Sprite): """" 飛機的基礎類 所有飛機的公共信息 飛機圖片,爆炸圖片,墜毀圖片,墜毀音效,存活狀態,子彈精靈組 """ # 飛機的圖片 plane_images = [] # 飛機爆炸的圖片 destroy_images = [] # 墜毀的音樂地址 down_sound_src = None # 飛機的狀態: True,活的,False,死的 active = True # 該飛機發射的子彈精靈組 bullets = pygame.sprite.Group() def __init__(self,screen, speed=None): # 如果子類重寫了__init__方法,那麼在方法內必須顯式的調用父類的__init__方法 super().__init__() #類的不同信息,不同實現類,資源信息不同 self.screen = screen # 加載靜態資源 self.img_list = [] self._destroy_img_list = [] self.down_sound = None self.load_src() #設定下落速度 self.speed = speed or 10 #獲取飛機位置信息 self.rect = self.img_list[0].get_rect() #獲取飛機寬高信息 self.plane_w , self.plane_h = self.img_list[0].get_size() #獲取窗口信息 self.width , self.height = self.screen.get_size() # 改變飛機的初始化位置, 放在屏幕的下方 self.rect.left = int((self.width - self.plane_w) / 2) self.rect.top = int(self.height / 2) @property def image(self): return self.img_list[0] #在屏幕上繪製飛機信息 def blit_me(self): self.screen.blit(self.image, self.rect) """ 加載圖像,聲音等資源信息 :return: """ def load_src(self): #裝載飛機圖片 此處需要藉助於pygame load image方法記載信息,提供的信息爲路徑信息 for img in self.plane_images: self.img_list.append(pygame.image.load(img)) #裝載墜毀圖片 for img in self.destroy_images: self._destroy_img_list.append(pygame.image.load(img)) #裝載音樂信息 if self.down_sound_src : self.down_sound = pygame.mixer.Sound(self.down_sound_src) """ 控制飛機移動的方法 """ def move_up(self): """ 飛機向上移動 """ self.rect.top -= self.speed def move_down(self): """ 飛機向下移動 """ self.rect.top += self.speed def move_left(self): """ 飛機向左移動 """ self.rect.left -= self.speed def move_right(self): """ 飛機向右移動 """ self.rect.left += self.speed """ 記錄飛機被擊毀後的操作 """ def broken_down(self): """ 播放墜毀音樂 加載墜毀動畫 生命標記置爲false :return: """ #播放墜毀音樂 if self.down_sound: self.down_sound.play() #加載墜毀動畫 for img in self._destroy_img_list: #繪製到屏幕上 self.screen.blit(img,self.rect) #生命標記改變 self.active = False "發射子彈" def shoot(self): """ 飛機發射子彈 """ bullet = Bullet(self.screen, self, 15) self.bullets.add(bullet)
#具體分析 #屬性信息 # 飛機的圖片 plane_images = [] # 飛機爆炸的圖片 destroy_images = [] # 墜毀的音樂地址 down_sound_src = None # 飛機的狀態: True,活的,False,死的 active = True # 該飛機發射的子彈精靈組 bullets = pygame.sprite.Group() """ 由於不同的飛機會有不同的圖片信息所以利用一個 plane_images的變量來存放。 隨後的 destroy_images, destroy_images也是同樣的設計理念 在每個類初始化時增設一個 img_list信息,用來記錄戰機的飛機信息 """ #初始化構造器方法 def __init__(self,screen, speed=None): # 如果子類重寫了__init__方法,那麼在方法內必須顯式的調用父類的__init__方法 super().__init__() #類的不同信息,不同實現類,資源信息不同 self.screen = screen # 加載靜態資源 self.img_list = [] #保存飛機信息 self._destroy_img_list = [] #保存墜毀後的圖片信息 self.down_sound = None self.load_src() #設定下落速度 self.speed = speed or 10 #獲取飛機位置信息 self.rect = self.img_list[0].get_rect() #獲取飛機寬高信息 self.plane_w , self.plane_h = self.img_list[0].get_size() #獲取窗口信息 self.width , self.height = self.screen.get_size() # 改變飛機的初始化位置, 放在屏幕的下方 self.rect.left = int((self.width - self.plane_w) / 2) self.rect.top = int(self.height / 2) #加載plane_images中的資源信息 def load_src(self): #裝載飛機圖片 此處需要藉助於pygame load image方法記載信息,提供的信息爲路徑信息 for img in self.plane_images: self.img_list.append(pygame.image.load(img)) #裝載墜毀圖片 for img in self.destroy_images: self._destroy_img_list.append(pygame.image.load(img)) #裝載音樂信息 if self.down_sound_src : self.down_sound = pygame.mixer.Sound(self.down_sound_src)
""" 處理移動邏輯的核心在於座標的移動,通過控制座標的加減來實現對飛機的控制效果 注:pgame的座標系不同於傳統的座標,pygame中其實座標在左上方,即左上方爲原點! """ #移動對象方法 def move_up(self): """ 飛機向上移動 """ self.rect.top -= self.speed def move_left(self): """ 飛機向左移動 """ self.rect.left -= self.speed
英雄飛機
""" 英雄飛機 """ class OurPlane(Plane): """ 我方的飛機 """ # 飛機的圖片 plane_images = constants.OUR_PLANE_IMG_LIST # 飛機爆炸的圖片 destroy_images = constants.OUR_DESTROY_IMG_LIST # 墜毀的音樂地址 down_sound_src = None #更新飛機動畫信息 def update(self, war): #傳入移動鍵盤信息到move中控制飛機的移動處理 self.move(war.key_down) if war.frame % 5 : self.screen.blit(self.img_list[0],self.rect) else: self.screen.blit(self.img_list[1],self.rect) # 飛機碰撞檢測 rest = pygame.sprite.spritecollide(self, war.enemies, False) if rest : # 1. 遊戲結束 war.status = war.OVER # 2. 敵方飛機清除 war.enemies.empty() war.small_enemies.empty() # 3. 我方飛機墜毀效果 self.broken_down() # 4. 記錄遊戲成績 def move(self, key): """ 飛機移動自動控制 """ if key == pygame.K_w or key == pygame.K_UP: self.move_up() elif key == pygame.K_s or key == pygame.K_DOWN: self.move_down() elif key == pygame.K_a or key == pygame.K_LEFT: self.move_left() elif key == pygame.K_d or key == pygame.K_RIGHT: self.move_right() def move_up(self): """ 向上移動,超出範圍之後,拉回來 """ super().move_up() if self.rect.top <= 0: self.rect.top = 0 def move_down(self): super().move_down() if self.rect.top >= self.height - self.plane_h: self.rect.top = self.height - self.plane_h def move_left(self): super().move_left() if self.rect.left <= 0: self.rect.left = 0 def move_right(self): super().move_right() if self.rect.left >= self.width - self.plane_w: self.rect.left = self.width - self.plane_w
#英雄類信息分析 """ 英雄飛機應該可供玩家操作,本遊戲操作核心相對簡單,通過監聽鍵盤按鍵來完成具體操作的處理 """ #根據不同的按鍵信息,響應不同的事件 def move(self, key): """ 飛機移動自動控制 """ if key == pygame.K_w or key == pygame.K_UP: self.move_up() elif key == pygame.K_s or key == pygame.K_DOWN: self.move_down() elif key == pygame.K_a or key == pygame.K_LEFT: self.move_left() elif key == pygame.K_d or key == pygame.K_RIGHT: self.move_right() #根據按鍵不同進行不同的移動處理 """" 在處理移動信息時,注意飛機的移動,控制飛機移動核心在於避免飛機移出屏幕 之後敵機類同該功能類似 """ def move_down(self): super().move_down() if self.rect.top >= self.height - self.plane_h: self.rect.top = self.height - self.plane_h
class SmallEnemyPlane(Plane): """ 敵方的小型飛機 """ # 飛機的圖片 plane_images = constants.SMALL_ENEMY_PLANE_IMG_LIST # 飛機爆炸的圖片 destroy_images = constants.SMALL_ENEMY_DESTROY_IMG_LIST # 墜毀的音樂地址 down_sound_src = constants.SMALL_ENEMY_PLANE_DOWN_SOUND def __init__(self, screen, speed): super().__init__(screen, speed) # 每次生成一架新的小型飛機的時候,隨機的位置出現在屏幕中 # 改變飛機的隨機位置 self.init_pos() def init_pos(self): """ 改變飛機的隨機位置 """ # 屏幕的寬度-飛機的寬度 self.rect.left = random.randint(0, self.width - self.plane_w) # 屏幕之外隨機高度 self.rect.top = random.randint(-5 * self.plane_h, -self.plane_h) def update(self, *args): """ 更新飛機的移動 """ super().move_down() # 畫在屏幕上 self.blit_me() # 超出範圍後如何處理 # 1. 重用 if self.rect.top >= self.height: self.active = False # self.kill() self.reset() # todo 2. 多線程、多進程 def reset(self): """ 重置飛機的狀態,達到複用的效果 """ self.active = True # 改變飛機的隨機位置 self.init_pos() def broken_down(self): """ 飛機爆炸 """ super().broken_down() # 重複利用飛機對象 self.reset()
子彈類
主要用於戰機發射子彈,核心難點就在於子彈的碰撞檢測
class Bullet(pygame.sprite.Sprite): """ 子彈類 """ # 子彈狀態,True: 活着 active = True def __init__(self, screen, plane, speed=None): super().__init__() self.screen = screen # 速度 self.speed = speed or 10 self.plane = plane # 加載子彈的圖片 self.image = pygame.image.load(constants.BULLET_IMG) #self.image = pygame.image.load("./assets/images/bullet1.png") # 改變子彈的位置 self.rect = self.image.get_rect() self.rect.centerx = plane.rect.centerx self.rect.top = plane.rect.top # 發射的音樂效果 self.shoot_sound = pygame.mixer.Sound(constants.BULLET_SHOOT_SOUND) self.shoot_sound.set_volume(0.3) self.shoot_sound.play() def update(self,war): """ 更新子彈的位置 """ self.rect.top -= self.speed # 超出屏幕的範圍 if self.rect.top < 0: self.remove(self.plane.bullets) # 繪製子彈 self.screen.blit(self.image, self.rect) # 碰撞檢測,檢測子彈是否已經碰撞到了敵機 rest = pygame.sprite.spritecollide(self, war.enemies, False) for r in rest: # 1. 子彈消失 self.kill() # 2. 飛機爆炸,墜毀效果 r.broken_down() # 3. 統計遊戲成績 war.rest.score += constants.SCORE_SHOOT_SMALL # 保存歷史記錄 war.rest.set_history()
""" 2個矩形如果發生碰撞(即:圖形有重疊區域),按上圖的判斷條件就能檢測出來,如果是圓形,則稍微變通一下,用半徑檢測。如果是其它不規則圖形,大多數遊戲中,並不要求精確檢測,可以在外層套一個矩形,大致用上圖的原理檢測。 """ def collision_check(a, b): temp1 = (b.x <= a.x + a.width <= b.x + b.width) temp2 = (b.y <= a.y + a.height <= b.y + b.height) return temp1 and temp2 #由於使用pygame框架,所以並不用這樣繁瑣處理碰撞檢測問題 #利用pygame提供的spritecollide來進行碰撞的測試處理 rest = pygame.sprite.spritecollide(self, war.enemies, False) #注意此時返回一個列表內容,包含發生碰撞飛機信息 #讓碰撞的敵機消失 for r in rest: # 1. 子彈消失 self.kill() # 2. 飛機爆炸,墜毀效果 r.broken_down() # 3. 統計遊戲成績 war.rest.score += constants.SCORE_SHOOT_SMALL # 保存歷史記錄 war.rest.set_history()
戰場類(war)
主要涵蓋如下功能
遊戲頁面初始化
事件監聽綁定
開始遊戲
添加敵機/英雄
import pygame import sys import constants from game.plane import OurPlane, SmallEnemyPlane from store.result import PlayRest class PlaneWar(object): """ 飛機大戰 """ # 遊戲狀態 READY = 0 # 遊戲準備中 PLAYING = 1 # 遊戲中 OVER = 2 # 遊戲結束 status = READY # 0準備中,1 遊戲中,2 遊戲結束 our_plane = None frame = 0 # 播放幀數 # 一架飛機可以屬於多個精靈組 small_enemies = pygame.sprite.Group() enemies = pygame.sprite.Group() # 遊戲結果 rest = PlayRest() def __init__(self): # 初始化遊戲 pygame.init() self.width, self.height = 480, 852 # 屏幕對象 self.screen = pygame.display.set_mode((self.width, self.height)) # 設置窗口標題 pygame.display.set_caption('飛機大戰') # 加載背景圖片 self.bg = pygame.image.load(constants.BG_IMG) self.bg_over = pygame.image.load(constants.BG_IMG_OVER) # 遊戲的標題 self.img_game_title = pygame.image.load(constants.IMG_GAME_TITLE) self.img_game_title_rect = self.img_game_title.get_rect() # 寬度和高度 t_width, t_height = self.img_game_title.get_size() self.img_game_title_rect.topleft = (int((self.width - t_width) / 2), int(self.height / 2 - t_height)) # 開始按鈕 self.btn_start = pygame.image.load(constants.IMG_GAME_START_BTN) self.btn_start_rect = self.btn_start.get_rect() btn_width, btn_height = self.btn_start.get_size() self.btn_start_rect.topleft = (int((self.width - btn_width) / 2), int(self.height / 2 + btn_height)) # 遊戲文字對象 self.score_font = pygame.font.SysFont('華文隸書', 32) # 加載背景音樂 pygame.mixer.music.load(constants.BG_MUSIC) pygame.mixer.music.play(-1) # 無限循環播放 pygame.mixer.music.set_volume(0.2) # 設置音量 # 我方飛機對象 self.our_plane = OurPlane(self.screen, speed=4) self.clock = pygame.time.Clock() # 上次按的鍵盤上的某一個鍵,用於控制飛機 self.key_down = None def bind_event(self): """ 綁定事件 """ # 1. 監聽事件 for event in pygame.event.get(): # 退出遊戲 if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠標點擊進入遊戲 # 遊戲正在準備中,點擊才能進入遊戲 if self.status == self.READY: self.status = self.PLAYING self.rest.score = 0 elif self.status == self.PLAYING: # 點擊鼠標發射子彈 self.our_plane.shoot() elif self.status == self.OVER: self.status = self.READY self.add_small_enemies(6) elif event.type == pygame.KEYDOWN: # 鍵盤事件 self.key_down = event.key # 遊戲正在遊戲中,才需要控制鍵盤 ASWD if self.status == self.PLAYING: if event.key == pygame.K_w or event.key == pygame.K_UP: self.our_plane.move_up() elif event.key == pygame.K_s or event.key == pygame.K_DOWN: self.our_plane.move_down() elif event.key == pygame.K_a or event.key == pygame.K_LEFT: self.our_plane.move_left() elif event.key == pygame.K_d or event.key == pygame.K_RIGHT: self.our_plane.move_right() elif event.key == pygame.K_SPACE: # 發射子彈 self.our_plane.shoot() def add_small_enemies(self, num): """ 隨機添加N架小型敵機 :param num: 飛機的產生數量 :return: """ # 隨機添加6架小型敵機 for i in range(num): plane = SmallEnemyPlane(self.screen, 4) plane.add(self.small_enemies, self.enemies) def run_game(self): """ 遊戲主循環部分 """ while True: # 1. 設置幀速率 self.clock.tick(60) self.frame += 1 if self.frame >= 60: self.frame = 0 # 2. 綁定事件 self.bind_event() # 3.更新遊戲的狀態 if self.status == self.READY: # 遊戲正在準備中 # 繪製背景 self.screen.blit(self.bg, self.bg.get_rect()) # 標題 self.screen.blit(self.img_game_title, self.img_game_title_rect) # 開始按鈕 self.screen.blit(self.btn_start, self.btn_start_rect) self.key_down = None elif self.status == self.PLAYING: # 遊戲進行中 # 繪製背景 self.screen.blit(self.bg, self.bg.get_rect()) # 繪製飛機 self.our_plane.update(self) # 繪製子彈 self.our_plane.bullets.update(self) # 繪製敵方飛機 self.small_enemies.update() # 遊戲分數 score_text = self.score_font.render( '得分: {0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_text, score_text.get_rect()) elif self.status == self.OVER: # # 遊戲結束 # 遊戲背景 self.screen.blit(self.bg_over, self.bg_over.get_rect()) # 分數統計 # 1. 本次總分 score_text = self.score_font.render( '{0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) score_text_rect = score_text.get_rect() text_w, text_h = score_text.get_size() # 改變文字的位置 score_text_rect.topleft = ( int((self.width - text_h) / 2), int(self.height / 2) ) self.screen.blit(score_text, score_text_rect) # 2. 歷史最高分 score_his = self.score_font.render( '{0}'.format(self.rest.get_max_core()), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_his, (150, 40)) pygame.display.flip()
#關鍵方法分析 """" 如下代碼看到可能會比較多,但是主要邏輯功能如下 由於遊戲有三種狀態:開始,進行中,結束三種狀態 遊戲操作邏輯如下: 開始 READY 首先用戶鼠標點擊觸發pygame提供的 pygame.MOUSEBUTTONDOWN事件,此狀態下要做的就是繪製遊戲登錄頁面信息 進行中 PLAYING 此時要做的就是更新頁面信息,進入遊戲主頁面,繪製敵機信息,播放遊戲背景音樂,繪製子彈信息,實時更新成績信息。如果英雄戰機被擊中則將狀態置爲OVer 結束 OVER 此時遊戲結束,顯示結束畫面,展示用戶得分情況 """" def run_game(self): """ 遊戲主循環部分 """ while True: # 1. 設置幀速率 self.clock.tick(60) self.frame += 1 if self.frame >= 60: self.frame = 0 # 2. 綁定事件 self.bind_event() # 3.更新遊戲的狀態 if self.status == self.READY: # 遊戲正在準備中 # 繪製背景 self.screen.blit(self.bg, self.bg.get_rect()) # 標題 self.screen.blit(self.img_game_title, self.img_game_title_rect) # 開始按鈕 self.screen.blit(self.btn_start, self.btn_start_rect) self.key_down = None elif self.status == self.PLAYING: # 遊戲進行中 # 繪製背景 self.screen.blit(self.bg, self.bg.get_rect()) # 繪製飛機 self.our_plane.update(self) # 繪製子彈 self.our_plane.bullets.update(self) # 繪製敵方飛機 self.small_enemies.update() # 遊戲分數 score_text = self.score_font.render( '得分: {0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_text, score_text.get_rect()) elif self.status == self.OVER: # # 遊戲結束 # 遊戲背景 self.screen.blit(self.bg_over, self.bg_over.get_rect()) # 分數統計 # 1. 本次總分 score_text = self.score_font.render( '{0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) score_text_rect = score_text.get_rect() text_w, text_h = score_text.get_size() # 改變文字的位置 score_text_rect.topleft = ( int((self.width - text_h) / 2), int(self.height / 2) ) self.screen.blit(score_text, score_text_rect) # 2. 歷史最高分 score_his = self.score_font.render( '{0}'.format(self.rest.get_max_core()), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_his, (150, 40)) #刷新頁面信息 pygame.display.flip()
成績信息讀取
讀取記錄中的信息,由於並未採用數據庫,所以此處通過一個score.txt文件來保存成績的最高分,主要就是文件讀寫模塊的應用
import constants
class PlayRest(object):
""" 玩家的結果統計 """
__score = 0 # 總分
__life = 3 # 生命數量
__blood = 1000 # 生命值
@property
def score(self):
""" 單次遊戲分數 """
return self.__score
@score.setter
def score(self, value):
""" 設置遊戲分數 """
if value < 0:
return None
self.__score = value
def set_history(self):
""" 記錄最高分 """
# 1. 讀取文件中的存儲的分數
# 2. 如果新的分數比文件中的分數要大,則進行存儲
# 如果小於文件中的分數,不需要做處理
# 3. 存儲分數,不是追加的模式a+,而是替換的模式w
if int(self.get_max_core()) < self.score:
with open(constants.PLAY_RESULT_STORE_FILE, 'w') as f:
f.write('{0}'.format(self.score))
def get_max_core(self):
""" 讀取文件中的歷史最高分 """
rest = 0
with open(constants.PLAY_RESULT_STORE_FILE, 'r') as f:
r = f.read()
if r:
rest = r
return rest
``
__blood = 1000 # 生命值
@property
def score(self):
""" 單次遊戲分數 """
return self.__score
@score.setter
def score(self, value):
""" 設置遊戲分數 """
if value < 0:
return None
self.__score = value
def set_history(self):
""" 記錄最高分 """
# 1. 讀取文件中的存儲的分數
# 2. 如果新的分數比文件中的分數要大,則進行存儲
# 如果小於文件中的分數,不需要做處理
# 3. 存儲分數,不是追加的模式a+,而是替換的模式w
if int(self.get_max_core()) < self.score:
with open(constants.PLAY_RESULT_STORE_FILE, 'w') as f:
f.write('{0}'.format(self.score))
def get_max_core(self):
""" 讀取文件中的歷史最高分 """
rest = 0
with open(constants.PLAY_RESULT_STORE_FILE, 'r') as f:
r = f.read()
if r:
rest = r
return rest
總結
由於時間倉促,作品難免有些不足。對於可以優化的地方,文中開頭也進行了闡述,總的來說還是較好的實現了基本遊戲邏輯。同時在源碼中也提供了詳細的註釋,方便讀者閱讀,如果您好的想法和觀點,歡迎在評論區留言討論。