飛機大戰-面向對象-pygame


飛機大戰

最近學習了python的面向對象,對面向對象的理解不是很深刻。

面向對象是數據和函數的'打包整理',將相關數據和處理數據的方法集中在一個地方,方便使用和管理。

本着學習的目的,在網上找了這個飛機大戰游戲的素材和相關代碼,自己研究學習,加深對面向對象的理解。

python可以做游戲,最基本的一個第三方模塊就是pygame,借助pygame可以實現2D和3D游戲的開發。

對python開發游戲感興趣的園友請參考官方文檔:pygame.doc

下面就開始學習了解對象思想吧,順便學學pygame,娛樂一下。

游戲需求

# 飛機大戰
<1> 玩機通過鍵盤操作我方飛機,我方飛機自動發射初級子彈。
<2> 敵機分三種:小型敵機、中型敵機、大型敵機,區別:速度不同,數量不同,外形不同,血值不同。
<3> 小型敵機速度快,數量多,一顆子彈必殺;大型敵機和中型敵機速度慢,數量少,需要多可子彈才能消滅。
<4> 小型敵機分值低,大型敵機和中型敵機分值高。
<5> 統計玩機得分和最高分記錄。
<6> 隨着得分的增加,提升游戲等級,增加游戲的難度:增加敵機速度和數量
<7> 任意敵機和我方飛機碰撞,則玩家挑戰失敗一次;玩家有三次挑戰機會。三次機會用完結束游戲。
<8> 我方飛機和敵機毀滅時,動畫效果要實現
<9> 游戲中,每隔30s有一次隨機空投補給:全屏炸彈或超級子彈。
<10> 游戲開始,玩家自帶三顆全屏炸彈,按空格鍵觸發,消滅屏幕內所有敵機;
<11> 使用一次全屏炸彈,數量減一,當玩家的全屏炸彈數少於3顆時,通過空投補給全屏炸彈,最多攜帶3顆全屏炸彈。
<12> 玩家領到超級子彈時,有18s的使用時間限制;超級子彈發射數量是普通子彈的兩倍。
<13> 大型敵機和中型敵機,要顯示血量,標識生命值,生命值結束,敵機摧毀,玩機得分。
<14> 玩家可以通過暫停按鈕暫停和繼續游戲
<15> 游戲有開始界面和結束是否繼續的界面。

需求分析

# 角色1:我方飛機、敵機(小型敵機、中型敵機、大型敵機)、子彈、補給(全屏炸彈、超級子彈)
# 角色2:設置類、面板類、游戲狀態類

- 我方飛機
	- 繼承pygame.sprite.Sprite
	- 本地保存設置類對象和 屏幕對象
	- 數據屬性:surface\destory_surface\rect\speed\active\invincible\mask
    - 功能屬性:上下左右移動、繪制、重置位置

- 敵機(小、中、大)
	- 繼承pygame.sprite.Sprite
    - 本地保存設置類對象和 屏幕對象
	- 數據屬性:surface\destory_surface\rect\speed\active\hit\mask\energy
    - 功能屬性:向下移動、繪制、血槽、重置位置

- 子彈(子彈1、子彈2)
	- 繼承pygame.sprite.Sprite
    - 本地保存設置類對象和 屏幕對象
	- 數據屬性:surface\rect\speed\active\mask
    - 功能屬性:向上移動、繪制、重置位置

- 補給(全屏炸彈、超級子彈)
	- 繼承pygame.sprite.Sprite
    - 本地保存設置類對象和 屏幕對象
	- 數據屬性:surface\rect\speed\active\mask
    - 功能屬性:向下移動、繪制、重置位置

- 設置類
	- 數據屬性:游戲基本參數設置
    - 功能屬性:游戲加載、暫停、統計得分、補給設置等
    
- 面板類
	- 本地保存設置類對象和 屏幕對象、游戲狀態對象
	- 數據屬性:分數面板數據、暫停面板數據、全屏炸彈面板數據、玩家生命個數面板數據、開啟|結束|繼續面板等
    - 功能屬性:繪制各種面板數據
    
- 面板類
	- 本地保存設置類對象
	- 數據屬性:game_active\game_paused\game_start\game_level\game_score_level

提取功能

