Python遊戲編程(十五)飛機大戰

我們將用面向對象的思維寫一個飛機大戰的小遊戲 。

分爲幾個類:

  • 全局設置類:包括一些基本的參數以及音樂、圖片文件的路勁。
  • 子彈基礎類
  • 玩家子彈
  • 敵人子彈
  • 飛機基類
  • 玩家飛機基類
  • 敵人飛機類

 

目錄

(一)class Settings():

(二)class Bullet:

(三)玩家、敵人子彈類

(四)class Plane:

(五)玩家飛機

(六)敵人飛機

(七)設置方法

(八)主函數


(一)class Settings():

import sys, time, random
import pygame
from pygame.locals import *
import threading

SCREENWIDTH = 512
SCREENHEIGHT = 768
Gray = (128, 128, 128)
# 全局的設置類
class Settings():

    def __init__(self):
        # 定義遊戲窗口大小,爲背景圖片的一半
        self.screen_size = (self.screen_width, self.screen_height) = (SCREENWIDTH, SCREENHEIGHT)
        self.bg_color = Gray
        self.bg_image = './images/bg2.jpg'
        # 不能播放 mp3,所以轉成 wav
        self.bg_music = './music/01.wav'
        self.gameover_image = './images/gameover.jpg'
        self.title = '飛機大戰'

        # 英雄機參數
        self.move_step = 5 # 鍵盤控制的速度
        self.hero_style = './images/me.png'

        # 敵機參數
        self.enemy_speed = 4 # 敵機飛行速度
        self.enemy_style_list = ['./images/e0.png', './images/e1.png', './images/e2.png']

        # 子彈參數
        self.bullet_style = './images/pd.png'
        self.bullet_hero_v = 10 # 英雄機子彈速度
        self.bullet_enemy_v = 8 # 敵機子彈速度


# 實例化設置對象
settings = Settings()

設置一個全局的類,其中包括一些基本的參數。

 

最後對於一個類,都需要經過初始化和實例化後纔可以發揮作用,類中使用__init__自動初始化,然後創建了一個settings對象,進行類的實例化。

 

(二)class Bullet:

# 子彈基類
class Bullet:
    def __init__(self, screen, x, y):
        self.x = x
        self.y = y
        self.screen = screen
        self.image = pygame.image.load(settings.bullet_style)
        


    def __del__(self):
        pass
    
    def bulletRect(self):
        bulletrect = self.image.get_rect()
        bulletrect.topleft = (self.x, self.y)
        return bulletrect

    def display(self):
        self.screen.blit(self.image, (self.x, self.y))

    def move(self):
        return True

 

設置子彈的基礎,玩家的子彈和敵機的子彈都要從這個子彈基類繼承。

當一個Python程序結束之後,Python解釋器會自動調用__del__()方法來釋放內存;當然我們也可以根據需要自己調用__del__()方法來刪除對象,釋放佔用的內存。因爲我們想要提高計算機的效率,暫時用不到的事情可以先不讓計算機做。

pygame.image.load()方法用來加載圖片。

用blit()方法在一個Surface對象上面繪製一個Surface對象。

最後建立move()方法,調用這個方法返回布爾值,通過布爾值來確定書否移除這個子彈。

 

 

(三)玩家、敵人子彈類

玩家子彈類和英雄子彈類都繼承子彈類,實現的功能差不多。

# 英雄子彈類
class HeroBullet(Bullet):
    def __init__(self, screen, x, y):
        super().__init__(screen, x, y)

    def move(self):
        self.y -= settings.bullet_hero_v
        # 判斷子彈是否出界
        if self.y <= -20:
            return True


# 敵機子彈類
class EnemyBullet(Bullet):
    def __init__(self, screen, x, y):
        super().__init__(screen, x, y)

    def move(self):
        self.y += settings.bullet_enemy_v
        # 判斷子彈是否出界
        if self.y >= settings.screen_height:
            return True

 

