講到編寫遊戲你會想到什麼程式語言呢?
大多數人可能第一時間會想到Unity之類的程式,但其實Python也能寫遊戲喔。其中Pygame就是為了使用Python寫遊戲所開發的套件,它是一個輕量的遊戲套件,能夠完成大部分遊戲所需要的功能,包括音樂管理、圖形處理及事件處理等等,適合用於開發2D遊戲。
初步了解Pygame後,就讓我們透過撰寫一個小遊戲(本文使用chrome的離線小恐龍遊戲),相信在跟著文章一步一步學習後,你會對於整體架構更有概念!
💡 補充:在撰寫pygame遊戲時需要用到大量Class(物件導向)的模組,若對物件導向不熟的可以先去網路上爬爬文再回來閱讀本文。我相信你很快就能理解物件導向的概念了!加油!
我使用的編譯器是vscode,但不一定要跟我一樣,用自己習慣的編譯器即可。
首先,我們需要先在終端機中安裝好pygame,指令如下
pip install pygame
請將此處的Assets資源包下載下來,裡面有這次遊戲製作中所需的所有圖片以及字體
資源包下載連結:
https://drive.google.com/drive/u/2/folders/1R1Fkk6mdl3G2PDu1yJJMVojc4mLXRsGq
完整程式
將assets拉進這次專案的資料夾後,創建一個名為「main.py」的檔案,這樣就設定完成了。
💡 補充:不一定要使用main來取名可依照個人習慣改名,但後面一定要是「.py」告訴電腦這是一個Python檔案
在main.py中寫一些基礎設定
import pygame
import os
import random
pygame.init()
screen_height = 600
screen_width = 1100
screen = pygame.display.set_mode((screen_width, screen_height))
第一行的import pygame
是要引用「pygame」這個套件,這樣才可以使用pygame中的函式。
接著,透過pygame.init()
來初始化 pygame。
而import os
是引用「os」套件,它的主要功能是處理檔案相關內容。
import random
會在背景移動時用到。
定義一些跟設定畫面有關的變數
screen_height
:設定畫面的高度為600screen_width
:設定畫面的寬度為1100pygame.display.set_mode((screen_width, screen_height))
:pygame的功能,藉由前面兩個高跟寬的變數設定畫面大小。
簡單來說,screen
就是一個設定好高為600、寬為1100的畫布(可以在上面顯示遊戲畫面)。
#接著上面的程式繼續寫...
RUNNING = [
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run.png")),
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run 2.png"))
]
JUMPING = pygame.image.load(os.path.join("assets/Dino", "Dino Jump.png"))
DUCKING = [
pygame.image.load(os.path.join("assets/Dino", "Dino Duck.png")),
pygame.image.load(os.path.join("assets/Dino", "Dino Duck 2.png"))
]
定義遊戲中所需要的小恐龍圖片(分別是跑步、跳躍、趴下)
pygame.image.load()
是一個Pygame中引入圖片的方式,在括號中的os.path.join()
是在「os」套件中將兩個路徑連接的函式。
舉例來說pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run.png"))
就是讀取以下這個檔案
依照這方式陸續讀取跑步、跳躍、趴下的圖片。
像是RUNNING裡有兩張圖片分別是腳抬起來以及腳放下來(如下圖),會使用一個陣列將其包起來,這樣之後的程式中就可以透過RUNNING[0]
以及RUNNING[1]
來切換動作
#接著上面的程式繼續寫...
class Dinosaur:
X_pos = 80
Y_pos = 310
Y_pos_duck = 340
set_jump_vel = 8.5
創建一個叫做「Dinosaur」的Class控制小恐龍的移動,使用變數定義一些基本設定
變數定義如下:
X_pos
:角色在畫面中的x座標Y_pos
:角色在畫面中的y座標Y_pos_duck
:由於圖片大小不同,所以需要在將趴下時的y座標稍微往下調整set_jump_vel
:目前可以先將它想成跳躍的高度(後面會在解釋用法)init 函式
#接著上面的程式繼續寫...
class Dinosaur:
#接著上面的程式繼續寫...
def __init__(self):
self.duck_img = DUCKING
self.run_img = RUNNING
self.jump_img = JUMPING
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
self.step_index = 0
self.jump_vel = self.set_jump_vel
self.image = self.run_img[0]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos
💡 備註:def __init__(self)
函式是在Class中的初始化定義,因此在此處定義所需的變數
self.duck_img
、self.run_img
及self.jump_img
只是將先前所定義好的圖片在複製一份到Class中,方便後面編寫。
初始化設定self.dino_run
為True
,而其餘的self.dino_duck
、self.dino_jump
設定為False
,代表遊戲開始時的狀態會是跑步,而不是趴下或跳躍。
self.step_index
及self.jump_vel
是在後面程式中會運用到的變數(會在文章後面解釋)。
self.image
這個變數是抓出run_img[0]
中的圖片也就是檔名為Chrome Dino Run.png的圖片
(如下圖)
💡 補充:.get_rect()
:是 Pygame 圖片物件的方法,會自動取得該圖片的“矩形”資訊,包含圖片的寬度、高度,以及位置(預設會在 (0, 0))。
self.dino_rect
會得到Chrome Dino Run.png的矩形資訊,並設定其x座標為X_pos(80)、y座標為Y_pos(310)
update函式
#接著上面的程式繼續寫...
class Dinosaur:
#接著上面的程式繼續寫...
def update(self, userInput):
if self.dino_duck:
self.duck()
if self.dino_run:
self.run()
if self.dino_jump:
self.jump()
if self.step_index >= 10:
self.step_index = 0
if userInput[pygame.K_UP] and not self.dino_jump:
self.dino_duck = False
self.dino_run = False
self.dino_jump = True
elif userInput[pygame.K_DOWN] and not self.dino_jump:
self.dino_duck = True
self.dino_run = False
self.dino_jump = False
elif not (self.dino_jump or userInput[pygame.K_DOWN]):
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
這裡函式「update」的名稱就沒有像__init__
一樣有特別規定,而是因為這個函式是用來更新角色控制狀態所以才會以update命名。userInput
通常是一個 字典 (dictionary) 或 列表 (list),其中包含使用者的輸入狀態,在這裡可以將它理解成使用者按的上下鍵就好了。
if self.dino_duck:
、if self.dino_run:
及 if self.dino_jump:
分別會在先前定義好的這三個變數為True
時執行對應的動作函式。
這個if self.step_index >= 10:
是要在step_index
≥ 10的時候將它歸零(後面用到step_index變數時會在講解)
後面三個 if 及 else if(python中的elif)的條件可以理解成
if userInput[pygame.K_UP] and not self.dino_jump:
:如果「向上鍵被按下並且不是在跳躍中」,那麼就將self.dino_jump
設定成True
其餘的設定成False
,代表恐龍正在執行跳躍的動作。elif userInput[pygame.K_DOWN] and not self.dino_jump:
:若上述條件沒有成立則會來判斷,「向下鍵被按下且不是在跳躍中」,就設定self.dino_duck
為True
其他的為False
,表示現在型態為趴下elif not (self.dino_jump or userInput[pygame.K_DOWN]):
:最後若以上條件都沒有成立,就判斷,「如果不是在跳躍也沒有按下向下鍵」,則將self.dino_run
為True
剩下的設定為False
,也就是把狀態設定回正在跑步。duck / run函式
#接著上面的程式繼續寫...
class Dinosaur:
#接著上面的程式繼續寫...
def duck(self):
self.image = self.duck_img[self.step_index // 5]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos_duck
self.step_index += 1
def run(self):
self.image = self.run_img[self.step_index // 5]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos self.step_index += 1
此函式會在上面寫的條件判斷self.dino_duck成立時被呼叫。
這裡稍稍複雜一點
要先知道在self.duck_img
這個陣列中有兩張圖片(可以回去看一下上面的程式碼),也就是說self.step_index // 5
(「//
」在Python中是無條件捨去的意思)這一串看起來很可怕的運算,其實只會有兩種可能,也就是0跟1而已 (若數值大於1則會造成資料溢位,出現IndexError: list index out of range 這個錯誤訊息)。
run函式與duck函式差不多,我就不多解釋了,但是注意self.image = self.run_img[self.step_index // 5]
這行與duck函式有稍稍不同。
這邊稍微追一下程式給各位看。
由於在程式最後會將self.step_index
加一,因此可以把self.step_index
想像成一個一直增加的變數,直到上面寫的if self.step_index >= 10:
成立就將self.step_index
歸零這樣才可以達到不斷在0跟1間切換的效果,也就是讓小恐龍看起來在跑步。
還對於這個運算式不太了解嗎?
那麼不妨自己在「這個角色移動段落結束時(寫完main函式)**」**後,試試看修改參數,把self.step_index // 5
改成 self.step_index // 2
,並且將if self.step_index >= 10:
這個判斷式改成if self.step_index >= 4:
,觀察看看恐龍步伐速度的變化,再回來看解釋時,或許能夠幫助理解~
jump函式
#接著上面的程式繼續寫...
class Dinosaur:
#接著上面的程式繼續寫...
def jump(self):
self.image = self.jump_img
if self.dino_jump:
self.dino_rect.y -= self.jump_vel * 5
self.jump_vel -= 0.85
if self.jump_vel < -self.set_jump_vel:
self.dino_jump = False
self.jump_vel = self.set_jump_vel
先將要顯示的圖片切換成跳躍的動作self.image = self.jump_img
,接著要判斷如果是在跳躍狀態,則執行跳躍的動作。
self.dino_rect.y -= self.jump_vel * 5
self.jump_vel -= 0.85
這兩橫程式碼就是在處理跳躍的動作(改變恐龍的y座標),由於解釋上必較複雜,因此我將恐龍在跳躍時的y座標及self.jump_vel
分別印出來,請觀察以下gif相信有助於你們理解。
如果還是不太懂原理的話可以自己改改看,把self.jump_vel -= 0.8
改成self.jump_vel -= 1
,並看看差異~
同樣「draw」這個名稱也是自己定義的並沒有其他特別的規則,也因為他的功能是將圖片畫到畫面上才稱視為draw。
#接著上面的程式繼續寫...
class Dinosaur:
#接著上面的程式繼續寫...
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.dino_rect.x, self.dino_rect.y))
💡 blit(image, (x, y))
SCREEN會是後來要引入的畫布。而blit函式簡單來說就是將圖片畫到畫面上需要給此函式要畫的圖片,以及圖片的xy座標。
那我們恐龍移動的Class就到這邊告一個段落。
main函式
#其他程式碼...
class Dinosaur:
...
#接在class後面寫
def main():
run = True
clock = pygame.time.Clock()
player = Dinosaur()
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
userInput = pygame.key.get_pressed()
player.draw(screen)
player.update(userInput)
clock.tick(30)
pygame.display.update()
main()
💡 補充:在 Pygame 中,Clock
是一個用於管理遊戲更新速度和時間間隔的對象。它主要用來限制畫面的刷新頻率,讓遊戲保持平穩流暢的運行,並讓每次循環(即每一個遊戲畫面更新)之間的時間間隔穩定。
前面寫了那麼多控制角色的程式,但是都還未讓整個遊戲開始運作,因此我們需要這個main函式來執行遊戲。
遊戲運作的原理就是讓畫面不斷刷新。使用while True
迴圈便能達到這個目的。
以下這段程式可以先當作是遊戲中斷的判斷就好
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
接著設定畫布顏色為白色screen.fill((255, 255, 255))
也可以自行修改背景顏色,以及設定按鍵輸入userInput = pygame.key.get_pressed()
最後,就可以呼叫我們先前寫好的draw函式跟update函式,並設定畫面更新速度為每秒30次clock.tick(30)
,然後讓pygame更新螢幕,將畫面顯示出來。
記得在main函式下呼叫他,這就是角色移動的所有部分,現在可以讓程式跑跑看,欣賞成果。
截至本段落的完整程式碼
import pygame
import os
pygame.init()
screen_height = 600
screen_width = 1100
screen = pygame.display.set_mode((screen_width, screen_height))
RUNNING = [
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run.png")),
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run 2.png"))
]
JUMPING = pygame.image.load(os.path.join("assets/Dino", "Dino Jump.png"))
DUCKING = [
pygame.image.load(os.path.join("assets/Dino", "Dino Duck.png")),
pygame.image.load(os.path.join("assets/Dino", "Dino Duck 2.png"))
]
class Dinosaur:
X_pos = 80
Y_pos = 310
Y_pos_duck = 340
set_jump_vel = 8.5
def __init__(self):
self.duck_img = DUCKING
self.run_img = RUNNING
self.jump_img = JUMPING
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
self.step_index = 0
self.jump_vel = self.set_jump_vel
self.image = self.run_img[0]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos
def update(self, userInput):
if self.dino_duck:
self.duck()
if self.dino_run:
self.run()
if self.dino_jump:
self.jump()
if self.step_index >= 20:
self.step_index = 0
if userInput[pygame.K_UP] and not self.dino_jump:
self.dino_duck = False
self.dino_run = False
self.dino_jump = True
elif userInput[pygame.K_DOWN] and not self.dino_jump:
self.dino_duck = True
self.dino_run = False
self.dino_jump = False
elif not (self.dino_jump or userInput[pygame.K_DOWN]):
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
def duck(self):
self.image = self.duck_img[self.step_index // 10]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos_duck
self.step_index += 1
def run(self):
self.image = self.run_img[self.step_index // 10]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos
self.step_index += 1
def jump(self):
self.image = self.jump_img
if self.dino_jump:
self.dino_rect.y -= self.jump_vel * 4
self.jump_vel -= 0.8
print(f"y pos: {self.dino_rect.y}, jump vel: {self.jump_vel: .2f}")
if self.jump_vel < -self.set_jump_vel:
self.dino_jump = False
self.jump_vel = self.set_jump_vel
print("jump stop", self.jump_vel)
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.dino_rect.x, self.dino_rect.y))
def main():
run = True
clock = pygame.time.Clock()
player = Dinosaur()
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
userInput = pygame.key.get_pressed()
player.draw(screen)
player.update(userInput)
clock.tick(30)
pygame.display.update()
main()
背景移動
首先,我們需要先讀取背景的圖片(地板、雲朵)
RUNNING = ...
JUMPING = ...
DUCKING = ...
#接著上面的程式繼續寫
CLOUD = pygame.image.load(os.path.join("assets/Other", "Chrome Dinosaur Cloud.png"))
BG = pygame.image.load(os.path.join("assets/Other", "Chrome Dinosaur Track.png"))
創建一個叫做Cloud的Class處理雲朵的移動
...
class Dinosaur:
...
class Cloud:
def __init__(self):
self.x = screen_width + random.randint(500, 2000)
self.y = random.randint(50, 200)
self.image = CLOUD
self.width = self.image.get_width()
def update(self):
self.x -= game_speed
if self.x < -self.width:
self.x = screen_width + random.randint(500, 1500)
self.y = random.randint(50, 200)
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.x, self.y))
💡 補充:random.randint
是 Pythonrandom
模組中的一個函式,用來產生一個指定範圍內的隨機整數。
在__init__(self):
初始化函式中,定義以下變數
self.x
:將雲初始x座標設定為,螢幕的寬度(1100)在加上500到2000中隨機的數值,也就是說雲初始時的位置會在螢幕之外,然後再飄進來。self.y
:初始化y座標設定為一個50到200之間的數值self.image
:設定圖片成雲朵self.width
:讀取雲朵的寬度update函式負責雲朵簡單的移動,不斷地將雲朵的x座標減去game_speed
,實現雲朵依照遊戲速度由右至左移動。
接著判斷若雲朵的x座標小於負的雲朵的寬度(0 - 雲朵的寬度),就代表雲朵已經完全移動至畫面外,便可以將其x座標設定回螢幕的寬度(1100)在加上500到2000中隨機的數值,以及y座標設定為一個50到200之間的數值。
...
class Dinosaur: ...
class Cloud: ...
def main():
#接著上面的程式碼寫...
#新增以下兩行
global game_speed
game_speed = 14
while run:
...
userInput = ...
#新增以下兩行
cloud.draw(screen)
cloud.update()
這樣雲朵移動的部分就處理完了,可以將程式跑起來看看效果!
再來,我們要增加的部分是地板移動的控制。
...
class Dinosaur: ...
class Cloud: ...
def main():
#接著上面的程式碼寫...
global ..., x_pos_bg, y_pos_bg
game_speed = ...
x_pos_bg = 0
y_pos_bg = 380
def background():
global x_pos_bg, y_pos_bg
image_width = BG.get_width()
screen.blit(BG, (x_pos_bg, y_pos_bg))
screen.blit(BG, (image_width + x_pos_bg, y_pos_bg))
if x_pos_bg <= -image_width:
screen.blit(BG, (image_width + x_pos_bg, y_pos_bg))
x_pos_bg = 0
x_pos_bg -= game_speed
while run:
...
userInput = ...
cloud.draw(screen)
cloud.update()
#新增以下這行
background()
其實移動地板讓地板看起來無窮無境得跑的原理,就是利用兩塊一樣的圖片銜接。講仔細一點,兩塊地板先是連接再一起的,然而當最前面那塊完全超出畫面後,便會跳回到最後面,在與前一塊地板連接。
以下是示意影片(將兩塊地板用紅色線匡起來)
截至本段落的完整程式碼
import pygame
import os
import random
pygame.init()
screen_height = 600
screen_width = 1100
screen = pygame.display.set_mode((screen_width, screen_height))
RUNNING = [
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run.png")),
pygame.image.load(os.path.join("assets/Dino", "Chrome Dino Run 2.png"))
]
JUMPING = pygame.image.load(os.path.join("assets/Dino", "Dino Jump.png"))
DUCKING = [
pygame.image.load(os.path.join("assets/Dino", "Dino Duck.png")),
pygame.image.load(os.path.join("assets/Dino", "Dino Duck 2.png"))
]
CLOUD = pygame.image.load(os.path.join("assets/Other", "Chrome Dinosaur Cloud.png"))
BG = pygame.image.load(os.path.join("assets/Other", "Chrome Dinosaur Track.png"))
class Dinosaur:
X_pos = 80
Y_pos = 310
Y_pos_duck = 340
set_jump_vel = 8.5
def __init__(self):
self.duck_img = DUCKING
self.run_img = RUNNING
self.jump_img = JUMPING
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
self.step_index = 0
self.jump_vel = self.set_jump_vel
self.image = self.run_img[0]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos
def update(self, userInput):
if self.dino_duck:
self.duck()
if self.dino_run:
self.run()
if self.dino_jump:
self.jump()
if self.step_index >= 20:
self.step_index = 0
if userInput[pygame.K_UP] and not self.dino_jump:
self.dino_duck = False
self.dino_run = False
self.dino_jump = True
elif userInput[pygame.K_DOWN] and not self.dino_jump:
self.dino_duck = True
self.dino_run = False
self.dino_jump = False
elif not (self.dino_jump or userInput[pygame.K_DOWN]):
self.dino_duck = False
self.dino_run = True
self.dino_jump = False
def duck(self):
self.image = self.duck_img[self.step_index // 10]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos_duck
self.step_index += 1
def run(self):
self.image = self.run_img[self.step_index // 10]
self.dino_rect = self.image.get_rect()
self.dino_rect.x = self.X_pos
self.dino_rect.y = self.Y_pos
self.step_index += 1
def jump(self):
self.image = self.jump_img
if self.dino_jump:
self.dino_rect.y -= self.jump_vel * 4
self.jump_vel -= 0.8
print(f"y pos: {self.dino_rect.y}, jump vel: {self.jump_vel: .2f}")
if self.jump_vel < -self.set_jump_vel:
self.dino_jump = False
self.jump_vel = self.set_jump_vel
print("jump stop", self.jump_vel)
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.dino_rect.x, self.dino_rect.y))
class Cloud:
def __init__(self):
self.x = screen_width + random.randint(500, 2000)
self.y = random.randint(50, 200)
self.image = CLOUD
self.width = self.image.get_width()
def update(self):
self.x -= game_speed
if self.x < -self.width:
self.x = screen_width + random.randint(500, 1500)
self.y = random.randint(50, 200)
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.x, self.y))
def main():
global game_speed, x_pos_bg, y_pos_bg
run = True
clock = pygame.time.Clock()
cloud = Cloud()
player = Dinosaur()
game_speed = 14
x_pos_bg = 0
y_pos_bg = 380
def background():
global x_pos_bg, y_pos_bg
image_width = BG.get_width()
bg_rect = BG.get_rect()
screen.blit(BG, (x_pos_bg, y_pos_bg))
screen.blit(BG, (image_width + x_pos_bg, y_pos_bg)
if x_pos_bg <= -image_width:
screen.blit(BG, (image_width + x_pos_bg, y_pos_bg))
x_pos_bg = 0
x_pos_bg -= game_speed
bg_rect.x = x_pos_bg
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((255, 255, 255))
userInput = pygame.key.get_pressed()
cloud.draw(screen)
cloud.update()
background()
player.draw(screen)
player.update(userInput)
clock.tick(30)
pygame.display.update()
main() screen.fill((255, 255, 255))
新增障礙物
將大、小的仙人掌以及鳥的圖片引入至程式中。
RUNNING = ...
JUMPING = ...
DUCKING = ...
CLOUD = ...
BG = ...
#接著上面的程式繼續寫
SMALL_CACTUS = [
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Small Cactus.png")),
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Small Cactus (1).png")),
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Small Cactus (2).png"))
]
LARGE_CACTUS = [
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Large Cactus.png")),
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Large Cactus (1).png")),
pygame.image.load(
os.path.join("assets/Cactus", "Chrome Dinosaur Large Cactus (2).png")),
]
BIRD = [
pygame.image.load(
os.path.join("assets/Bird", "Chrome Dinosaur Bird1.png")),
pygame.image.load(
os.path.join("assets/Bird", "Chrome Dinosaur Bird2.png")),
]
class Dinosaur: ...
class Cloud: ...
#接著上面的程式繼續寫
class Obstacle:
def __init__(self, image, type):
self.image = image
self.type = type
self.rect = self.image[self.type].get_rect()
self.rect.x = screen_width
def update(self):
self.rect.x -= game_speed
if self.rect.x < -self.rect.width:
obstacles.pop()
def draw(self, SCREEN):
SCREEN.blit(self.image[self.type], self.rect)
__init__(self)
這裡大多數的概念都與前面的相似,差別只在與多了一個選擇形狀的type變數(大、小仙人掌都有不同的形狀)。
update函式與前面幾乎一模一樣了,都是將x座標不斷的減去game_speed
達到往左移動的效果,並且在完全超出畫面時,將其從儲存障礙物的陣列中刪除。
class Dinosaur: ...
class Cloud: ...
class Obstacle:...
#接著上面的程式繼續寫
class SmallCactus(Obstacle):
def __init__(self, image):
self.type = random.randint(0, 2)
super().__init__(image, self.type)
self.rect.y = 325
class LargeCactus(Obstacle):
def __init__(self, image):
self.type = random.randint(0, 2)
super().__init__(image, self.type)
self.rect.y = 300
class Bird(Obstacle):
def __init__(self, image):
self.type = 0
super().__init__(image, self.type)
self.rect.y = 250
self.index = 0
def draw(self, SCREEN):
if self.index >= 10:
self.index = 0
SCREEN.blit(self.image[self.index//5], self.rect)
self.index += 1
SmallCactus
及LargeCactus
都會隨機選擇0到2中的一個數值(在SMALL_CACTUS
陣列中有三個元素,因此是選擇0到2這個區間),並且將這個type丟回給剛剛所寫好的Obstacle Class,最後設定其障礙物的y座標。
在Bird
中__init__(self)
與前兩個也很相似,就不多做解釋了,draw函式的部分可以參考恐龍移動的部分概念上是一樣的。
在main的函式中,我們需要先將儲存障礙物的陣列定義出來。
def main():
global ..., ..., ..., obstacles
run = ...
...
y_pos_bg = ...
obstacles = []
def main():
...
while run:
...
background()...
#新增以下程式碼
if len(obstacles) == 0:
if random.randint(0, 2) == 0:
obstacles.append(SmallCactus(SMALL_CACTUS))
elif random.randint(0, 2) == 1:
obstacles.append(LargeCactus(LARGE_CACTUS))
elif random.randint(0, 2) == 2:
obstacles.append(Bird(BIRD))
for obstacle in obstacles:
obstacle.draw(screen)
obstacle.update()
if player.dino_rect.colliderect(obstacle.rect):
pygame.draw.rect(screen, (255, 0, 0), player.dino_rect, 2)
💡 補充:在 Pygame 中,.colliderect
是一個用於檢測兩個矩形物件是否發生碰撞的函式。
那一串if
、else if
在做得事就是判斷,若儲存障礙物的陣列obstalces
是空的話,那麼就隨機將大、小仙人掌以及鳥放進去此陣列中。
確認陣列中有東西時,便利用for迴圈將陣列中所有元素都提取出來,並畫在畫面上。最後,利用pygame的.colliderect
函式判斷恐龍是否有碰到障礙物,而目前為了方面觀察碰到時,先使用框框將恐龍框起來。
分數計算以及遊戲重置畫面
定義以下變數及字體
def main():
global ..., ..., ..., obstacles, points
obstacles = ...
#新增以下變數
death_count = 0
points = 0
font = pygame.font.Font(os.path.join("assets/font", "ARCADECLASSIC.TTF"), 30)
...
def main():
...
def background():
...
#接著上面的程式寫
def score():
global points, game_speed
points += 1
if points % 100 == 0:
game_speed += 1
text = font.render(str(points), True, (94, 94, 94))
textRect = text.get_rect()
textRect.center = (1000, 60)
screen.blit(text, textRect)
while run:
...
background()
#新增以下程式
score()
for obstacle in obstacles:
obstacle.draw(screen)
obstacle.update()
if player.dino_rect.colliderect(obstacle.rect):
#pygame.draw.rect(screen, (255, 0, 0), player.dino_rect, 2)
pygame.time.delay(2000)
death_count += 1
menu(death_count)
判斷當分數 / 100
的餘數為零的時候就將game_speed
+ 1,簡單來說,就是分數每增加100就將遊戲加速。
後面就是在設定字的位置以及顏色。
在判斷是否碰到障礙物的for迴圈中將畫紅框那行註解或刪除,並增加以下幾行程式
pygame.time.delay(2000)
:遊戲暫停2秒death_count += 1
:死亡次數+1menu(death_count)
:呼叫結束畫面的函式def main():
...
#main() 記得將這裡呼叫的main函式「刪除或註解」
#接著上面的程式寫
def menu(death_count):
global points
run = True
while run:
screen.fill((255, 255, 255))
font = pygame.font.Font(os.path.join("assets/font", "ARCADECLASSIC.TTF"), 30)
if death_count == 0:
text = font.render("Press any key to start", True, (94, 94, 94))
elif death_count > 0:
text = font.render("Press any key to restart", True, (94, 94, 94))
score = font.render("Your Score " + str(int(points)), True, (94, 94, 94))
scoreRect = score.get_rect()
scoreRect.center = (screen_width // 2, screen_height // 2 + 50)
screen.blit(score, scoreRect)
textRect = text.get_rect()
textRect.center = (screen_width // 2, screen_height // 2)
screen.blit(text, textRect)
screen.blit(RUNNING[0], (screen_width // 2 - 20, screen_height // 2 - 140))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
main()
menu(death_count=0)
這一整段程式碼就是在創建這個畫面,僅此而已。
謝謝你有耐心得將這麽攏長的文章看完,也希望你在這段過程有所收穫!
完整程式碼以及assets都在github