提取游戲功能背后對應的pygame功能

  • 游戲背景圖、背景音樂 - 顯示、圖片、音效功能
  • 繪制我方飛機、實現移動 - 事件監聽功能
  • 繪制敵機
  • 我機-敵機碰撞檢測 - 碰撞檢測功能
  • 我機死亡后重置-5秒無敵模式 - 自定義時間事件
  • 增加普通子彈
  • 子彈-敵機碰撞檢測
  • 繪制血槽能量值 - 幾何圖形功能
  • 得分統計面板 - 字體功能
  • 全屏炸彈面板功能
  • 游戲難度等級
  • 游戲暫停-繼續 - 光標設置圖像功能
  • 空投補給-全屏炸彈和超級子彈 - 自定義時間事件
  • 普通子彈和超級子彈的切換 - 自定義時間事件

運行環境

- win10
- python3.8
- pygame1.9.6
- pycharm2019.3

實現思路

  • 采用腳本的方式運行整個游戲,游戲運行在一個主函數內,在這個主函數內實例化各種對象。
  • 然后通過一個主循環,主循環內有兩個功能:監測事件,更新對象
  • 監測事件,觸發相應功能函數的執行,改變對象屬性
  • 更新屏幕,功能1:根據游戲狀態,更新對象位置並繪制;功能2:對象間碰撞判斷(可以優化)
  • 每個對象的類,在不同的文件中實現
  • 所有圖片素材、音效素材、字體素材單獨文件夾管理存放
  • 增加設置類,保存游戲設置相關的參數和方法,在大多數對象內保存設置類對象,方便直接獲取游戲相關參數
  • 增加面板類,分擔設置類的壓力;面板類負責管理游戲中各種面板按鈕標題繪制時需要的數據和方法。
  • 增加游戲狀態類,判斷游戲是否處於:開始、活動、結束 等狀態。

程序結構

腳本的方式

Aircraft-Battle/	# 游戲根目錄
|-- fonts			# 字體文件夾
|-- images			# 圖片文件夾
|-- sounds			# 音樂文件夾
|-- board.py		# 面板類
|-- bullet.py		# 子彈類
|-- enemy.py		# 敵機類
|-- game_functions.py		# 游戲方法,包括事件監測、屏幕更新等等
|-- game_status.py		# 游戲狀態類
|-- main.py			# 游戲入口函數,主函數
|-- my_ship.py		# 我方飛機類
|-- settings.py		# 配置類
|-- supply.py		# 空投補給類
|-- recorded.txt        # 最高分記錄
|-- readme.md

功能實現

飛機噴氣動畫

任何視頻動畫效果都是一幀一幀順出來的,pygame也不例外。噴氣效果就是兩張圖片不停的切換實現的。

游戲全局設置了一個刷新頻率,clock.tick(60), 在這樣一個速度下切換兩張局部不同的圖,肉眼很難分辨。

這就需要一個在不改變全局刷新頻率,實現局部延遲的需求

  • 在全局設置一個時間延遲變量,每次刷新都減一,減到零后再從100開始累減。
  • 根據這個延遲變量,設置切換的頻率,每隔幾秒切換一次圖片。
delay = 100
switch_image = True

while 1:
   delay -= 1
   if delay == 0:
       delay = 100

   if not (delay % 5):		# 每個5幀切換一次
           switch_image = not switch_image

   if switch_image:
       """開始切換"""
  • 類推:飛機催化時毀滅的動畫效果、敵機毀滅的動畫效果、擊中敵機的動畫效果都是采用這個策略實現的。

游戲暫停功能

游戲暫停功能其實很簡單,所謂的暫停其實就是讓對象們不要動起來,實現的方式就是不更新對象的位置。

可以通過一個檢測事件,當玩家在暫停按鈕處點擊了鼠標左鍵,則將游戲狀態參數paused設置為True,

根據這個參數,判斷何時更新對象位置合適不更新對象位置。

一個小的注意點是,暫停時要將某些事件音效關閉。

判斷鼠標在指定位置

比如,在游戲結束時,鼠標點擊重新開始按鈕,開始游戲。那如何判斷鼠標在重新開始位置呢?

我們可以使用pygame.rect.collidepoint(event.pos)

