Python入門項(xiàng)目:外星人入侵

前言

作為一個(gè)編程小白選手,經(jīng)過一段時(shí)間的基礎(chǔ)語法學(xué)習(xí),終于迎來了首個(gè)項(xiàng)目學(xué)習(xí)眨唬,從一步步的模仿到理解到每個(gè)步驟的思路想法会前,每個(gè)類之間的聯(lián)系與構(gòu)造,還有些編程中的一些小細(xì)節(jié)匾竿,如何養(yǎng)成一個(gè)編程的好習(xí)慣瓦宜。

分析

外星人入侵作為一個(gè)入門小游戲項(xiàng)目,整體來說思路是比較簡單搂橙,代碼行也不多歉提,總計(jì)就是500+行,除去一些縮進(jìn)空行区转,就沒有多少了。當(dāng)我們?nèi)プ鲆粋€(gè)項(xiàng)目的時(shí)候版扩,無論是難的還是簡單的废离,都是需要進(jìn)行一個(gè)分析,這樣我們實(shí)際去寫的時(shí)候礁芦,思路就會清晰很多蜻韭,把一個(gè)項(xiàng)目的功能拆分出來,再各個(gè)實(shí)現(xiàn)柿扣,最后串聯(lián)起來肖方。
言歸正傳,這個(gè)項(xiàng)目主要分為9個(gè)模塊未状,9個(gè)模塊及每個(gè)模塊的作用如下:

  • alien.py →class_Alien 主要是用來放繪制外星人的圖像及位置信息
  • bullet.py→class_Bullet 主要是用來放置繪制飛船發(fā)射子彈圖像及位置信息
  • button.py→class_Button 主要是用來放置繪制游戲開始按鈕的圖像及位置信息
  • game_stats.py→ class Game_stats 主要是游戲中的數(shù)據(jù)參數(shù)俯画,如得分,游戲等級等等
  • scoreboard.py→ class Scoreboard 主要是用來放置游戲中各個(gè)計(jì)分板的圖像繪制和位置信息
  • settings.py→class Settings 主要是用來放置游戲中初始化參數(shù)司草,像屏幕大小顏色等等
  • ship.py→class Ship 主要用來放置繪制飛船的圖像及位置信息
  • alien_invasion.py 主要是用來放置運(yùn)行游戲的主方法
  • game_functions.py 主要是用來放置游戲功能的實(shí)現(xiàn)艰垂,如屏幕的圖像更新,飛船的移動埋虹,飛船如何發(fā)射子彈等等猜憎,基本功能代碼都會集中在這個(gè)包里面
    image.png
步驟如下:

創(chuàng)建游戲窗口→添加飛船圖像→控制飛船移動→添加子彈圖像及移動→
添加一個(gè)外星人圖像→添加一群外星人圖像及移動→檢測外星人圖像與子彈碰撞,與飛船碰撞和碰到屏幕底端的情況及反應(yīng)→添加游戲開始按鈕圖像及反應(yīng)→添加計(jì)分板圖像:包括玩家得分搔课,歷史最高得分胰柑,難度級別顯示,玩家剩余飛船數(shù)量

實(shí)現(xiàn)

創(chuàng)建游戲窗口

首先來創(chuàng)建游戲窗口爬泥,創(chuàng)建一個(gè)alien_invasion.py的包柬讨,導(dǎo)入pygame庫(這個(gè)項(xiàng)目是基于pygame的庫下完成的),創(chuàng)建一個(gè)def run_game()的主方法急灭,pygame.init()初始化背景參數(shù)姐浮,創(chuàng)建一個(gè)screen的對象pygame.display.set_mode()函數(shù)傳入我們設(shè)置好的寬高參數(shù)就可以創(chuàng)建窗口,用(1200(寬),650(高))一個(gè)元組的實(shí)參傳入(具體參數(shù)可以根據(jù)自身屏幕大小設(shè)置)葬馋,再用函數(shù)pygame.display.set_caption()傳入str可以命名窗口卖鲤,將顯示在窗口的左上角肾扰。我們創(chuàng)建的screen對象是一個(gè)surface(面板),還有接下來我們創(chuàng)建的飛船蛋逾,子彈集晚,都是一個(gè)surface(面板)set_mode函數(shù)返回的就是一個(gè)surface。
接下來就是游戲需要的主循環(huán)区匣,用來不斷更新的屏幕的圖像和玩家行為偷拔,直至玩家退出游戲,我們用while True來循環(huán)亏钩,并在里面設(shè)置一個(gè)檢測pygame.QUIT的事件跳出循環(huán)莲绰,避免造成死循環(huán)。用函數(shù)pygame.event.get()可以得到玩家鼠標(biāo)和按鍵響應(yīng)事件姑丑,再用一個(gè)for in循環(huán)得到每個(gè)事件蛤签,并進(jìn)行判斷是否有pygame.QUIT事件,如果有就用sys.exit()函數(shù)來退出游戲跳出循環(huán)(這個(gè)時(shí)候我們就需要導(dǎo)入sys栅哀,在代碼最上面import sys)最后再用pygame.display.flip()繪制的圖像顯示出來震肮,這個(gè)是放于run_game()最后的地方。代碼如下:


