在本系列的第五部分,學習如何增加一個壞蛋與你的好人戰鬥。
在本系列的前幾篇文章中(參見 、、 以及 ),你已經學習了如何使用 Pygame 和 Python 在一個空白的電動遊戲世界中生成一個可玩的角色。但沒有惡棍,英雄又將如何?
如果你沒有敵人,那將會是一個非常無聊的遊戲。所以在此篇文章中,你將為你的遊戲新增一個敵人並構建一個用於建立關卡的框架。
在對玩家妖精實現全部功能之前,就來實現一個敵人似乎就很奇怪。但你已經學到了很多東西,創造惡棍與與創造玩家妖精非常相似。所以放輕鬆,使用你已經掌握的知識,看看能挑起怎樣一些麻煩。
針對本次訓練,你能夠從 Open Game Art 下載一些預建立的素材。此處是我使用的一些素材:
是的,不管你意識到與否,你其實已經知道如何去實現敵人。這個過程與創造一個玩家妖精非常相似:
update
方法使得敵人能夠檢測碰撞move
方法使得敵人能夠四處遊蕩從類入手。從概念上看,它與你的 Player
類大體相同。你設定一張或者一組圖片,然後設定妖精的初始位置。
在繼續下一步之前,確保你有一張你的敵人的影象,即使只是一張臨時影象。將影象放在你的遊戲專案的 images
目錄(你放置你的玩家影象的相同目錄)。
如果所有的活物都擁有動畫,那麼遊戲看起來會好得多。為敵方妖精設定動畫與為玩家妖精設定動畫具有相同的方式。但現在,為了保持簡單,我們使用一個沒有動畫的妖精。
在你程式碼 objects
節的頂部,使用以下程式碼建立一個叫做 Enemy
的類:
class Enemy(pygame.sprite.Sprite): ''' 生成一個敵人 ''' def __init__(self,x,y,img): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(os.path.join('images',img)) self.image.convert_alpha() self.image.set_colorkey(ALPHA) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y
如果你想讓你的敵人動起來,使用讓你的玩家擁有動畫的 。
你能夠通過告訴類,妖精應使用哪張影象,應出現在世界上的什麼地方,來生成不只一個敵人。這意味著,你能夠使用相同的敵人類,在遊戲世界的任意地方生成任意數量的敵方妖精。你需要做的僅僅是呼叫這個類,並告訴它應使用哪張影象,以及你期望生成點的 X 和 Y 坐標。
再次,這從原則上與生成一個玩家精靈相似。在你指令碼的 setup
節新增如下程式碼:
enemy = Enemy(20,200,'yeti.png') # 生成敵人enemy_list = pygame.sprite.Group() # 建立敵人組enemy_list.add(enemy) # 將敵人加入敵人組
在範例程式碼中,X 坐標為 20,Y 坐標為 200。你可能需要根據你的敵方妖精的大小,來調整這些數位,但盡量生成在一個範圍內,使得你的玩家妖精能夠碰到它。Yeti.png
是用於敵人的影象。
接下來,將敵人組的所有敵人繪製在螢幕上。現在,你只有一個敵人,如果你想要更多你可以稍後新增。一但你將一個敵人加入敵人組,它就會在主迴圈中被繪製在螢幕上。中間這一行是你需要新增的新行:
player_list.draw(world) enemy_list.draw(world) # 重新整理敵人 pygame.display.flip()
啟動你的遊戲,你的敵人會出現在遊戲世界中你選擇的 X 和 Y 坐標處。
你的遊戲仍處在襁褓期,但你可能想要為它新增另一個關卡。為你的程式做好未來規劃非常重要,因為隨著你學會更多的程式設計技巧,你的程式也會隨之成長。即使你現在仍沒有一個完整的關卡,你也應該按照假設會有很多關卡來程式設計。
思考一下“關卡”是什麼。你如何知道你是在遊戲中的一個特定關卡中呢?
你可以把關卡想成一系列專案的集合。就像你剛剛建立的這個平台中,一個關卡,包含了平台、敵人放置、戰利品等的一個特定排列。你可以建立一個類,用來在你的玩家附近建立關卡。最終,當你建立了一個以上的關卡,你就可以在你的玩家達到特定目標時,使用這個類生成下一個關卡。
將你寫的用於生成敵人及其群組的程式碼,移動到一個每次生成新關卡時都會被呼叫的新函數中。你需要做一些修改,使得每次你建立新關卡時,你都能夠建立一些敵人。
class Level(): def bad(lvl,eloc): if lvl == 1: enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敵人 enemy_list = pygame.sprite.Group() # 生成敵人組 enemy_list.add(enemy) # 將敵人加入敵人組 if lvl == 2: print("Level " + str(lvl) ) return enemy_list
return
語句確保了當你呼叫 Level.bad
方法時,你將會得到一個 enemy_list
變數包含了所有你定義的敵人。
因為你現在將創造敵人作為每個關卡的一部分,你的 setup
部分也需要做些更改。不同於創造一個敵人,取而代之的是你必須去定義敵人在那裡生成,以及敵人屬於哪個關卡。
eloc = []eloc = [200,20]enemy_list = Level.bad( 1, eloc )
再次執行遊戲來確認你的關卡生成正確。與往常一樣,你應該會看到你的玩家,並且能看到你在本章節中新增的敵人。
一個敵人如果對玩家沒有效果,那麼它不太算得上是一個敵人。當玩家與敵人發生碰撞時,他們通常會對玩家造成傷害。
因為你可能想要去跟蹤玩家的生命值,因此碰撞檢測發生在 Player
類,而不是 Enemy
類中。當然如果你想,你也可以跟蹤敵人的生命值。它們之間的邏輯與程式碼大體相似,現在,我們只需要跟蹤玩家的生命值。
為了跟蹤玩家的生命值,你必須為它確定一個變數。程式碼範例中的第一行是上下文提示,那麼將第二行程式碼新增到你的 Player 類中:
self.frame = 0 self.health = 10
在你 Player
類的 update
方法中,新增如下程式碼塊:
hit_list = pygame.sprite.spritecollide(self, enemy_list, False) for enemy in hit_list: self.health -= 1 print(self.health)
這段程式碼使用 Pygame 的 sprite.spritecollide
方法,建立了一個碰撞檢測器,稱作 enemy_hit
。每當它的父類別妖精(生成檢測器的玩家妖精)的碰撞區觸碰到 enemy_list
中的任一妖精的碰撞區時,碰撞檢測器都會發出一個信號。當這個信號被接收,for
迴圈就會被觸發,同時扣除一點玩家生命值。
一旦這段程式碼出現在你 Player
類的 update
方法,並且 update
方法在你的主迴圈中被呼叫,Pygame 會在每個時鐘滴答中檢測一次碰撞。
如果你願意,靜止不動的敵人也可以很有用,比如能夠對你的玩家造成傷害的尖刺和陷阱。但如果敵人能夠四處徘徊,那麼遊戲將更富有挑戰。
與玩家妖精不同,敵方妖精不是由玩家控制,因此它必須自動移動。
最終,你的遊戲世界將會捲動。那麼,如何在遊戲世界自身捲動的情況下,使遊戲世界中的敵人前後移動呢?
舉個例子,你告訴你的敵方妖精向右移動 10 步,向左移動 10 步。但敵方妖精不會計數,因此你需要建立一個變數來跟蹤你的敵人已經移動了多少步,並根據計數變數的值來向左或向右移動你的敵人。
首先,在你的 Enemy
類中建立計數變數。新增以下程式碼範例中的最後一行程式碼:
self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.counter = 0 # 計數變數
然後,在你的 Enemy
類中建立一個 move
方法。使用 if-else 迴圈來建立一個所謂的死迴圈:
死迴圈沒有終點,因為迴圈判斷條件永遠為真,所以它將永遠迴圈下去。在此情況下,計數器總是介於 0 到 100 或 100 到 200 之間,因此敵人會永遠地從左向右再從右向左移動。
你用於敵人在每個方向上移動距離的具體值,取決於你的螢幕尺寸,更確切地說,取決於你的敵人移動的平台大小。從較小的值開始,依據習慣逐步提高數值。首先進行如下嘗試:
def move(self): ''' 敵人移動 ''' distance = 80 speed = 8 if self.counter >= 0 and self.counter <= distance: self.rect.x += speed elif self.counter >= distance and self.counter <= distance*2: self.rect.x -= speed else: self.counter = 0 self.counter += 1
你可以根據需要調整距離和速度。
當你現在啟動遊戲,這段程式碼有效果嗎?
當然不,你應該也知道原因。你必須在主迴圈中呼叫 move
方法。如下範例程式碼中的第一行是上下文提示,那麼新增最後兩行程式碼:
enemy_list.draw(world) #refresh enemy for e in enemy_list: e.move()
啟動你的遊戲看看當你打擊敵人時發生了什麼。你可能需要調整妖精的生成地點,使得你的玩家和敵人能夠碰撞。當他們發生碰撞時,檢視 IDLE 或 Ninja-IDE 的控制台,你可以看到生命值正在被扣除。
你應該已經注意到,在你的玩家和敵人接觸時,生命值在時刻被扣除。這是一個問題,但你將在對 Python 進行更多練習以後解決它。
現在,嘗試新增更多敵人。記得將每個敵人加入 enemy_list
。作為一個練習,看看你能否想到如何改變不同敵方妖精的移動距離。