Python在類中可任意通過__init__方法實現對實例中代碼實現自動初始化,但是Python本身不會自動執行初始化操作,但是我們可能要用帶繼承類的其他方法,所以要調用super().__init__()方法來來實現繼承類的初始化。

 

 

(四)class Plane:

# 飛機基類
class Plane:
    def __init__(self, screen, style, geo):
        self.screen = screen
        self.image = pygame.image.load(style)
        self.bullet_list = []
        self.x = geo[0]
        self.y = geo[1]
        self.is_dead = False
        self.finished = False
        self.bomb_seq = ['4','4','3','3','2','2','1','1']

    def __del__(self):
        pass
    
    def planeRect(self):
        planerect = self.image.get_rect()
        planerect.topleft = (self.x, self.y)
        return planerect

    def display(self):
        for b in self.bullet_list:
            b.display()
            # 回收子彈
            if b.move():
                self.bullet_list.remove(b)

        # 爆炸效果
        if self.is_dead:
            death_x = self.x
            death_y = self.y
            death_w = self.image.get_width()
            death_h = self.image.get_height()
            try:
                bomb_image = './images/bomb'+self.bomb_seq.pop()+'.png'
                self.image = pygame.image.load(bomb_image)
            except:
                self.image = pygame.image.load('./images/bomb4.png')
                self.finished = True
            finally:
                x = death_x + (death_w - self.image.get_width())/2
                y = death_y + (death_h - self.image.get_height())/2
                self.screen.blit(self.image, (x, y))

        else:
            # 重新繪製飛機
            self.screen.blit(self.image, (self.x, self.y))

    def fire(self):
        self.bullet_list.append(Bullet(self.screen, self.x, self.y))
        print(len(self.bullet_list))

    def over(self):
        #print("Oops: plane over ...")
        #del self
        return self.finished

screen爲屏幕的Surface對象,表示一個矩形圖像。

 

 

pop()方法:用來刪除列表中最後一個元素。

>>> bomb_seq = ['4','4','3','3','2','2','1','1']
>>> bomb_seq.pop()
'1'
>>> bomb_seq
['4', '4', '3', '3', '2', '2', '1']

 

(五)玩家飛機

 

# 英雄飛機
class HeroPlane(Plane):
    def __init__(self, screen):
        # 英雄機初始位置
        geo = (200, 600)
        super().__init__(screen, settings.hero_style, geo)
        

        self.step = settings.move_step
        # 英雄機移動範圍
        self.limit_left = -(self.image.get_width()/2)+10
        self.limit_right = settings.screen_width-self.image.get_width()/2-10
        self.limit_top = 5
        self.limit_bottom = settings.screen_height-self.image.get_height()

    def fire(self):
        self.bullet_list.append(HeroBullet(self.screen, self.x+53, self.y))

    def move_left(self):
        if self.x <= self.limit_left:
            pass
        else:
            self.x -= self.step

    def move_right(self):
        if self.x >= self.limit_right:
            pass
        else:
            self.x += self.step

    def move_up(self):
        if self.y <= self.limit_top:
            pass
        else:
            self.y -= self.step

    def move_down(self):
        if self.y >= self.limit_bottom:
            pass
        else:
            self.y += self.step

其中玩家移動的範圍下左右都是由screen_size決定的,但是上方我們直接設置一個值就好了,這裏我們設置爲5:self.limit_top = 5,意思是玩家飛機最高能飛到5個像素的位置。

然後是英雄飛機左右上下的移動,先判斷是否超出邊界,如果在邊界範圍內的話,就進行“移動”,也就是更改相應方向的座標。

 

(六)敵人飛機

 