image.png

此時(shí)運(yùn)行run_game()留拾,我們將得到一個(gè)黑色的窗口:


image.png

接下來我們給窗口上色戳晌,用的是RGB值,用紅色痴柔,綠色沦偎,藍(lán)色組成,每個(gè)值取值范圍為0到255竞帽,創(chuàng)建一個(gè)screen_color賦值(230扛施,230,230),我們需要不斷更新這個(gè)顏色屹篓,所以放在while循環(huán)里疙渣,用fill()函數(shù),傳入我們的screen_color實(shí)參堆巧,在我們創(chuàng)建的窗口屏幕screen上妄荔,所以代碼如下:
def run_game():
    pygame.init()
    screen = pygame.display.set_mode((1200,650))
    pygame.display.set_caption("Alien Invasion")
    screen_color = (230,230,230)
    
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
        screen.fill(screen_color)
        pygame.display.flip()

運(yùn)行得到畫面:

image.png

建議可以熟悉下pycharm的快捷鍵,這樣會更快的提高谍肤,例如當(dāng)你不熟悉一個(gè)函數(shù)的使用時(shí)候啦租,可以選中之后按Ctrl + B或者Ctrl + 鼠標(biāo)點(diǎn)擊函數(shù)可以直接跳轉(zhuǎn)到函數(shù)的聲明,會幫助你更加理解函數(shù)的使用荒揣,變量也是可以這樣操作篷角,當(dāng)你看完之后可以按Alt + 左鍵跳回就非常放便
快捷鍵使用

setting模塊(存放游戲設(shè)置參數(shù))

接下來可以創(chuàng)建一個(gè)setting.py的模塊,里面創(chuàng)建一個(gè)Settting類系任,用于存放游戲參數(shù)恳蹲,這樣以來就方便引用虐块,而且當(dāng)你需要修改的時(shí)候不需要全部都去修改,而是只在Setting的類中修改即可嘉蕾,然后回到alien_invasion.py中導(dǎo)入Setting創(chuàng)建一個(gè)Setting的對象ai_settings贺奠,之后只需用對象.變量名即可引用,整理結(jié)果如下:

class Setting():
    def __init__(self):
        #初始化游戲的設(shè)置
        #屏幕參數(shù)設(shè)置
        self.screen_width = 1200
        self.screen_height = 650
        self.screen_color = (230,230,230)
import pygame
import sys
from setting import Setting

def run_game():
    pygame.init()
    ai_settings = Setting()
    screen = pygame.display.set_mode(
(ai_settings.screen_width,ai_settings.button_height))
    pygame.display.set_caption("Alien Invasion")

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
        screen.fill(ai_settings.screen_color)
        pygame.display.flip()

run_game()
創(chuàng)建飛船

接下來我們就要創(chuàng)建飛船了错忱,使用位圖.bmp文件儡率,需要在項(xiàng)目文件夾中創(chuàng)建一個(gè)Images的文件夾,將圖片放入文件夾中以清,再創(chuàng)建一個(gè)ship.py的模塊儿普,里面有一個(gè)class Ship類并進(jìn)行初始化,def init(self,screen)這里需要傳入我們創(chuàng)建的窗口screen玖媚,因?yàn)槲覀兊娘w船是出現(xiàn)在這個(gè)窗口屏幕上的箕肃,然后需要讀取我們飛船的圖片,創(chuàng)建一個(gè)self.image接收今魔,用pygame.image.loda(Images’/ship.bmp)前面為文件夾名,后面會文件名障贸,再用get_rect()函數(shù)來得到這個(gè)圖片的矩形rect對象错森,這個(gè)rect對象會有一個(gè)坐標(biāo)位置信息由四個(gè)參數(shù)組成(left,top篮洁,width涩维,height),width和height又表示rect矩形的寬和高

image.png

圖像都是從原點(diǎn)(0,0)創(chuàng)建的

  • top和left可以理解成圖像距離原點(diǎn)的坐標(biāo)
  • bottom是rect矩形下邊緣距離x軸的距離袁波,也可理解成y坐標(biāo)
  • width就是rect圖像的寬度瓦阐,height是rect圖像的高度
    *center是圖像的中心,然后分出centerx(中心在x軸上的坐標(biāo))
    centery(中心在y軸上的坐標(biāo))
  • right 可以理解成rect矩形圖像右邊緣x軸坐標(biāo)

所以我們導(dǎo)入ship.bmp篷牌,創(chuàng)建ship_rect接受用函數(shù)get_rect()得到飛船rect矩形圖像的坐標(biāo)位置信息睡蟋。要將飛船放于screen屏幕的底部中間,所以要將飛船的bottom等于屏幕的bottom枷颊,在將飛船的centerx等于screen屏幕的centerx戳杀,之后在創(chuàng)建一個(gè)繪制飛船圖像的方法def blitme(),方法里面用blit()在屏幕上screen繪制飛船夭苗,傳入飛船的圖像self.image和飛船圖像的self.rect信卡,代碼如下:

