在之前的博文中我們為游戲添加了隨分數累加的難度遞增機制,這就帶來一個問題:當到達后面的難度等級后,敵方飛機鋪天蓋地而來,我方小飛機根本應付不過來,因此在這篇博文中我們為我方飛機賦予必殺技——隨機補給全屏炸彈和超級子彈。
首先來簡單描述這兩個必殺技,全屏炸彈是指在游戲過程中,當用戶按下空格鍵時,就觸發一枚全屏炸彈(如果當前有的話),此時屏幕上的所有敵機立即銷毀。超級子彈是指玩家在接收到指定補給包之后,我方飛機能夠一次發射兩發子彈,攻擊力加倍。ok,開始工作。
1、定義supply模塊
定義一個名為supply.py的模塊用以保存超級武器的相關屬性,包含全屏炸彈補給類和超級子彈補給類。對于全屏炸彈補給類,這里先給出完整代碼:
~~~
# ====================定義超級炸彈補給包====================
class BombSupply(pygame.sprite.Sprite):
def __init__(self, bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("image/ufo2.png")
self.rect = self.image.get_rect()
self.width, self.height = bg_size[0], bg_size[1]
self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100
self.speed = 5
self.active = False
self.mask = pygame.mask.from_surface(self.image)
def move(self):
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.active = False
def reset(self):
self.active = True
self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100
~~~
可見BombSupply類結構與之前敵方飛機的類結構非常相似,其實補給包本身就和敵機具有相同的屬性:隨機位置初始化,以一定速度向下移動,需要進行掩膜碰撞檢測(只不過碰撞之后不會造成我方敵機損毀)等等。類似的,超級子彈補給類的代碼如下,兩者在結構上完全相同,不再贅述。
~~~
# ====================定義超級子彈補給包====================
class BulletSupply(pygame.sprite.Sprite):
def __init__(self, bg_size):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("image/ufo1.png")
self.rect = self.image.get_rect()
self.width, self.height = bg_size[0], bg_size[1]
self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100
self.speed = 5
self.active = False
self.mask = pygame.mask.from_surface(self.image)
def move(self):
if self.rect.top < self.height:
self.rect.top += self.speed
else:
self.active = False
def reset(self):
self.active = True
self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100
~~~
? 2、實例化超級炸彈
值得注意的一點是,在游戲初始是自帶3個全屏炸彈的,因此我們先不啟動補給發放機制,先把這三發超級炸彈使用好。
首先在游戲運行時需要將全屏炸彈的圖標和剩余數量顯示在畫面左下角的位置,由于炸彈數量是可變的(我方飛機可能吃到補給包),因此在main函數中初始化一個全局變量用來保存當前全屏炸彈的數量:
~~~
bomb_num = 3 # 初始為三個炸彈
~~~
然后加載全屏炸彈的小圖標并獲得其位置屬性,注意之前supply.py模塊中加載的圖片是補給包的圖標(帶小降落傘的),并非全屏炸彈的圖標:
~~~
bomb_image = pygame.image.load("image/bomb.png") # 加載全屏炸彈圖標
bomb_rect = bomb_image.get_rect()
bomb_front = score_font
~~~
注意既然接下來既然需要顯示字符(全屏炸彈數量),我們就直接應用之前創建好的用來打印分數的字體對象即可(詳見上篇博文)。接下來的工作就是在main()函數的while循環中將圖片以及炸彈數量實時的繪制到屏幕上:
~~~
# ====================繪制全屏炸彈數量和剩余生命數量====================
bomb_text = bomb_front.render("× %d" % bomb_num, True, color_black)
bomb_text_rect = bomb_text.get_rect()
screen.blit(bomb_image, (10, height - 10 - bomb_rect.height))
screen.blit(bomb_text, (20 + bomb_rect.width, height - 10 - bomb_text_rect.height))
~~~
注意這里由于要求炸彈圖標的乘號以及炸彈數量并排顯示,因此需要獲取圖片與字體的尺寸,至于如何擺放,相信大家稍加推敲就能理解的。此時運行程序,屏幕左下角正常顯示圖標。
3、觸發全屏炸彈
? 既然已經把全屏炸彈顯示出來了,僅僅放在那震懾是不夠的,每當用戶按下一次空格鍵,就觸發一枚全屏炸彈,屏幕上所有敵機立即損毀。當然,我們首先需要知道用戶什么時候按下了空格鍵:
~~~
# ====================檢測用戶的退出及暫停操作====================
for event in pygame.event.get(): # 響應用戶的偶然操作
if event.type == QUIT:
# 如果用戶按下屏幕上的關閉按鈕,觸發QUIT事件,程序退出elif event.type == KEYDOWN:
if event.key == K_SPACE: # 如果檢測到用戶按下空格鍵
if bomb_num: # 如果炸彈數量大于零,則引爆一顆超級炸彈
bomb_num -= 1
bomb_sound.play()
for each in enemies:
if each.rect.bottom > 0: # 屏幕上的所有敵機均銷毀
each.active = False
~~~
由于空格鍵的按下屬于偶然操作,因此采用“pygame.event.get()”的事件響應機制,代碼結構簡單,即當檢測到用戶鍵盤按下并且對應鍵值為“K_SPACE”時,炸彈數量減一(如果炸彈數量大于零),播放全屏炸彈音效、屏幕上所有敵機損毀,在這里也能體現出當初我們在實例化敵機對象時將敵機統一放在了“enemies”這個精靈組結構中的優勢了,操作異常便捷。
4、開啟補給機制
全屏炸彈順利施放,接下來開啟補給機制。設定為每10秒發放一次補給,我們通過觸發時間機制來完成這個功能,即將補給發放定義為一個用戶時間,每隔30秒觸發一次,在響應時間的過程中初始化補給包,首先在main()函數中定義事件:
~~~
supply_timer = USEREVENT # 補給包發放定時器
pygame.time.set_timer(supply_timer, 10 * 1000) # 定義每10秒發放一次補給包
~~~
注意Pygame模塊中為每個事件通過宏定義的方式定義了一個標號,用以區分各個時間。我們在定義自己的用戶事件的時候也要人為的為其賦值一個事件標號,為了保證用戶定義的事件標號與系統事件標號不沖突,Pygame特意提供了“USEREVENT”這個宏,標號在(0,USEREVENT-1)的事件為系統時間,因此此處我們將定義的用戶事件指定為“USEREVENT”是不會和系統事件發生沖突的。事件定義完之后調用time類中的set_timer()函數設定事件觸發的事件間隔,注意這里的時間是以毫秒為單位的,因此需要乘以1000轉換成秒。
當然要想順利的打印補給包,首先需要對其進行實例化(和敵方飛機的實例話類似):
~~~
# ====================實例化補給包====================
bullet_supply = supply.BulletSupply(bg_size)
bomb_supply = supply.BombSupply(bg_size)
~~~
接下來在whlie()循環內部編寫補給定時器“supply_timer”的事件響應函數:
~~~
# ====================檢測用戶的退出及暫停操作====================
for event in pygame.event.get(): # 響應用戶的偶然操作
if event.type == QUIT:
# 如果用戶按下屏幕上的關閉按鈕,觸發QUIT事件,程序退出elif event.type == supply_timer: # 響應補給發放的事件消息
if choice([True, False]):
bomb_supply.reset()
else:
bullet_supply.reset()
~~~
?
事件響應函數的主要任務是確定當前應該實例化何種補給包,是全屏炸彈補給包還是超級子彈補給包,這種選擇應該是隨機的,因此可以用“choice()”實現隨機選擇機制,然后再根據隨機結果將對應的補給包進行復位操作(reset會將對應的補給對象的active變量激活),繼而引導接下來的補給發放機制。
5、檢測用戶是否獲得補給包
在通過事件響應函數確定當前發放的補給類型之后,接下里就需要檢測我方飛機是否順利拾獲了補給包,核心是通過碰撞檢測機制來完成,首先判斷是否獲得了全屏炸彈補給包:
~~~
# ====================繪制補給并檢測玩家是否獲得====================
if bomb_supply.active: # 如果是超級炸彈補給包
bomb_supply.move()
screen.blit(bomb_supply.image, bomb_supply.rect)
if pygame.sprite.collide_mask(bomb_supply, me): # 如果玩家獲得超級炸彈補給包
get_bomb_sound.play()
if bomb_num < 3:
bomb_num += 1
bomb_supply.active = False
~~~
程序思路很清晰,如果當前全屏炸彈補給包被激活,則打印其圖標并開始自動移動,在移動過程中若與我方飛機發生了碰撞(碰撞檢測返回true),則認為玩家順利獲取了全屏炸彈補給包,此時再判斷玩家剩余全屏炸彈的數量,如果小于三個,則加一,之后再將補給包對象的active變量置為false,關閉補給對象。
接下來判斷用戶是否獲得超級子彈補給包,這里有一點需要注意,就是在獲得全屏炸彈之后,我方飛機將以此發射兩發子彈,因此需要設置一個標志位“is_double_bullet”來表示當前的子彈狀態,“is_double_bullet = false”表示當前發射的是普通子彈,“is_double_bullet = true”表示當前發射的是超級子彈,首先在main函數中聲明變量并初始化:
~~~
is_double_bullet = False # 是否使用超級子彈標志位
~~~
然后開始進行超級子彈補給包的碰撞檢測:
~~~
if bullet_supply.active: # 如果是超級子彈補給包
bullet_supply.move()
screen.blit(bullet_supply.image, bullet_supply.rect)
if pygame.sprite.collide_mask(bullet_supply, me):
get_bullet_sound.play()
is_double_bullet = True
bullet_supply.active = False
~~~
既然添加了兩種子彈屬性,因此有必要在繪制子彈之前進行一把判斷了,不過到現在為止我們貌似還沒有定義超級子彈類,之前在定義bullet.py模塊的時候曾經提到過,Bullet1代表普通子彈,Bullet2代表超級子彈,在這里我們將bullet模塊中的Bullet2的類定義代碼補上,和Bullet1的結構完全相同:
~~~
# ====================定義超級子彈====================
class Bullet2(pygame.sprite.Sprite):
def __init__(self, position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("image/bullet2.png")
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
self.speed = 14
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
~~~
在定義好Bullet2之后,需要在主程序中實例化超級子彈。和之前普通子彈的實例化方式類似(參見之前博文),只是這里在子彈實例話的順序位置安排上有一點小技巧,先上代碼:
~~~
# ====================生成超級子彈====================
bullet2 = []
bullet2_index = 0
bullet2_num = 10 # 定義子彈實例化個數
for i in range(bullet2_num//2):
bullet2.append(bullet.Bullet2((me.rect.centerx - 33, me.rect.centery)))
bullet2.append(bullet.Bullet2((me.rect.centerx + 30, me.rect.centery)))
~~~
可見,這里的超級子彈是成對實例化的(10發子彈,分為5次循環),即敵機左邊一發右邊一發,這樣只要在將來打印的過程中一次打印相鄰的兩個子彈對象,就能產生“一次射兩發”的效果了,類似的,如何實現一次射三發、四發,大家應該也都能掌握了。
接下來需要做的就是對原有的子彈顯示程序進行修改,增加一項判斷分支,代碼如下:
~~~
if not (delay % 10):
# 播放子彈音效if not is_double_bullet:
# 發射普通子彈else: # 如果當前是超級子彈狀態
bullets = bullet2
bullets[bullet2_index].reset((me.rect.centerx - 33, me.rect.centery))
bullets[bullet2_index + 1].reset((me.rect.centerx + 30, me.rect.centery))
bullet2_index = (bullet2_index + 2) % bullet2_num
~~~
果不其然,這里在打印超級子彈的時候一次激活子彈組中的相鄰兩發子彈,即實現左右兩邊各發射一發子彈的效果。這里之所有采用bullets這個中間變量,是為了簡化接下來的子彈與敵機的碰撞檢測(不必再區分普通子彈和超級子彈)。至于(me.rect.centerx - 33, me.rect.centery)、(me.rect.centerx - 33, me.rect.centery)這兩個坐標位置也是經驗值,正好代表我方飛機的兩個發動機的位置。
6、設置超級子彈時間定時器
運行程序,上面的所有預期功能都正常事項,不過存在一個問題,就是一旦我們獲取了超級子彈補給包,則我方飛機就會無休止的發射超級子彈,這顯然是不合理的,畢竟超級武器還是超級武器,一直都能用就不能體現其珍貴了,因此我們添加一個機制,即獲得超級子彈補給包之后,超級子彈轉態只能持續一定時間,規定時間過后自動變為普通子彈。
同樣我們通過事件觸發機制完成這個功能,首先在main()函數中定義這個事件:
~~~
double_bullet_timer = USEREVENT + 1 # 超級子彈持續時間定時器
~~~
在成功獲取超級子彈補給包之后,開啟這個定時器:
~~~
if bullet_supply.active: # 如果是超級子彈補給包
pass
if pygame.sprite.collide_mask(bullet_supply, me):
pass
pygame.time.set_timer(double_bullet_timer, 18 * 1000)
~~~
可見這里我們將超級子彈的持續時間設置為18秒。然后編寫對應的事件響應函數,在響應過程中關閉超級子彈狀態即可:
~~~
# ====================檢測用戶的退出及暫停操作====================
for event in pygame.event.get(): # 響應用戶的偶然操作
if event.type == QUIT:
# 如果用戶按下屏幕上的關閉按鈕,觸發QUIT事件,程序退出elif event.type == double_bullet_timer:
is_double_bullet = False
pygame.time.set_timer(double_bullet_timer, 0)
~~~
注意這里將時間觸發時間設置為零表示關閉了這個事件,等待下一次獲得超級子彈之后再重新開啟。今天的博文就到這里,內容有點多,大家慢慢看吧。