# 敵機
class EnemyPlane(Plane):
    def __init__(self, screen):
        geo = (random.choice(range(408)), -75)
        enemy_style = settings.enemy_style_list[random.choice(range(3))]
        super().__init__(screen, enemy_style, geo)

        self.pipe_x = self.image.get_width()/2-1 # 1 for the width of bullet
        self.pipe_y = self.image.get_height()
        #self.planeRect.topleft = geo
    #是否要刪除敵機,返回布爾值
    def move(self, hero):
        self.y += settings.enemy_speed
        if self.y > settings.screen_height:
            return True

        # 飛機的碰撞檢測
        #使用位置檢測碰撞:
        #if self.x > hero.x-50 and self.x < hero.x+50 and self.y > hero.y-40 and self.y < hero.y+40:
        #使用Rect對象的colliderect()方法:
        if self.planeRect().colliderect(hero.planeRect()):
            
            self.is_dead = True
            hero.is_dead = True

        # 看看我中彈了沒
        for bo in hero.bullet_list:
            
            if self.planeRect().colliderect(bo.bulletRect()):
            #if bo.x > self.x+12 and bo.x < self.x+92 and bo.y < self.y+60 and bo.y > self.y:
                hero.bullet_list.remove(bo)
                # 爆炸
                self.is_dead = True

        # 看看英雄機中彈了沒
        for bo in self.bullet_list:
            if hero.planeRect().colliderect(bo.bulletRect()):
            #if bo.x > hero.x+25 and bo.x < hero.x+75 and bo.y > hero.y+5 and bo.y < hero.y+50:
                self.bullet_list.remove(bo)
                hero.is_dead = True

    def fire(self):
        self.bullet_list.append(EnemyBullet(self.screen, self.x+self.pipe_x, self.y+self.pipe_y))

 

(七)設置方法

這裏寫一個事件檢測和繪製文字的函數,把功能放到這個函數裏面,選喲這兩個功能的時候就調用相應的函數就可以了。

def check_event(hero, usedbullet):

        #Key event capture and key_control

    which = pygame.key.get_pressed()
    

    if which[K_SPACE]:
        hero.fire()
        usedbullet += 1
        print("Space...")

    if which[K_LEFT] or which[K_a]:
        hero.move_left()
        print("Left...")
    elif which[K_RIGHT] or which[K_d]:
        hero.move_right()
        print("Right...")
    elif which[K_UP] or which[K_w]:
        hero.move_up()
        print("Up...")
    elif which[K_DOWN] or which[K_s]:
        hero.move_down()
        print("Down...")      
    
    for event in pygame.event.get():
        
             
        if event.type == MOUSEMOTION:
            hero.x = event.pos[0]
            hero.y = event.pos[1]
            
        if event.type == QUIT:
            print("exit...")
            sys.exit()
     
    return usedbullet
        
        
#draw score
def drawText(text, font, screen, x, y):
    TEXTCOLOR = (0, 0, 0)
    textobj = font.render(text, 1, TEXTCOLOR)
    textrect = textobj.get_rect()
    textrect.topleft = (x, y)
    screen.blit(textobj, textrect)

事件檢測這裏使用的是pygame.key.get_pressed()這個方法,在前面的遊戲中有使用pygame.event.get()這個方法,二者之間有一些不同,在這個遊戲裏面,用這兩種事件調用的方法遊戲的操作會有顯著的區別。

大家可以在上面代碼中修改一下,在遊戲操作裏面體驗一下有神麼不同。

看看官網中的介紹:

get()方法中:

This will get all the messages and remove them from the queue. If a type or sequence of types is given only those messages will be removed from the queue.

get_pressed()方法中:

Returns a sequence of boolean values representing the state of every key on the keyboard. Use the key constant values to index the array. A True value means the that button is pressed.

 

       """ 
        event = pygame.event.get()

        if event.type == KEYDOWN:
            
            if event.key == K_SPACE:
                hero.fire()
                print("Space...")
                
            if event.key == K_LEFT or K_a:
                hero.move_left()
                
            if event.key == K_RIGHT or K_d:
                hero.move_right()
                
            if event.key == K_UP or K_w:
                hero.move_up()
                
            if event.key == K_DOWN or K_s:
                hero.move_down()
                """

(八)主函數

 