class Ship():
    def __init__(self,screen):
        self.screen = screen

        #飛船參數(shù)
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()

        #將飛船放在屏幕底部中間
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
def blitme(self):
        #繪制飛船
        self.screen.blit(self.image,self.rect)

然后在alien_invasion中導(dǎo)入Ship的類,創(chuàng)建一個(gè)Ship類對象题造,需要傳入screen參數(shù)傍菇,最后在循環(huán)中使用ship.blitme()方法繪制飛船圖像

ship == Ship(screen)
whlie True:
...
ship.blitme() 
控制飛船

接下來就是控制飛船移動的,還是在遍歷event.get()里用if語句判斷界赔,如果檢測到事件是按下鍵盤事件丢习,然后在用if語句判斷牵触,按下鍵盤事件的按鈕是不是對應(yīng)的方向鍵,根據(jù)上面的坐標(biāo)圖泛领,向左鍵則是ship.rect.centerx -=1荒吏,向右則是ship.rect.centerx += 1;當(dāng)玩家持續(xù)按下左右鍵的時(shí)候渊鞋,就需要判斷按下按鍵事件和松開按鍵事件绰更,先在Ship類里面的init方法里添加兩個(gè)變量self.moving_right 和self.moving_left并賦值False,在創(chuàng)建一個(gè)方法def update()用if語句判斷如果為True的反應(yīng)

    def update():
        if moving_right:
            self.rect.centerx += 1
        if moving_left:
            self.rect.centerx -= 1

再回到for循環(huán)判斷锡宋,按下和松開的響應(yīng):

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = True
                elif event.key ==pygame.K_LEFT:
                    ship.moving_left = True
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = False
                elif event.key == pygame.K_LEFT:
                    ship.moving_left = False

接下來我們需要限制飛船的運(yùn)行范圍儡湾,從上面的坐標(biāo)圖來看,我們可以判斷飛船圖像的right如果小于screen窗口的right执俩,那就是還可以繼續(xù)向右移動徐钠,如果飛船的left是大于0的話,那就是還沒有到屏幕坐邊緣役首,還可以繼續(xù)向左移動尝丐,所以我們可以在ship.update里面加多一個(gè)判斷條件,用and連接衡奥,只有同時(shí)滿足這兩個(gè)條件爹袁,飛船才能繼續(xù)移動,因?yàn)橐玫斤w船的rect和screen的rect矮固,所以需要在update方法里面?zhèn)魅雜hip和screen作為參數(shù)失息,代碼如下:

def update(self):
      if moving_right and self.rect.right < self.screen_rect.right:
          self.rect.centerx += 1
      if moving_left and self.rect.left > 0:
          self.rect.centerx -= 1

如果覺得飛船移動的太慢的話,可以設(shè)置一個(gè)飛船速度的變量档址,建議賦值浮點(diǎn)值盹兢,這樣可以更細(xì)致的控制飛船速度,所以我們把+1-1換成+-我們嘎剛剛設(shè)置的飛船速度變量守伸,但是rect是整數(shù)绎秒,整數(shù)不能和浮點(diǎn)值加減,所以先把rect.centerx轉(zhuǎn)換為浮點(diǎn)值float賦給變量center含友,最后加減完之后再賦值給center賦值給rect.centerx

    def __init__(self):
        ...
        self.ship_centerx = float(self.rect.centerx)
    def ship_update(self):
        #更新飛船位置
        if self.ship_moving_left and self.rect.left > 0:
            self.ship_centerx -= self.ai_settings.ship_speed_factor
        if self.ship_moving_right and self.rect.right < self.screen_rect.right:
            self.ship_centerx += self.ai_settings.ship_speed_factor
        self.rect.centerx = self.ship_centerx

注意這里飛船速度的變量是在Setting里創(chuàng)建的替裆,所以我們在Ship里面要導(dǎo)入Setting類,并在init里面?zhèn)魅隺i_settings對象,ship對象里面我們也要傳入相應(yīng)的實(shí)參ai_settings

class Ship():
    def __init__(self,ai_settings,screen):       
        ...
        self.ai_settings = ai_settings
ship = Ship(ai_settings窘问,screen)
game_functions的作用(整理代碼辆童,讓代碼更容易管理和解讀)

while True里的循環(huán)現(xiàn)在代碼比較多,也為了更好理解代碼惠赫,現(xiàn)在創(chuàng)建一個(gè)game_functions的模塊把鉴,用于存放功能代碼,在里面創(chuàng)建def check_events(按鍵和鼠標(biāo)響應(yīng)事件)和screen_update(窗口更新,把需要在窗口上更新的圖像歸在里面)庭砍,check_events里面還可以在分出響應(yīng)按下和松開的兩個(gè)方法场晶。最后在alien_invasion.py中導(dǎo)入game_function(用import game_function as gs 導(dǎo)入,并簡化為gs怠缸,通過gs來調(diào)用就可以了)如下:

