在完成敵方敵機的初步設置后,運行程序我們發現在屏幕上我方飛機和敵方飛機能夠友好共存,互不干涉,這顯然不符合游戲的宗旨,在這篇文章中我們為游戲添加我方飛機和敵機之間的碰撞損毀機制。
1、碰撞檢測
碰撞檢測是游戲設計中的最基本的部分,幾乎任何游戲中的主角都具有發射一些飛行道具的能力,如何準備判斷主角射出的子彈、飛鏢、能量球、龜派氣功是否準確命中目標,就是碰撞檢測所要實現的功能。對于一些規則的圖形(例如說圓形),我們可以計算兩個圓形圓心之間的距離與其半徑的關系來判斷其是否已經相撞,但對于一些不規則圖形(如我們這里的小飛機),這種簡單的方法是行不通的。為此,Pygame模塊已經在精靈(Sprite)類中添加了碰撞檢測函數:spritecollide()。
Pygame碰撞檢測有兩種機制,一種是直接調用spritecollide()函數,函數需要傳入一個精靈對象和一個精靈組對象,用以檢測一個精靈與另一個精靈組中的所有精靈是否發生碰撞。這種基本的碰撞檢測機制實際上是存在一定缺陷的。由于它是通過檢測兩個精靈所擁有的對象圖片(精靈的image)是否發生重疊來檢測碰撞的發生,當精靈圖片除了精靈本身若還存在較大空白區域的話(例如我們的飛機和敵機圖片),可能程序在檢測到碰撞時只是兩個精靈圖片的空白部分發生了重疊,而兩個精靈對象還并沒有重疊到一起,這就給游戲帶來了不好的體驗,因此在本程序中我們使用另外一種更為精確的碰撞檢測方法:基于精靈圖像掩膜的碰撞檢測方法。
這種方法同樣是調用spritecollide()函數,只是在調用時指定函數的調用方式為掩膜檢測類型,這樣在碰撞檢測時程序檢測的就是精靈的非透明部分(掩膜)是否發生碰撞,而非整個圖像是否發生重疊。至于如何將精靈圖片中的背景區域變得透明,大家可以從網上查閱相關方法,不過這里我們給出的圖片資源都已經經過了透明化處理,可以拿來直接使用的。
2、為我方飛機和敵方飛機指定掩膜屬性以及生存狀態標志位
由于需要基于掩膜進行碰撞檢測,因此需要在飛機類(包括我方飛機和敵機)中添加一個掩膜(mask)屬性:
~~~
self.mask = pygame.mask.from_surface(self.image1) # 獲取飛機圖像的掩膜用以更加精確的碰撞檢測
~~~
需要在MyPlane、SmallEnemy、MidEnemy、BigEnemy這些飛機模塊中都添加這句代碼,以將其對應圖片中不透明部分(精靈的實際面積)轉換成掩膜以便碰撞檢測時調用。同時,既然我方飛機和敵機隨時都有可能損毀(無論是被子彈射中還是被全屏炸彈消滅還是通過撞擊來玉石俱焚),因此有必要在類內部添加一個標志位來記錄當前對象時正常存活還是已經損毀(我方飛機例外):
~~~
self.active = True # 設置飛機當前的存在屬性,True表示飛機正常飛行,False表示飛機已損毀
~~~
同樣,這句代碼也應該共存于以上三個類中。同時需要在各個類的reset()成員函數中將active標志位置為真,以保證各個類型的飛機在重置之后是激活的狀態:
~~~
self.active = True
~~~
3、主程序中進行碰撞檢測
接下來在主程序循環中,我們需要實時檢測我方飛機是否和敵方飛機的任何一個對象發生了碰撞:
~~~
enemies_down = pygame.sprite.spritecollide(me, enemies, False, pygame.sprite.collide_mask)
if enemies_down: # 如果碰撞檢測返回的列表非空,則說明已發生碰撞,若此時我方飛機處于無敵狀態
me.active = False
for e in enemies_down:
e.active = False # 敵機損毀
~~~
注意在這里體會基于掩膜碰撞檢測的調用方式,只要將spritecollide()的第四個參數指定為pygame.sprite.collide_mask,程序就能自動根據精靈的mask成員變量進行精準的碰撞檢測。在這里me表示我方敵機實例,enemies代表敵方飛機的所有實例(精靈組對象),這也是為什么之前我們在調用諸如add_small_enemies等敵機添加控制函數時,每次都把生成的敵機一方面添加到對應類型的精靈組中,另一方面也將其添加到總體敵機的精靈組中的原因:add_small_enemies(small_enemies, enemies, 1)。
spritecollide()這個函數將返回一個列表,如果我方飛機(me)與敵機組(enemies)中的任何一個精靈對象檢測到了碰撞,就會將enemies中檢測到碰撞的對象添加到結果列表中(enemies_down),在接下來的程序中只需判斷enemies_down是否為空,如果非空則說明已發生碰撞,一方面將我方飛機的active標志位置為false,表示我方飛機以掛,同時將檢測到發生碰撞的敵機(enemies_down列表中所包含的精靈對象)的標志位也設置為false,達到玉石俱焚的目的。
4、加載損毀圖片資源
飛機撞毀之后會有爆炸的特效,這是通過將多張漸進演變的爆炸特效圖片一次播放后取得的效果,說白了就是當檢測到飛機損毀之后,就一次播放爆炸的圖片。爆炸特效的圖片資源已經存放在image文件夾下來,其中我方飛機的爆炸特效有四張、小型敵機有四張、中型敵機有四張,大型敵機有六張。
在對應類中加載爆炸圖像以便使用,我方飛機(MyPlane類中):
~~~
self.destroy_images = [] # 加載飛機損毀圖片
self.destroy_images.extend([pygame.image.load("image/hero_blowup_n1.png"),
pygame.image.load("image/hero_blowup_n2.png"),
pygame.image.load("image/hero_blowup_n3.png"),
pygame.image.load("image/hero_blowup_n4.png")])
~~~
由于飛機損毀圖片都是多張,為了方便索引,我們先創建一個名為“destroy_image”的類成員列表變量,然后通過列表的extend()方法將各個圖片添加到列表中。一次類推,小型敵機類SmallEnemy:
~~~
self.destroy_images = [] # 加載飛機損毀圖片
self.destroy_images.extend([pygame.image.load("image/enemy1_down1.png"),
pygame.image.load("image/enemy1_down2.png"),
pygame.image.load("image/enemy1_down3.png"),
pygame.image.load("image/enemy1_down4.png")])
~~~
中型敵機類MidEnemy:
~~~
self.destroy_images = [] # 加載飛機損毀圖片
self.destroy_images.extend([pygame.image.load("image/enemy2_down1.png"),
pygame.image.load("image/enemy2_down2.png"),
pygame.image.load("image/enemy2_down3.png"),
pygame.image.load("image/enemy2_down4.png")])
~~~
大型敵機類BigEnemy:
~~~
self.destroy_images = [] # 加載飛機損毀圖片
self.destroy_images.extend([pygame.image.load("image/enemy3_down1.png"),
pygame.image.load("image/enemy3_down2.png"),
pygame.image.load("image/enemy3_down3.png"),
pygame.image.load("image/enemy3_down4.png"),
pygame.image.load("image/enemy3_down5.png"),
pygame.image.load("image/enemy3_down6.png")])
~~~
5、播放爆炸損毀特效
接下來需要在主程序中加入損毀的爆炸效果。基本思路是當程序檢測到當前飛機對象(無論是我方飛機還是敵機)因碰撞而掛掉(成員變臉active=false)后,則依次打印其若干張損毀圖像。在因此打印的過程中,我們采用索引值的方式來判別接下來應該打印第幾張損毀特效圖片,因此需要在main函數的開始部分(while之前)先聲明各個索引值:
~~~
# ====================飛機損毀圖像索引====================
e1_destroy_index = 0
e2_destroy_index = 0
e3_destroy_index = 0
me_destroy_index = 0
~~~
以我方飛機損毀為例,當檢測到active變量為true時,正常繪制我方飛機模型,當檢測到active為false時,開始繪制損毀特效:
~~~
if me.active:# 繪制我方飛機的兩種不同的形式else:
if not (delay % 3):
screen.blit(me.destroy_images[me_destroy_index], me.rect)
me_destroy_index = (me_destroy_index + 1) % 4
if me_destroy_index == 0:
me_down_sound.play()
me.reset()
~~~
?
與之前飛機尾氣噴氣特效的原理類似,這里在播放損毀特效時同樣需要控制圖片的播放速度,因此需要借用延時參數delay(之前的博文中有介紹),這里涉及為每三幀切換播放一張損毀圖片,當損毀圖片播放完畢后(me_destroy_index 再次等于零),播放飛機損毀音效(me_down_sound.play())。
與此類似,小型敵機的損毀特效代碼:
~~~
for each in small_enemies:
if each.active:
# 繪制小型敵機
else:
if e1_destroy_index == 0:
enemy1_down_sound.play()
if not (delay % 3):
screen.blit(each.destroy_images[e1_destroy_index], each.rect)
e1_destroy_index = (e1_destroy_index + 1) % 4
if e1_destroy_index == 0:
each.reset()
~~~
在所有損毀圖片繪制完成后(e1_destroy_index再次等于零),調用reset()成員函數來重置敵機位置。
同理,中型敵機損毀特效代碼:
~~~
for each in mid_enemies: # 繪制中型敵機并自動移動
if each.active:
# 繪制中型敵機else:
if e2_destroy_index == 0:
enemy2_down_sound.play()
if not (delay % 3):
screen.blit(each.destroy_images[e2_destroy_index], each.rect)
e2_destroy_index = (e2_destroy_index + 1) % 4
if e2_destroy_index == 0:
each.reset()
~~~
大型敵機損毀特效:
~~~
for each in big_enemies: # 繪制大型敵機并自動移動
if each.active: # 如果飛機正常存在
# 繪制大型敵機
else:
big_enemy_flying_sound.stop()
if e3_destroy_index == 0:
enemy3_down_sound.play() # 播放飛機撞毀音效
if not (delay % 3): # 每三幀播放一張損毀圖片
screen.blit(each.destroy_images[e3_destroy_index], each.rect)
e3_destroy_index = (e3_destroy_index + 1) % 6 # 大型敵機有六張損毀圖片
if e3_destroy_index == 0: # 如果損毀圖片播放完畢,則重置飛機屬性
each.reset()
~~~
需要補充一點,在之前MyPlane()類中貌似沒有寫reset()函數,在這里補上吧:
~~~
def reset(self):
self.rect.left, self.rect.top = (self.width - self.rect.width) // 2, (self.height - self.rect.height - 60)
self.active = True
~~~
ok,運行程序,屏幕上出現敵機,控制我方飛機移動并與敵機相撞后,順利播放損毀特效以及音效,ok,這次博文就先介紹到這里吧。