def main():

    # 初始化 Pygame
    pygame.init()

    # 創建一個遊戲窗口
    screen = pygame.display.set_mode(settings.screen_size, 0, 0)

    # 設置窗口標題
    pygame.display.set_caption(settings.title)

    # 在窗口中加載遊戲背景
    background = pygame.image.load(settings.bg_image)
    
    #設置積分器
    font = pygame.font.SysFont(None, 48)

    # 背景音樂
    pygame.mixer.init()
    pygame.mixer.music.load(settings.bg_music)
    #pygame.mixer.music.play()

    # 創建英雄機
    hero = HeroPlane(screen)

    play = True
    enemylist = []
    bg_y = -(settings.screen_height)
    
    usedbullet = 0                    
    score = 0
    
    while True:
        

        #檢查退出
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit() 
                sys.exit()
        if not pygame.mixer.music.get_busy():
            print("play music")
            pygame.mixer.music.play()

        # 從座標(0, -768)開始繪圖,所以看到的是背景圖片的下半部
        screen.blit(background, (0, bg_y))
        bg_y += 2
        if bg_y >= 0:
            bg_y = -(settings.screen_height)

        if play:
            drawText('Score:{}'.format(score),font,screen,256,256)
            drawText('Used Bullet:{}'.format(usedbullet),font,screen,256,300)
            # 繪製英雄機
            hero.display()

            # 隨機繪製敵機
            if random.choice(range(50)) == 10:
                enemylist.append(EnemyPlane(screen))

            # 遍歷敵機並繪製移動
            for em in enemylist:
                em.display()

                # 敵機隨機發炮彈
                if random.choice(range(50)) == 10:
                    em.fire()

                # 判斷敵機是否出界
                if em.move(hero):
                    enemylist.remove(em)
                # 判斷敵機是否炸燬
                if em.over():
                    score += 1
                    enemylist.remove(em)

            # 英雄機炸燬,遊戲結束
            if hero.over():
                play = False

            #pygame.display.flip()
            pygame.display.update()

        else:
            gameover = pygame.image.load(settings.gameover_image)
            screen.blit(gameover, ((settings.screen_width-gameover.get_width())/2,
                        (settings.screen_height-gameover.get_height())/2))
            pygame.display.update()
            #print("Game Over")
            #continue

        # 檢查按鍵事件
        usedbullet = check_event(hero,usedbullet)

        time.sleep(0.04)



# 判斷是否爲主運行程序,是則調用 main()
if __name__ == '__main__':
    main()

對於全局變量報錯:UnboundLocalError: local variable ‘usedbullet’ referenced before assignment的解釋:

在程序中設置的 sum 屬於全局變量,而在函數中沒有 sum 的定義,根據python訪問局部變量和全局變量的規則:當搜索一個變量的時候,python先從局部作用域開始搜索,如果在局部作用域沒有找到那個變量,那樣python就在全局變量中找這個變量,如果找不到拋出異常(NAMEERROR或者Unbound-LocalError,這取決於python版本。)

如果內部函數有引用外部函數的同名變量或者全局變量,並且對這個變量有修改.那麼python會認爲它是一個局部變量,又因爲函數中沒有sum的定義和賦值,所以報錯。

其中if __name__ == '__main__':是隻作爲腳本執行時,執行main()函數,如果作爲包導入其他腳本中時,則不執行。

 

這個遊戲修改了很多天,對於初學者,需要理解面向對象思維的編程特點。當然如果按照之前的面向過程的思路來寫的話,代碼可能會短一些,但是修改起來就會有很大的問題,這也是面向對象和麪型過程二者之間的區別,我會在下一個專欄中介紹這些,期待自己學習的進步吧!

這個飛機大戰的遊戲還是未完成的狀態,我們可以在以上代碼中進行一些修改,爲這個遊戲增加一些功能,讓這個遊戲變得更好玩,當然,在我們增加功能的過程中,也是對Python編程和pygame模塊的更深入的理解。

 

 

參考:

  1. http://c.biancheng.net/view/2371.html
  2. https://www.runoob.com/w3cnote/python-unboundlocalerror.html
  3. https://gitee.com/luhuadong/Python_Learning/repository/archive/master.zip

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章