import sys
import pygame
def check_event(ai_settings,screen,ship):
    #響應(yīng)按鍵和鼠標(biāo)事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown(event,ai_settings,screen,ship)
        elif event.type == pygame.KEYUP:
            check_keyup(event,ship)

def check_keydown(event,ai_settings,screen,ship):
    #響應(yīng)按下
    if event.key == pygame.K_RIGHT:
        ship.ship_moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.ship_moving_left = True

def check_keyup(event,ship):
    #響應(yīng)松開
    if event.key == pygame.K_RIGHT:
        ship.ship_moving_right = False
    if event.key == pygame.K_LEFT:
        ship.ship_moving_left = False
def screen_update(ai_settings,ship,screen):
    #更新屏幕圖像
    screen.fill(ai_settings.screen_color)
    ship.blitme()

    pygame.display.flip()
import game_functions as gs
...
    while True:
        #監(jiān)聽鼠標(biāo)和按鍵事件
        gs.check_event(ai_settings,screen,ship)
        ship.ship_update()
        gs.screen_update(ai_settings,ship,screen)
子彈的創(chuàng)建

接下來創(chuàng)建飛船需要的子彈诗轻,在bullet模塊里創(chuàng)建,過程跟上面的飛船差不多揭北,但是需要通過繼承Sprite類使用精靈將游戲相關(guān)的元素編組并操作編組中的所有元素扳炬,同時(shí)子彈是沒有bmp位圖的,所有需要用pygame.Rect函數(shù)來繪制搔体,需要傳輸x恨樟,y軸上的坐標(biāo)和子彈的寬高,同時(shí)在Settging里面創(chuàng)建子彈需要的寬高和顏色疚俱,子彈的速度劝术,我們在原點(diǎn)創(chuàng)建,之后在把子彈設(shè)置子彈飛船的中心呆奕,這樣看起來就是子彈從飛船中射出养晋,所以子彈的矩形圖像就是:

from pygame.sprite import Sprite
class Bullet(Sprite):
    def __init__(self,ai_settings,screen,ship):
        '''
        python2.7中需要super(self,Bullet).init()這種寫法,python3也可以這樣寫梁钾,
        但是也可省略匙握,用下面這種寫法
        '''
        super().init()
        self.rect = pygame.Rect(0,0,ai_setting.bullet_width,ai_setting.bullet_height)

子彈的更新方法,因?yàn)槭菑娘w船射出陈轿,所以是在屏幕從下往上的,所以self.rect.y -= 子彈速度秦忿,可以借鑒飛船移動速度用float值麦射。
子彈的繪制用pygame.draw.rect(Surface,color灯谣,rect)或者Surface.fill(color潜秋,rect)都可以
在alien_invasion中創(chuàng)建一個(gè)子彈編組bullets = Group()用于存放每一個(gè)創(chuàng)建的子彈。在while循環(huán)里面通過編組bullets調(diào)用update方法可以對編組中每個(gè)元素調(diào)用update方法胎许。

子彈的射擊

子彈的射擊同樣通過按鍵響應(yīng)事件判斷峻呛,如果玩家按下的是空格鍵,就創(chuàng)建一個(gè)子彈對象辜窑,并將子彈加入子彈編組里面钩述,在通過遍歷子彈編組在屏幕上繪制出每個(gè)子彈,bullets.sprites()可以返回一個(gè)列表穆碎。

def screen_update(ai_settings,screen,ship,bullets):
    ...
    for bullet in bullets.sprites():
        bullet.draw_bullet()
子彈管理

會發(fā)現(xiàn)發(fā)射的子彈雖然到達(dá)了屏幕頂端之后消失牙勘,其實(shí)并沒有消失,子彈位置還在不斷往Y軸負(fù)數(shù)區(qū)域更新,這樣會到導(dǎo)致子彈越來越多方面,可以用過bullets.copy()復(fù)制一個(gè)bullets放钦,遍歷判斷子彈的位置,如果子彈的bottom位置超過屏幕也就是0恭金,那就使用Group.remove()函數(shù)操禀,將超過屏幕的子彈刪除,同時(shí)我們限制子彈的數(shù)量横腿,通過len(bullets)得到子彈編組的數(shù)量颓屑,用if語句判斷如果小于我們設(shè)定的子彈數(shù)量就創(chuàng)建子彈

外星人創(chuàng)建