elif event.type == MOUSEMOTION:
    if ab_board.pause_rect.collidepoint(event.pos):  # 鼠標在pause_rect內,返回True
        pass

碰撞檢測功能

運動只是第一步,碰撞檢測才是大多數游戲的靈魂。只有碰撞檢測實現了,才有可能實現更過的業務邏輯。

敵機和子彈的碰撞、我機和敵機的碰撞,我們都借助pygame中的精靈類(Sprite)幫我們實現。

我們將所有敵機對象都放在一個大Group精靈組內,然后每種類型的敵機單獨放在各自的精靈組內。

這樣做碰撞檢測時,我們只需要將我機對象和大Group精靈組判斷是否碰撞,子彈和敵機也類似。

collide_obj = pygame.sprite.spritecollide(
    sprite, group, False, collide=pygame.sprite.collide_mask)

if collide_obj:
    pass
  • spritecollide 是一個精靈和一個精靈組的碰撞檢測方法,返回一個列表,列表內是發生碰撞的精靈組成員

  • 參數False背后對應的參數是,是否將發生碰撞的精靈從精靈組內移出,False表示不移出。這里我們不移出,但會做一些邏輯判斷,將這個精靈的active屬性設為False,然后重置該靜靈位置,不需要再次刪除和創建。

  • 參數collide表示,指定碰撞檢測機制,這里通過圖像不透明部分的重疊來檢測碰撞,這種碰撞檢測機制需要被檢測的對象有一個mask數據屬性,self.mask = pygame.mask.from_surface(self.image)

# 補充:
# pygame的碰撞檢測機制有很多
- 通過矩形  collide_rect(left, right),返回布爾值,判斷矩形位置知否重疊,left和right是兩個精靈
- 通過圓形	collide_circle(left, right),返回布爾值,判斷圓心和半徑,需要精靈有rect和radius屬性
- 通過圖像不透明面積 collide_mask(left, right) mask判斷,需要精靈有rect和mask屬性


# pygame的碰撞檢測方式
- 一對多 spritecollide(sprite, group, dokill, collided=None),
		返回碰撞的精靈列表,存放group內被sprite碰撞到的精靈
- 多對多 groupcollide(groupa, groupb, dokilla, dokillb, collided=None),
		返回字典,key:groupa內發生碰撞的精靈,value:groupb內被key撞的精靈

- 一對多 spritecollideany(sprite, group, collided=None),
		返回一個group內找到的第一個被撞的精靈,如果沒有返回None

控制玩家飛機移動

玩家移動飛機操作,可以交給pygame.event的事件監測實現,不過我們這里通過pygame.key實現的。

因為有這么一個'規則':比如鍵盤事件,偶然的事件采用event,頻繁的事件采用key。

其實這樣是有道理的,因為pygame.event在處理鍵盤事件時,內部采用key的一些方法。直接采用key更快一點。

def check_me_move(me, ab_state):
    """檢測我方飛機移動事件"""
    if ab_state.game_active and not ab_state.game_paused:
        key_pressed = pygame.key.get_pressed()
        if key_pressed[K_w] or key_pressed[K_UP]:	# 上移
            me.move_up()
        if key_pressed[K_s] or key_pressed[K_DOWN]: # 下移
            me.move_down()
        if key_pressed[K_a] or key_pressed[K_LEFT]: # 左移
            me.move_left()
        if key_pressed[K_d] or key_pressed[K_RIGHT]: # 右移
            me.move_right()

定時功能

比如每隔30s一個空投,超級子彈18s使用時間,我機重生時5s無敵時間,都需要使用定時功能。

定時功能,可以通過pygame提供的自定義事件實現,pygame提供 USEREVENT,用戶可以自定義自己的事件。

注意:每自定義一個事件,USEREVENT + 1

比如,我們自定義上述三個功能的自定義事件

import pygame
from pygame.locals import *

# 定義事件
SUPPLY_INTERVAL = USEREVENT
SUPPER_BULLET_TIME = USEREVENT + 1
INVINCIBLE_TIME = USEREVENT + 1

# 觸發自定義時間事件
pygame.time.set_timer(SUPPLY_INTERVAL, 30 * 1000)  # 注意時間單位事毫秒
pygame.time.set_timer(SUPPER_BULLET_TIME, 18 * 1000) 
pygame.time.set_timer(INVINCIBLE_TIME, 5 * 1000) 

