之前的工作已經基本上將我方飛機的圖形顯示工作做的差不多了,這篇博客中我們將開始添加敵方飛機——小型敵機、中型敵機(直升機)和大型敵機(坦克)。新建一個enemy.py文件,導入pygame和random模塊,開始編寫吧(還是要注意文件編碼問題,以后就不再啰嗦了)。
敵方飛機類與我方飛機模塊有一定的相似性,但不會左右移動,不會發射子彈等等。小型敵機是敵方飛機中最基本的類型,一擊斃命,沒有血量、沒有出場音效。中型敵機有一定血量,損毀時附帶特殊音效。大型敵機血量最多,出場和損毀時都有特殊音效,游戲中中型敵機和大型敵機都將顯示血槽。
1、小型敵機類
和我方飛機一樣,小型敵機類也是繼承自Pygame的精靈類,在初始化過程中需要先調用基類的初始化函數,在加載飛機圖像、獲取飛機圖像尺寸,設置小飛機的移動速度等等:
~~~
class SmallEnemy(pygame.sprite.Sprite):
def __init__(self, bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("image/enemy1.png") # 加載敵方飛機圖片
self.rect = self.image.get_rect() # 獲得敵方飛機的位置
self.width, self.height = bg_size[0], bg_size[1] # 本地化背景圖片位置
self.speed = 2 # 設置敵機的速度
~~~
在初始化小飛機的位置時,需要稍微做一點工作:
~~~
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定義敵機出現的位置
randint(-5 * self.rect.height, -5) # 保證敵機不會在程序已開始就立即出現
)
~~~
小飛機在初始化時需要隨機指定其位置,其x左邊(橫向寬度)在(0,width - rect.width)之間,保證飛機能夠在整個屏幕的X軸的任何位置出現并且不會越界。其top(縱向高度)在(-5 * rect.height, -5)之間,這里之所以最大值設置為負數(-5),是為了保證程序開始運行時敵機不會立即出現,給用戶以反應時間。這里還有一個小細節需要注意,就是如果Python的代碼太長,在換行時建議使用小括號來指定范圍,不推薦只使用“\”來連接各個行。
2、move()和reset()函數
在定義完小型飛機的數據結構之后,需要定義其移動操作move()函數(成員函數)。敵機的移動函數功能相對單一,只是在屏幕上以固定速度向下移動:
~~~
def move(self): # 定義敵機的移動函數
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
~~~
這里即可體現出speed參數的作用。當然還需要編寫一個reset()成員函數來將已經移動出屏幕的敵機進行重置:
~~~
def reset(self): # 當敵機向下移動出屏幕時
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width),
randint(-5 * self.rect.height, 0)
)
~~~
3、中型敵機
中型敵機結構與小型敵機類此,區別在于中型敵機的圖標、血量以及損毀音效等方面,其中敵機血量以及損毀音效的添加我們在之后的博文中再加以介紹:
~~~
class MidEnemy(pygame.sprite.Sprite):
def __init__(self, bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("image/enemy2.png") # 加載敵方飛機圖片
self.rect = self.image.get_rect() # 獲得敵方飛機的位置
self.width, self.height = bg_size[0], bg_size[1] # 本地化背景圖片位置
self.speed = 1 # 設置敵機的速度,應該比小型敵機速度稍慢
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定義敵機出現的位置
randint(-10 * self.rect.height, -self.rect.height)
)
def move(self): # 定義敵機的移動函數
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
def reset(self): # 當敵機向下移動出屏幕時
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定義敵機出現的位置
randint(-10 * self.rect.height, -self.rect.height) # 保證一開始不會有中型敵機出現
)
~~~
4、大型敵機
大型敵機作為boss級別的存在,其不僅僅具有高血量、特殊出場音效以及損毀音效,其在移動過程中也存在圖片切換的特效(與我方飛機尾部噴氣特效類似),因此在加載圖片時同樣需要加載兩張圖片,以便在主程序顯示的過程中進行切換:
~~~
class BigEnemy(pygame.sprite.Sprite):
def __init__(self, bg_size):
pygame.sprite.Sprite.__init__(self)
self.image1 = pygame.image.load("image/enemy3_n1.png") # 加載敵方飛機圖片,其中大型飛機有幀切換的特效
self.image2 = pygame.image.load("image/enemy3_n2.png")
self.rect = self.image1.get_rect() # 獲得敵方飛機的位置
self.width, self.height = bg_size[0], bg_size[1] # 本地化背景圖片位置
self.speed = 2 # 設置敵機的速度
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定義敵機出現的位置
randint(-15 * self.rect.height, -5 * self.rect.height)
)
def move(self): # 定義敵機的移動函數
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.reset()
def reset(self): # 當敵機向下移動出屏幕時
self.rect.left, self.rect.top = (randint(0, self.width - self.rect.width), # 定義敵機出現的位置
randint(-15 * self.rect.height, -5 * self.rect.height)
)
~~~
? 5、敵方飛機實例化控制函數
在完成敵機模塊enemy.py的構建之后,我們開始在主程序中將敵機實例化并顯示在屏幕上,首先在程序開始時導入已經編寫好的模塊:
~~~
import enemy
~~~
注意我們的打飛機程序在設計時是設計成有難度分級的,玩家得分越多、需要挑戰的難度等級就越高;難度等級越高,所出現的敵機數量就越多,可見敵機的實例數量是隨時可能發生變化的,直接實例化具體數量的敵機顯然不滿足要求,在此我們通過編寫一個實例化控制函數來實現敵機對象的動態添加,以小型敵機添加函數為例:
~~~
# ====================敵方飛機生成控制函數====================
def add_small_enemies(group1, group2, num):
for i in range(num):
e1 = enemy.SmallEnemy(bg_size)
group1.add(e1)
group2.add(e1)
~~~
這個函數結構簡單,通俗易懂,即將指定個敵機對象添加到精靈組(sprite.group)中。參數group1、group2是兩個精靈組類型的形參,用以存儲多個精靈對象(敵機)。需要注意的一點是group既然是特定的精靈組結構體,在向其內部添加精靈對象時需要調用其對應的成員函數add(),不能使用列表添加函數append()。至于for循環這些python的基礎知識這里就不再贅述
以此類推,編寫中型敵機和大型敵機的實例化控制函數:
~~~
def add_mid_enemies(group1, group2, num):
for i in range(num):
e2 = enemy.MidEnemy(bg_size)
group1.add(e2)
group2.add(e2)
def add_big_enemies(group1, group2, num):
for i in range(num):
e3 = enemy.BigEnemy(bg_size)
group1.add(e3)
group2.add(e3)
~~~
友情提示,所有函數都應該在第一次使用之前進行定義,也就是說以上三個函數都應該定義在main()函數之前,以保證在main()函數中順利調用。
6、敵機實例化
第一步,在main函數中,在進入while循環之前,調用enemy.py模塊,向精靈組中添加敵機精靈對象:
~~~
# ====================實例化敵方飛機====================
enemies = pygame.sprite.Group() # 生成敵方飛機組
small_enemies = pygame.sprite.Group() # 敵方小型飛機組
add_small_enemies(small_enemies, enemies, 1) # 生成若干敵方小型飛機
mid_enemies = pygame.sprite.Group() # 敵方小型飛機組
add_mid_enemies(mid_enemies, enemies, 1) # 生成若干敵方中型飛機
big_enemies = pygame.sprite.Group() # 敵方小型飛機組
add_big_enemies(big_enemies, enemies, 1) # 生成若干敵方大型飛機
~~~
先調用pygame.sprite.Group()生成精靈組,這里需要生成兩種精靈組,一種精靈組用以存儲所有敵機精靈(不區分小型中型大型),另一種則是針對不同型號敵機創建不同的精靈組來存儲。之所以這樣做,是因為不同類型的敵機之前既有共同屬性,又有各自的特殊屬性,在處理中更為方便。精靈組生成之后調用對應的生成控制函數來向其中添加敵機精靈對象即可(這里先將添加數量均設置為1)。
第二步,顯示敵機。繪制小型敵機和中型敵機的方法類似,即將當前精靈組中的所有對象通過for()循環進行索引,并逐個blit()到屏幕對象中,并激活其內部的移動函數使其移動,注意這部分代碼應該位于while循環之內:
~~~
for each in small_enemies: # 繪制小型敵機并自動移動
each.move()
screen.blit(each.image, each.rect)
~~~
~~~
for each in mid_enemies:
if each.active:
each.move()
screen.blit(each.image, each.rect)
~~~
大型敵機在繪制時需要添加圖片切換的特效,參照之前我方飛機的尾部噴氣特效的實現方法:
~~~
for each in big_enemies: # 繪制大型敵機并自動移動
each.move()
if switch_image:
screen.blit(each.image1, each.rect) # 繪制大型敵機的兩種不同的形式
else:
screen.blit(each.image2, each.rect)
~~~
~~~
if each.rect.bottom == -50:
big_enemy_flying_sound.play(-1)
~~~
~~~
?
~~~
這里當大型敵機即將出場時rect.bottom == -50,需要實現播放出場音效,并且是循環播放(參數設置為-1),如上所示。OK,運行代碼,就會看到敵方飛機陸續從屏幕上飛過,不過這里應該有一個bug,就是大型敵機出場時不能順利播放出場音效,這涉及Pygame的一個聲道阻塞的問題,我們將在后續的博文中進行總結,今天就先進行到這里吧。