外星人的創(chuàng)建就和飛船的創(chuàng)建比較相似,因?yàn)槎际怯衎mp位圖的蔑水,就不需要我們自己創(chuàng)建邢锯,只需要將外星人的圖片讀取進(jìn)來就可以了,唯一不同的就是外星人是一群的搀别,所以我們要借鑒創(chuàng)建子彈的方法丹擎,繼承Spirte類,創(chuàng)建一個(gè)外星人列表歇父,同時(shí)要計(jì)算窗口可以容納多少外星人蒂培,在使用for循環(huán)嵌套創(chuàng)建外星人群,最后在screen_update中用編組調(diào)用draw(surface)方法繪制
每個(gè)外星人最初都是從(0榜苫,0)的地方創(chuàng)建护戳,所以我們將外星人的寬度設(shè)置成外星人的左邊距,將外星人的高度設(shè)置為外星人的上邊距
計(jì)算屏幕可以容納多少個(gè)外星人垂睬,每行容納的外星人空間可以通過窗口的寬度 - 兩個(gè)外星人寬度(預(yù)設(shè)屏幕兩端的空余空間)媳荒,將其設(shè)置為available_space_x
同時(shí)兩個(gè)外星人之間需要有一定空間,所以設(shè)定一個(gè)外星人實(shí)際需要的空間是外星人寬度的兩倍驹饺,所以得出每行可以容納的外星人數(shù)量為:
number_aliens_x = int(available_space_x/(2*alien_width))
我們在game_functions.py創(chuàng)建一個(gè)alien_fleet()方法钳枕,用于創(chuàng)建外星人群:

def alien_fleet():
    alien = Alien(ai_settings,screen)
    alien_width = alien.rect.width
    available_space_x = ai_settings.screen_width - 2*alien_width
    number_aliens_x = int(available_space_x/(2*alien_width))
    for alien_number in range(number_aliens_x):
        alien = Alien(ai_settings,screen)
        alien.x = alien.width  + 2 *alien_width *alien_number
        alien.rect.x = alien.x
        aliens.add(alien)

接下來計(jì)算屏幕可以放下幾行外星人,存放外星人的空間 = 窗口高度 - 第一行外星人的上邊距 - 外星人之間的間隔 - 留給玩家反應(yīng)的距離 - 飛船的距離赏壹,將前面三種參數(shù)都設(shè)置成外星人高度鱼炒,即為3*外星人高度 - 飛船高度,屏幕存放外星人的空間為: available_space_y = ai_settings.screen_height - 3 * alien_height - ship._height
可以存放的行數(shù)為:number_rows = int(available_space_y / (2 * alien_height)),結(jié)合起來代碼:

def alien_fleet():
...
    for row_number in range(number_rows):
        for alien_number in range((number_aliens_x):
            ...
            alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
            aliens.add(alien)

最后整理下代碼蝌借,將其整理四個(gè)方法昔瞧,def get_number_aliens_x();
def get_number_rows()菩佑;def create_alien()自晰;def create_fleet(),分別完成獲取外星人每行存放多少個(gè)外星人擎鸠,獲取可以存放幾行外星人缀磕,for循環(huán)里面創(chuàng)造外星人及創(chuàng)建外星人群

外星人群的移動

設(shè)置外星人群的平行移動速度alien_speed_factor = 1,下降速度alien_drop_speed = 10,alien_direction = 1為左右移動(1為向右移動,-1為向左移動)袜蚕,通過外星人群的left和right屬性判斷是否有到達(dá)窗口邊緣糟把,如果有,那么外星人群alien.rect.y += 下降速度(根據(jù)坐標(biāo)圖可以看出下降y軸坐標(biāo)是加大的)牲剃,在alien類創(chuàng)建一個(gè)check_edges()方法和一個(gè)update方法

def check_edges(self):
    if self.rect.right >= screen.rect.right:
        return True
    if self.rect.left <= 0:
        return True

def updates(self):
     self.x += self.settings.alien_speed_factor * self.settings.alien_fleet_direction
     self.rect.x = self.x

在game_functions.py中創(chuàng)建check_fleet_edges()方法遣疯,根據(jù)check_edges()返回的結(jié)果,在進(jìn)行一次if判斷凿傅,如果為True缠犀,就for循環(huán)遍歷一次aliens.sprites列表聪舒,將每個(gè)外星人都下移辨液,并將alien_direciton *= -1,這樣外星人的左右方向就會改變

檢測子彈和外星人接觸

在game_functions.py里按創(chuàng)建一個(gè)def check_alien_bullet_collisions()方法,使用函數(shù)pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb, collided=None)燎悍,接受子彈編組和外星人編組宏怔,和兩個(gè)True,是檢測兩個(gè)編組中的精靈的rect是有否重疊臊诊,兩個(gè)True是刪除碰撞的兩個(gè)精靈思劳。當(dāng)所有外星人都被打完,就可以用len(aliens)判斷妨猩,如果為等于0,就使用bullets.empy()函數(shù)清空編組然后調(diào)用create_fleet()創(chuàng)造新的外星人群

def bullet_update(bullets,ai_settings,screen,ship,aliens,stats,sb):
    #更新子彈位置
    bullets.update()

    #刪除子彈
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

    check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb)

def check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb):
    #檢查子彈是否有擊中外星人
    #如果有擊中秽褒,就刪除對應(yīng)的子彈和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