# 捕捉自定義事件, 終止事件
for event in pygame.event.get():
    if event.type == QUIT:
        sys.exit()
        
    elif event.type == SUPPLY_INTERVAL:
        pass
    
    elif event.type == SUPPER_BULLET_TIME:
        pygame.time.set_timer(SUPPER_BULLET_TIME, 0) 
        
   	elif event.type == INVINCIBLE_TIME:
       	pygame.time.set_timer(INVINCIBLE_TIME, 0) 

繪制血槽功能

繪制血槽,標識敵機的能量值,對於大型敵機和中型敵機,子彈擊中只能減少敵機的一個生命值;

當敵機的生命值耗盡時,敵機毀滅,玩家得分。

可以通過敵機當前的生命值和初始生命值相除,得到一個比例,這個比例是敵機的當前血量百分比。

在敵機頂部一定位置畫一條寬度為2的線,一條固定長度的黑線表示血槽,另一條可變長度的線表示剩余血量。

當血量百分比大於0.2時畫綠線,否則顯示紅線。

此處畫線使用:pygame.draw.line(Surface, color, start_pos, end_pos, width=1)

子彈位置更新

本游戲中,子彈的操作比較特殊。游戲開始先實例化普通子彈和超級子彈,存放在兩個列表中。

普通子彈4顆,從飛機頂部發射;超級子彈8顆,從飛機兩側同時發射兩顆。

每隔10幀,將子彈列表中的一個子彈位置重置,其他幀繪制更新子彈位置並繪制,效果就是子彈不停的射出。

def blit_bullet(ab_settings, bullets, me):
    # 每個10幀,重置一個子彈的位置
    if not (ab_settings.delay % 10):
        ab_settings.bullet_sound.play()
        if ab_settings.is_double_bullet:
            bullets[ab_settings.bullet2_index].reset((me.rect.centerx - 33, me.rect.centery))
            bullets[ab_settings.bullet2_index + 1].reset((me.rect.centerx + 30, me.rect.centery))
            ab_settings.bullet2_index = (ab_settings.bullet2_index + 2) % ab_settings.bullet2_num
        else:
            bullets[ab_settings.bullet1_index].reset((me.rect.centerx, me.rect.top - 5))
            ab_settings.bullet1_index = (ab_settings.bullet1_index + 1) % ab_settings.bullet1_num
    for bullet in bullets:
        bullet.move()
        bullet.blitme()

自定義鼠標圖像

繪制心愛的圖像,位置設為鼠標所在位置;再將鼠標隱藏。

pygame.mouse.set_visible(False)
mouse_image = pygame.image.load('images/check.png').convert_alpha()
mouse_rect = mouse_image.get_rect()
mouse_rect = pygame.mouse.get_pos()
screen.blit(mouse_image, mouse_rect)

補充

# 游戲圖標,需要 .ico文件,32x32,不要convert_alpha(),一定要在set_mode()之前
# 背景圖片一定要在set_mode()之后,即一定要在有了screen之后才能image.load()加載圖片
# 鼠標顯示設置關閉使用完畢后,記得還原設置。
# 相關的數據放在一塊,比如面板的繪制操作,將image和rect盡可能在board.init中;繪制動作在board下的方法中
# 得分統計,千分位顯示,使用 format(1234567, ',')  --> 1,234,567

總結

  • pygame游戲動畫是一幀一幀刷出來的,圖像在屏幕上繪制的先后順序很重要。

  • 通過事件監測,對象位置更新、繪制屏幕,實現基本游戲動畫的制作

  • 游戲采用面向對象的方式,實在太合適了;可擴展性極強,一個屬性判斷就可以擴展出很多功能。

  • pygame的事件監測和精靈類的非常好用,碰撞檢測機制很有用

  • 面向對象,對象的屬性和方法息息相關;本質還是面向過程,好處是數據使用起來更加方便靈活。

  • 有了對象,就有了對象的數據和方法,自己的能力也同時得到極大的提升。

程序源碼

游戲源碼和素材

fork my on Github

Update to [V3-優化子彈生成函數, 微調游戲等級]

代碼量統計如下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM