Python編寫微信打飛機小遊戲(六)
如果覺得這篇文章對您有所啟發,歡迎關注我的公眾號,我會盡可能積極和大家交流,謝謝。
接下來,我們為我方飛機新增武器——發射子彈。
考慮到Python語言的模組化,我們同樣將子彈封裝為一個模組,bullet.py。新建py檔案,匯入Pygame,程式設計開始。
1、定義子彈類——Bullet1
強調這裡之所謂命名為Bullet1,是因為遊戲中我方飛機射出的子彈是有兩種形式,一種是普通子彈,另外一種是超級子彈。其中超級子彈(Bullet2)將在之後的補給發放機制中進行講解,這裡先給出Bullet1類的程式碼:
# ====================定義普通子彈====================class Bullet1(pygame.sprite.Sprite): def __init__(self, position): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("image/bullet1.png") self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.speed = 12 self.active= True self.mask = pygame.mask.from_surface(self.image) def move(self): if self.rect.top < 0: self.active = False else: self.rect.top -= self.speed def reset(self, position): self.rect.left, self.rect.top = position self.active= True
Bullet1與之前定義的飛機類在結構上很相似,同樣都繼承至Pygame的精靈類,同樣具有active標誌位、mask掩膜成員、具有移動函式move()和復位函式reset(),需要注意的有一下幾點:1、子彈的速度屬性speed要稍微大一點。2、子彈的初始化位置是一個隨我方飛機位置變化而變化的量,因此需要在初始化子彈物件時由外部傳入(程式碼中的position,是一個rect型別變數)。3、子彈在螢幕中是自下而上移動的,因此是“-= self.speed”。
2、在主程式中例項化子彈
玩過小蜜蜂遊戲的同學都知道,這種打飛機類的遊戲子彈的發射速度是要比90坦克的炮彈速度快的,嚴格的說不是速度快,而是頻率高。90坦克中我方坦克一次只發射一枚炮彈,在炮彈達到最大射程或者擊中敵方坦克之後才能打出下一發炮彈。打飛機則不然,一次只發射一發子彈顯然不能應付眾多敵機,需要源源不斷的發射子彈,落實到程式中也就是需要例項化多個子彈物件並且迴圈列印,這與之前敵方飛機的初始化方式很像,都是新增多個精靈物件並迴圈顯示,因此我們採用和之前敵機例項化時相類似的機制,首先在main函式的while迴圈之前建立子彈精靈的索引,然後向其中新增指定數目的子彈:
# ====================生成普通子彈==================== bullet1 = [] bullet1_index = 0 bullet1_num = 6 # 定義子彈例項化個數 for i in range(bullet1_num): bullet1.append(bullet.Bullet1(me.rect.midtop))
這裡通過for迴圈語句來產生指定數目的子彈物件,並存儲於列表結構體中(bullet1),值得注意的一點是,在前面已經提到,在例項化子彈物件時,需要外部傳入子彈的初始位置,這裡的me.rect.midtop代表的是我方飛機的上方正中間的位置,其實rect結構體還有很多有趣的成員變數來表徵其某部分屬性,比如說以後我們會用到的rect.center,代表矩形的中心位置,這些知識遇到了再積累吧。
3、顯示子彈及音效
子彈初始化完成後需要將子彈顯示在螢幕上:
if not (delay % 10): # 每十幀發射一顆移動的子彈 bullet_sound.play() bullets = bullet1 bullets[bullet1_index].reset(me.rect.midtop) bullet1_index = (bullet1_index + 1) % bullet1_num
這部分程式碼應該放在while迴圈之內,這裡if not (delay % 10)是設定子彈列印的速度,即每十幀繪製一發子彈,delay引數在之前已經詳細介紹過,這裡不再贅述。在呼叫子彈物件的reset()成員函式是,即將該物件的active成員變數設定為true,說明該子彈物件已經處於啟用狀態了。程式編寫到這裡,如果此時執行程式的話,只能聽到發射子彈的聲音,並不能看到實際發射的子彈,原因是沒有對子彈進行繪製(blit函式)。但這裡並不能簡單的把子彈繪製到螢幕上,因為我們還要為子彈賦予它的本質功能,擊毀敵機,也就是和敵機的碰撞檢測。
4、子彈與敵機的碰撞檢測
在例項化完子彈之後,我們要做的第一件事並不是將子彈顯示出來,二是要先檢測該發子彈是否擊中了敵機,如果擊中,就不再顯示這發子彈了。因此這裡正常的程式設計思路應該是:每一發子彈 -> 該發子彈是否已經啟用 -> 如果啟用,是否擊中敵機 -> 如果沒擊中,正常繪製子彈影象 -> 如果擊中,則子彈損毀,同時敵機損毀,程式碼如下:
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 # 小型敵機損毀
這裡在碰撞檢測是,考慮到子彈物件和敵機物件都已經在內部定義了mask(掩膜)成員變數,因此直接呼叫基於掩膜的的碰撞檢測函式即可(詳見上篇博文),再次說明碰撞檢測函式的返回值(enemise_hit)是一個存放精靈的精靈組結構,通過for()迴圈遍歷其中的元素及確定那個精靈檢測到了碰撞。
ok,程式編寫到這裡,按道理來說應該能夠正常執行,但在這裡很可能會出現一個類似於“local variable 'bullets' referenced before assignment”的錯誤,在這裡簡單分析一下。錯誤的意思是bullets變數沒有定義,出現這個錯誤的原因在於我們過早的將delay延時引數進行了減一操作。假如我們將“delay -= 1”這句話放在了while迴圈的開始位置,delay的初始值為60,進入while迴圈的一瞬間它就會減為59,這樣“if not (delay % 10)”這個條件就不會成立,也就不會執行“bullets = bullet1”,當然也不會播放子彈音效,因此在程式執行到“for b in bullets:”時,由於之前的“bullets = bullet1”操作沒有順利執行,自然bullets引數屬於未定義的狀態。解決方案也很簡單,就是把delay引數的控制程式碼:
if delay == 0: delay = 60 delay -= 1
移至while迴圈的末尾,這樣程式在進入迴圈是delay=60,所有程式碼順利執行。
截止到這裡,程式順利執行,我方飛機射出的子彈將敵機一擊斃命。當然這裡也有不合理的地方,小型敵機可以一擊斃命,但中型敵機和大型敵機皮糙肉厚,應該能多挨幾發子彈才對,這在之後的博文中會給中型敵機和大型敵機賦予一定血量,並以血槽的形式顯示出來。OK,這篇博文就到這裡,大家工作順利。