接下來可以檢測外星人與飛船碰撞的情況壶硅,函數(shù)pygame.sprite.spritecolideany(sprite, group, collided=None),接受一個(gè)精靈和一個(gè)編組,檢測是否有重疊销斟,并且返回一個(gè)布爾值庐椒,根據(jù)這個(gè)布爾值來進(jìn)行if判斷,飛船數(shù)量-1蚂踊,子彈清空约谈,外星人清空,重新創(chuàng)建外星人群,同時(shí)將飛船重新放回屏幕下端中央棱诱,并將這些功能整理成ship_hit函數(shù)泼橘,飛船的數(shù)量,需要創(chuàng)建一個(gè)專門管理游戲數(shù)據(jù)的類迈勋,命名game_stats.py→ class Game_stats炬灭。

class Setting():
    def __init__(self):
        ...
        self.ship_allow = 3
class Game_stats():
    def __init__(self,ai_settings):
    #初始化游戲數(shù)據(jù)統(tǒng)計(jì)
        self.ai_settings  = ai_settings
        self.ships_break = self.ai_settings.ship_allow
from time import Sleep
def aliens_update(ai_settings,screen,ship,stats,aliens,sb,bullets):
    #檢測是否有外星人碰到邊緣,并更新外星人位置
    check_fleet_edges(ai_settings,aliens)
    check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets)
    aliens.update()

    if pygame.sprite.spritecollideany(ship,aliens):
        #當(dāng)外星人和飛船碰撞反應(yīng)
        ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets)

def ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets):
    if stats.ships_break > 0:
        #減少一只飛船的數(shù)量
        stats.ships_break -= 1

        #更新飛船記分牌
        sb.prep_ships()

        #清空飛船列表和子彈列表
        aliens.empty()
        bullets.empty()

        #重新創(chuàng)造一組外星人练链,并把飛船放到屏幕下端中央
        creat_fleet(ai_settings,screen,ship,aliens)
        ship.ship_centerx = ship.screen_rect.centerx

        #停止0.5秒凿滤,以便讓玩家知道飛船撞到鹏控,或者外星人碰到底端
        sleep(0.5)

遍歷aliens編組,如果有外星人到達(dá)屏幕底端鼻吮,也需要調(diào)用ship_hit方法

def check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets):
    #檢查外星人是否有碰到底端及其反應(yīng)
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings,screen,ship,stats,aliens,bullets)
            break
def aliens_update(ai_settings,screen,ship,stats,aliens,sb,bullets):
     ...
    check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets)
游戲開始的按鈕

首先在games_stats.py中設(shè)置游戲狀態(tài)為True,可以ship_hit中加一個(gè)if较鼓,else的判斷椎木,如果飛船數(shù)量大于0就執(zhí)行剛剛的代碼,否則就游戲狀態(tài)設(shè)置成為False笨腥,再回到alien_invasion.py中的while循環(huán)開頭加上if判斷拓哺,如果游戲狀態(tài)為True才執(zhí)行。
接下來就需要來繪制play按鈕了脖母,按鈕的繪制與子彈一樣士鸥,只不過要加上字,就需要用到pygame提供的將文字熏染成圖像的函數(shù)谆级,首先用函數(shù)pygame.font.SysFont(None,48)用一個(gè)變量接收,None位置傳入的是字體烤礁,None使用默認(rèn)字體,48為大小肥照。再用變量調(diào)用render(self, text, antialias, color, background=None)脚仔,text需要的文字,antlalia為抗鋸齒選項(xiàng)舆绎,接收True和False鲤脏,讓文字更平滑,后面兩個(gè)參數(shù)就是文字顏色和背景顏色吕朵。并且讓文字的放于按鈕的中間猎醇,最后創(chuàng)建draw_button方法,分別把背景和文字的圖像繪制出來即可努溃。

class Button():
    def __init__(self,ai_settings,screen,msg):
        #初始化按鈕參數(shù)
        self.screen = screen
        self.ai_settings = ai_settings
        self.screen_rect = screen.get_rect()

        #設(shè)置按鈕顏色及文本顏色硫嘶、文本字體
        self.color = ai_settings.button_color
        self.msg_color = ai_settings.text_color
        self.font = pygame.font.SysFont(None,48)

        #設(shè)置按鈕位置
        self.rect = pygame.Rect(0,0,ai_settings.button_width,ai_settings.button_height)
        self.rect.center = self.screen_rect.center

        #設(shè)置文本位置
        self.msg_image = self.font.render(msg,True,self.msg_color,self.color)
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        #繪制按鈕
        self.screen.fill(self.color,self.rect)
        self.screen.blit(self.msg_image,self.msg_image_rect)
檢測鼠標(biāo)點(diǎn)擊play按鈕

先把游戲狀態(tài)設(shè)置為False,用if語句判斷梧税,如果為False沦疾,就繪制按鈕称近。
在檢測按鍵鼠標(biāo)事件的if判斷加上的鼠標(biāo)判斷。pygame.mouse.get_pos()函數(shù)可以檢測鼠標(biāo)點(diǎn)擊哮塞,返回一個(gè)(x,y)的坐標(biāo)刨秆。用函數(shù)
collidepoint(self,x,y),檢測是否在rect矩形內(nèi)有碰撞彻桃,傳入?yún)?shù)x,y坐標(biāo)坛善,返回結(jié)果是布爾值。如果為Ture邻眷,就讓游戲狀態(tài)為True眠屎。
當(dāng)結(jié)束之后,play的按鈕會出現(xiàn)肆饶,這是會發(fā)現(xiàn)的情況是繼續(xù)游戲改衩,而不是重新開始,如果我們還需要加上一下重置游戲的設(shè)置驯镊,除了讓參數(shù)回到初始化葫督,還需要將子彈清空,外星人清空板惑,并創(chuàng)建新的外星人群橄镜,并讓飛船回到原來的位置。

def check_play_button(ai_settings,screen,ship,bullets,aliens,stats,sb,play_button,mouse_x,mouse_y):
     button_clickde = play_button.rect.collidepoint(mouse_x,mouse_y)
     #檢測鼠標(biāo)點(diǎn)擊行為是否在paly按鈕區(qū)域及反應(yīng)
     if button_clickde and not stats.game_state:
        ai_settings.game_initialization()
        stats.game_state = True
        stats.reset_stats()
        aliens.empty()
        bullets.empty()

        #創(chuàng)造外星人群并將飛船放于屏幕下端中央
        creat_fleet(ai_settings, screen, ship, aliens)
        ship.ship_centerx = ship.screen_rect.centerx

游戲開始之后會發(fā)現(xiàn)冯乘,現(xiàn)在點(diǎn)擊按鈕的位置洽胶,還是會重置游戲,所以在判斷加上游戲狀態(tài)為False的時(shí)候才重置裆馒,而且讓鼠標(biāo)光標(biāo)在游戲開始之后隱藏起來姊氓,pygame.mouse.set_visible(False),并在游戲結(jié)束的時(shí)候?qū)⒃O(shè)置為True顯示出來。

游戲難度的升級

可以設(shè)置一個(gè)倍數(shù)變量喷好,在判斷外星人數(shù)量為0的if判斷里讓原來的參數(shù)乘與這個(gè)倍數(shù)翔横,同時(shí)也要在游戲開始判斷里重置參數(shù),達(dá)到重新開始的效果

游戲計(jì)分板

需要記錄玩家游戲得分梗搅,最高得分記錄禾唁,難度的等級,還有玩家的剩余飛船數(shù)量圖標(biāo)无切,需要設(shè)置初始得分為0蟀俊,每個(gè)外星人的分值,初始難度為1订雾。

  • 還記得之前檢測子彈和外星人碰撞的函數(shù)groupcollide,返回的是一個(gè)字典存儲在collisions里矛洞,我們遍歷collisions字典中的value值洼哎,就可以得到外星人被子彈碰撞的數(shù)量烫映,將它乘以我們設(shè)置好的外星人的分值,并將分值用render函數(shù)渲染成圖片繪制出來噩峦,同時(shí)做一個(gè)if判斷锭沟,如果當(dāng)前得分高過歷史最高得分,就將它賦值給歷史最高得分變量识补,注意的是這個(gè)歷史最高得分變量不會因?yàn)橛螒蛑匦麻_始而重置族淮,同樣用render函數(shù)繪制出來。
  • 難度等級可放于外星人群數(shù)量等于0的if判斷中凭涂,如果為True祝辣,等級變量+1,同樣用render函數(shù)繪制切油。
  • 創(chuàng)建一個(gè)ships的編組蝙斜,Ship類就需要繼承Sprite,將玩家飛船數(shù)量遍歷出來按照外星人群的方法繪制澎胡,并加到ships編組中孕荠。在飛船與外星人碰撞的if判斷中繪制。
def check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb):
    #檢查子彈是否有擊中外星人
    #如果有擊中攻谁,就刪除對應(yīng)的子彈和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:
        #擊中外星人玩家得分
        for alines in collisions.values():
            stats.score += ai_settings.alien_points * len(alines)
            sb.prep_point()

         #記錄玩家最高得分
        if stats.score > stats.hight_score:
            stats.hight_score = stats.score
            sb.prep_hightscore()

    if len(aliens) == 0:
        #刪除現(xiàn)有的子彈并生成新的外星人
        bullets.empty()

        #升級難度級別
        stats.level += 1

        #更新難度級別記分牌稚伍,增加游戲難度,并創(chuàng)建新外星人群
        sb.prep_level()
        ...
def ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets):
    if stats.ships_break > 0:
        #減少一只飛船的數(shù)量
        stats.ships_break -= 1

        #更新飛船記分牌
        sb.prep_ships()
        ...
class Scoreboard():
     def __init__(self,ai_settings,screen,stats):
     ...
       #記分牌圖像
        self.prep_point()
        self.prep_hightscore()
        self.prep_level()
        self.prep_ships()

    def prep_point(self):
        # rounded_score = int(round(self.stats.score,-1))
        # self.score = str(self.stats.score)
        #玩家得分轉(zhuǎn)化為渲染圖像
        score ='{:,}'.format(self.stats.score)
        self.score_image = self.font.render(score,True,self.point_color,self.ai_settings.screen_color)

        #將得分記分牌放于屏幕左上角
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 20

    def prep_hightscore(self):
        #將玩家最高得分轉(zhuǎn)化為渲染的圖像
        hight_score = '{:,}'.format(self.stats.hight_score)
        self.hight_score_image = self.font.render(hight_score,True,self.point_color,self.ai_settings.screen_color)

        #將最高法記分牌放于屏幕上端中央
        self.hight_score_rect = self.hight_score_image.get_rect()
        self.hight_score_rect.centerx = self.screen_rect.centerx
        self.hight_score_rect.top = 20

    def prep_level(self):
        #將等級轉(zhuǎn)換為渲染的圖像
        self.level_str = str(self.stats.level)
        self.level_image = self.font.render(self.level_str,True,self.point_color,self.ai_settings.screen_color)

        #將等級記分牌放于得分牌下發(fā)
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.screen_rect.right - 20
        self.level_rect.top = self.score_rect.bottom + 5

    def prep_ships(self):
        #顯示飛船數(shù)量
        self.ships = Group()
        for ships_number in range(self.stats.ships_break):
            ship = Ship(self.ai_settings,self.screen)
            ship.rect.x = 10 + ships_number * ship.rect.width
            ship.rect.top = self.screen_rect.top + 10
            self.ships.add(ship)

    def draw_score(self):
        #繪制記分牌圖像
        self.screen.blit(self.score_image,self.score_rect)
        self.screen.blit(self.hight_score_image, self.hight_score_rect)
        self.screen.blit(self.level_image,self.level_rect)
        self.ships.draw(self.screen)
易錯(cuò)點(diǎn)

小游戲項(xiàng)目完成了戚宦,現(xiàn)在就可以運(yùn)行體驗(yàn)了个曙。
整個(gè)編程下來,感覺容易出錯(cuò)導(dǎo)致報(bào)錯(cuò)的阁苞,往往都是一些非常細(xì)節(jié)的東西困檩,例如:

  • 調(diào)用類方法或者類對象的調(diào)用錯(cuò)誤,或者傳入的參數(shù)沒有對不上那槽,出現(xiàn)漏掉或者位置錯(cuò)誤悼沿,當(dāng)然也可以指定參數(shù)賦值,但是過于繁瑣骚灸,建議寫方法之前糟趾,可以先不用傳參數(shù),寫完之后在將方法中需要的參數(shù)填上在復(fù)制粘貼統(tǒng)一即可甚牲。
  • 代碼的整潔性义郑,代碼塊不易過長,會導(dǎo)致不好理解丈钙,而且修改的時(shí)候也不好修改非驮,要養(yǎng)成寫完一個(gè)功能代碼之后整理一下,將功能細(xì)分成更多小功能代碼塊雏赦,這個(gè)需要日后多次項(xiàng)目經(jīng)歷培養(yǎng)形成劫笙。
    *注釋芙扎,對每個(gè)代碼塊進(jìn)行注釋,讓閱讀者更快的了解代碼的作用性填大,注釋的規(guī)范戒洼,可放于代碼的上方或者下方,用#號鍵注釋允华,最好注釋之后可以空行圈浇,會更美觀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靴寂,一起剝皮案震驚了整個(gè)濱河市磷蜀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌榨汤,老刑警劉巖蠕搜,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異收壕,居然都是意外死亡妓灌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門蜜宪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虫埂,“玉大人,你說我怎么就攤上這事圃验〉舴” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵澳窑,是天一觀的道長斧散。 經(jīng)常有香客問我,道長摊聋,這世上最難降的妖魔是什么鸡捐? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮麻裁,結(jié)果婚禮上箍镜,老公的妹妹穿的比我還像新娘。我一直安慰自己煎源,他們只是感情好色迂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著手销,像睡著了一般歇僧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锋拖,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天诈悍,我揣著相機(jī)與錄音埂淮,去河邊找鬼。 笑死写隶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讲仰。 我是一名探鬼主播慕趴,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鄙陡!你這毒婦竟也來了冕房?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤趁矾,失蹤者是張志新(化名)和其女友劉穎耙册,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毫捣,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡详拙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔓同。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饶辙。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斑粱,靈堂內(nèi)的尸體忽然破棺而出弃揽,到底是詐尸還是另有隱情,我是刑警寧澤则北,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布矿微,位于F島的核電站,受9級特大地震影響尚揣,放射性物質(zhì)發(fā)生泄漏涌矢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一惑艇、第九天 我趴在偏房一處隱蔽的房頂上張望蒿辙。 院中可真熱鬧,春花似錦滨巴、人聲如沸思灌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泰偿。三九已至,卻和暖如春蜈垮,著一層夾襖步出監(jiān)牢的瞬間耗跛,已是汗流浹背裕照。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留调塌,地道東北人晋南。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像羔砾,于是被迫代替她去往敵國和親负间。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容