The Nature of Code閱讀心得與Python實作:11.3 Coding Flappy Bird

更新 發佈閱讀 18 分鐘

要想用《Flappy Bird》來重現Ronald及Schoenauer的研究成果,首先必須要針對《Flappy Bird》,寫個能讓神經演化可以在其中運作的專屬版本。

在《Flappy Bird》中有兩個主角:小鳥和水管,所以實作時,我們可以用兩個類別來描述這兩個主角。我們把描述小鳥的類別叫做Bird;把描述水管的類別叫做Pipe。現在,就先來設計Bird類別。

因為在玩遊戲時,小鳥只會在同一個地點上下移動,所以x軸方向的位置是固定的,只有y軸方向的位置會變動。這也就是說,只需要一個純量就可以用來描述小鳥的位置和速度,不需要用到向量。

為了更進一步簡化程式,在計算小鳥的速度時,我們把小鳥拍翅向上飛的作用力直接加進速度中,而不是用以前的做法,也就是把作用力累加進加速度中,然後再用加速度來計算速度。除了固定會有的update()方法和show()方法之外,我們新加入一個flap()方法,用來處理小鳥拍翅向上飛這件事。完整的Bird類別程式碼如下:

class Bird:
def __init__(self):
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

self.radius = 8

# 小鳥的位置;其中x軸方向的位置是固定不變的。
self.x, self.y = 50, 120

# 因為小鳥只會上下移動,所以速度和作用力都是純量
self.velocity = 0
self.gravity = 0.5
self.flap_force = -10

def flap(self):
# 小鳥拍翅向上飛
self.velocity += self.flap_force

def update(self):
self.velocity += self.gravity
self.y += self.velocity

self.velocity *= 0.95

# 小鳥跌落地面
if self.y > self.height:
self.y = self.height
self.velocity = 0

def show(self):
# 用圓來代表小鳥
pygame.draw.circle(self.screen, (0, 0, 0), (self.x, self.y), self.radius)

在設計Bird類別時,我們用圓來代表小鳥,這樣比較省事一些;畢竟我們的重點是要讓程式能順暢運作,而不是畫隻漂亮的小鳥。

接下來,來設計Pipe類別。

在遊戲中,水管有幾個特性。首先,水管只會由右向左等速移動,而不會朝其他方向移動。所以,要描述水管的速度,就和描述小鳥的速度一樣,只需用一個純量就可以了,不需要用到向量。另外,既然水管會由右向左持續等速移動,所以它最後會移到畫面之外。

除了上述的特性之外,水管還有另一個特性:每根水管都有個長度一樣的缺口,不過缺口的位置是隨機的。

綜合以上水管的特性,描述水管的Pipe類別可以這樣設計:

class Pipe:
def __init__(self):
self.screen = pygame.display.get_surface()
self.width, self.height = self.screen.get_size()

# 水管開口長度
self.spacing = 100

# 水管開口頂部位置
self.top = random.randint(0, self.height - self.spacing)
# 水管開口底部位置
self.bottom = self.top + self.spacing

# 水管一開始的位置是在螢幕最右方
self.x = self.width

# 水管寬度
self.w = 20

# 水管會水平等速移動
self.velocity = 2

def update(self):
# 水管從右向左移動
self.x -= self.velocity

def off_screen(self):
# 水管是否已移至畫面外?
return self.x < -self.w

def show(self):
# 開口上方之水管
pipe = pygame.Rect(self.x, 0, self.w, self.top)
pygame.draw.rect(self.screen, (0, 0, 0), pipe)

# 開口下方之水管
pipe = pygame.Rect(self.x, self.bottom, self.w, self.height-self.bottom)
pygame.draw.rect(self.screen, (0, 0, 0), pipe)

Bird類別和Pipe類別設計好了,不過這樣還不算完工,還有個關鍵的部分需要處理:碰撞。

《Flappy Bird》的玩法是要讓小鳥平安穿過水管,所以我們必須不斷檢查小鳥和水管是不是產生碰撞;說得比較直白一點,就是要知道小鳥和水管有沒有撞在一起。

小鳥和水管的碰撞可以寫個collides()方法來處理。不過,這個collides()方法要放在哪呢?是放在Bird類別,還是放在Pipe類別?其實都可以啦,就看怎麼看小鳥和水管撞在一起這件事。如果認為是小鳥撞上水管,那就把collides()方法放在Bird類別中;如果認為是水管撞上小鳥,那就把collides()方法放在Pipe類別中。在這裡,我們就把collides()方法放在Pipe類別中,也就是去偵測水管有沒有撞上小鳥。

要檢查水管和小鳥有沒有發生碰撞,做法有很多種。其中一種做法,就是先檢查小鳥的高度,看看是不是位於開口之上的水管或開口之下的水管中,然後再檢查小鳥的水平位置,看看是不是位於水管的寬度範圍內。當小鳥的高度位於開口之上的水管或開口之下的水管中,而且水平位置位於水管的寬度範圍內時,那就代表水管和小鳥發生碰撞了。所以,collides()方法可以這樣寫:

def collides(self, bird):
# 小鳥的高度是否位於開口之上的水管或開口之下的水管中?
self.vertical_collision = (bird.y < self.top) or (bird.y > self.bottom)
# 小鳥的水平位置是否位於水管的寬度範圍內?
self.horiziontal_collision = self.x < bird.x < self.x+self.w

return self.vertical_collision and self.horiziontal_collision

這裡要注意一下。雖然我們是用一個圓來代表小鳥,不過在寫collides()方法時,我們其實是用圓心而不是用圓來檢查碰撞。這樣做的好處是程式可以簡化,而且會比較好寫;不過代價是碰撞的偵測不是那麼精準,有可能明明圓的邊邊已經切過水管了,但因為圓心沒有碰到水管,所以collides()方法不會偵測到發生碰撞。

設計好Bird類別和Pipe類別之後,接下來就是主程式的部分。

因為水管會由右向左不斷移動,最後會跑到畫面之外。所以,我們設定每隔100幀畫面就會有新的水管出現,免得最後畫面上都沒有水管。當然啦,跑出畫面的水管最好從程式中給移除掉,免得佔用資源。這部分程式的寫法其實和粒子系統的寫法差不多,就用一個list存放水管,跑出畫面的水管就從list中移除,要新增水管時,就用append()方法加到list中。

Example 11.1: Flappy Bird Clone

按滑鼠按鍵小鳥會往上飛。撞到水管時,會顯示「OOPS!」字樣。

vocus|新世代的創作平台
# python version 3.13.9
import random
import sys

import pygame # version 2.6.1


pygame.init()

pygame.display.set_caption("Example 11.1: Flappy Bird Clone")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

bird = Bird()
pipes = [Pipe()]

font = pygame.font.SysFont('courier', 24)
text = font.render('OOPS!', True, (0, 0, 0))

counter = 0

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 按滑鼠按鍵則小鳥拍翅向上飛
bird.flap()

screen.fill(WHITE)

for pipe in pipes:
pipe.show()
pipe.update()

for pipe in pipes:
if pipe.collides(bird):
text_rect = text.get_rect(center=(pipe.x+15, pipe.top+20))
screen.blit(text, text_rect)
break

# 將移至畫面外之水管移除?
for pipe in pipes.copy():
if pipe.off_screen():
pipes.remove(pipe)

bird.update()
bird.show()

# 每100幀畫面加入一根新水管
counter += 1
if counter == 100:
counter = 0
pipes.append(Pipe())

pygame.display.update()
frame_rate.tick(FPS)

Exercise 11.1

在畫面中以紅色字體顯示分數。

vocus|新世代的創作平台

見下圖。當小鳥由區域A進入區域B,再由區域B進入區域C,如果這期間沒有發生碰撞,那就代表小鳥安全穿越水管。

vocus|新世代的創作平台

Bird類別和Pipe類別不變,主程式如下:

# python version 3.13.9
import random
import sys

import pygame # version 2.6.1


pygame.init()

pygame.display.set_caption("Exercise 11.1")

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

bird = Bird()
pipes = [Pipe()]

font = pygame.font.SysFont('courier', 24)
text = font.render('OOPS!', True, (0, 0, 0))

counter = 0

score = 0

# 小鳥是否正在穿越水管?
going_thru = False
# 是否發生碰撞?
collide = False

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 按滑鼠按鍵則小鳥拍翅向上飛
bird.flap()

screen.fill(WHITE)

for pipe in pipes:
pipe.show()
pipe.update()

for pipe in pipes:
if pipe.collides(bird):
collide = True
text_rect = text.get_rect(center=(pipe.x+15, pipe.top+20))
screen.blit(text, text_rect)
break

for pipe in pipes:
if pipe.x <= bird.x <= pipe.x + pipe.w:
# 小鳥正在穿越水管
going_thru = True
break
else:
# 小鳥不在水管範圍內
if going_thru:
# 小鳥已穿越水管
if not collide:
score += 1

collide = False
going_thru = False

# 將移至畫面外之水管移除?
for pipe in pipes.copy():
if pipe.off_screen():
pipes.remove(pipe)

bird.update()
bird.show()

total_score = font.render(f'{score}', True, (255, 0, 0), (255, 255, 255))
score_rect = total_score.get_rect(center=(WIDTH//2, 20))
screen.blit(total_score, score_rect)

# 每100幀畫面加入一根新水管
counter += 1
if counter == 100:
counter = 0
pipes.append(Pipe())

pygame.display.update()
frame_rate.tick(FPS)


留言
avatar-img
ysf的沙龍
188會員
171內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2026/04/27
簡單介紹神經演化技術中的NEAT演算法。
2026/04/27
簡單介紹神經演化技術中的NEAT演算法。
2026/04/20
增強式學習將機器學習融入模擬環境中,讓具備類神經網路的智慧體(agent)透過與環境的互動來學習。在與環境互動的過程中,智慧體會因其決策結果的好壞而得到獎勵或懲罰;結果好的決策會得到獎勵,而結果不好的決策則會得到懲罰。透過這種棒子與胡蘿蔔恩威並施的方式,智慧體就會學到最佳的策略。
Thumbnail
2026/04/20
增強式學習將機器學習融入模擬環境中,讓具備類神經網路的智慧體(agent)透過與環境的互動來學習。在與環境互動的過程中,智慧體會因其決策結果的好壞而得到獎勵或懲罰;結果好的決策會得到獎勵,而結果不好的決策則會得到懲罰。透過這種棒子與胡蘿蔔恩威並施的方式,智慧體就會學到最佳的策略。
Thumbnail
2026/04/13
這一章要介紹的是神經演化(neuroevolution)這種結合基因演算法及類神經網路的機器學習方法。
2026/04/13
這一章要介紹的是神經演化(neuroevolution)這種結合基因演算法及類神經網路的機器學習方法。
看更多
你可能也想看
Thumbnail
5 月,方格創作島正式開島。這是一趟 28 天的創作旅程。活動期間,每週都會有新的任務地圖與陪跑計畫,從最簡單的帳號使用、沙龍建立,到帶著你從一句話、一張照片開始,一步一步找到屬於自己的創作節奏。不需要長篇大論,不需要完美的文筆,只需要帶上你今天的日常,就可以出發。征服創作島,抱回靈感與大獎!
Thumbnail
5 月,方格創作島正式開島。這是一趟 28 天的創作旅程。活動期間,每週都會有新的任務地圖與陪跑計畫,從最簡單的帳號使用、沙龍建立,到帶著你從一句話、一張照片開始,一步一步找到屬於自己的創作節奏。不需要長篇大論,不需要完美的文筆,只需要帶上你今天的日常,就可以出發。征服創作島,抱回靈感與大獎!
Thumbnail
見諸參與鄧伯宸口述,鄧湘庭於〈那個大霧的時代〉記述父親回憶,鄧伯宸因故遭受牽連,而案件核心的三人,在鄧伯宸記憶裡:「成立了成大共產黨,他們製作了五星徽章,印刷共產黨宣言——刻鋼板的——他們收集中共空飄的傳單,以及中國共產黨中央委員會有關文化大革命決議文的英文打字稿,另外還有手槍子彈十發。」
Thumbnail
見諸參與鄧伯宸口述,鄧湘庭於〈那個大霧的時代〉記述父親回憶,鄧伯宸因故遭受牽連,而案件核心的三人,在鄧伯宸記憶裡:「成立了成大共產黨,他們製作了五星徽章,印刷共產黨宣言——刻鋼板的——他們收集中共空飄的傳單,以及中國共產黨中央委員會有關文化大革命決議文的英文打字稿,另外還有手槍子彈十發。」
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
一個被蚊子激怒的夏夜,催生了我人生第一個程式專案!本文紀錄一個程式新手,如何靠著一股怨氣與勇氣,用Python打造出懷舊的「滅蚊大進擊」遊戲。分享從零到一的真實挑戰、充滿血淚的學習心得,以及最終戰勝BUG的喜悅。
Thumbnail
一個被蚊子激怒的夏夜,催生了我人生第一個程式專案!本文紀錄一個程式新手,如何靠著一股怨氣與勇氣,用Python打造出懷舊的「滅蚊大進擊」遊戲。分享從零到一的真實挑戰、充滿血淚的學習心得,以及最終戰勝BUG的喜悅。
Thumbnail
ETL是資料倉儲領域中一個重要的概念,全稱為Extract-Transform-Load,中文可譯為"抽取-轉換-載入"。ETL的作用是將來自不同來源的資料抽取出來,經過清理、轉換、整合等處理後,最終將處理好的資料載入到資料倉儲或其他單一的資料存放區
Thumbnail
ETL是資料倉儲領域中一個重要的概念,全稱為Extract-Transform-Load,中文可譯為"抽取-轉換-載入"。ETL的作用是將來自不同來源的資料抽取出來,經過清理、轉換、整合等處理後,最終將處理好的資料載入到資料倉儲或其他單一的資料存放區
Thumbnail
當代名導基里爾.賽勒布倫尼科夫身兼電影、劇場與歌劇導演,其作品流動著強烈的反叛與詩意。在俄烏戰爭爆發後,他持續以創作回應專制體制的壓迫。《傳奇:帕拉贊諾夫的十段殘篇》致敬蘇聯電影大師帕拉贊諾夫。本文作者透過媒介本質的分析,解構賽勒布倫尼科夫如何利用影劇雙棲的特質,在荒謬世道中尋找藝術的「生存之道」。
Thumbnail
當代名導基里爾.賽勒布倫尼科夫身兼電影、劇場與歌劇導演,其作品流動著強烈的反叛與詩意。在俄烏戰爭爆發後,他持續以創作回應專制體制的壓迫。《傳奇:帕拉贊諾夫的十段殘篇》致敬蘇聯電影大師帕拉贊諾夫。本文作者透過媒介本質的分析,解構賽勒布倫尼科夫如何利用影劇雙棲的特質,在荒謬世道中尋找藝術的「生存之道」。
Thumbnail
在人與人溝通之間,最怕雞同鴨講,彼此不對頻的狀況常會造成誤會。在程式語言中也會出現類似的情況,所以就有一些約定來彼此約束。 PEP 8 是 Python 社群廣泛遵循的一種風格指南,用於提高 Python 程式碼的可讀性和一致性。一開始是 Python 之父 Guido van Rossum 自己
Thumbnail
在人與人溝通之間,最怕雞同鴨講,彼此不對頻的狀況常會造成誤會。在程式語言中也會出現類似的情況,所以就有一些約定來彼此約束。 PEP 8 是 Python 社群廣泛遵循的一種風格指南,用於提高 Python 程式碼的可讀性和一致性。一開始是 Python 之父 Guido van Rossum 自己
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
關鍵字:python、 Cython、Model、C語言、加速、編譯、腳本、模組 Cython是將python轉換成C語言後執行,據說程式在C環境裡面執行速度高於python 以下就在Linux的作業系統下示範 先從下載安裝開始 使用pip3安裝Cython pip3 instal
Thumbnail
關鍵字:python、 Cython、Model、C語言、加速、編譯、腳本、模組 Cython是將python轉換成C語言後執行,據說程式在C環境裡面執行速度高於python 以下就在Linux的作業系統下示範 先從下載安裝開始 使用pip3安裝Cython pip3 instal
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
另外站長打個廣告,最近站長正在嘗試經營遊戲直播平台希望大家能夠幫忙追隨訂閱一下,站長真心感謝~ TWITCH直播: https://www.twitch.tv/saioyan Youtube: https://www.youtube.com/channel/UCtCeeanvsVdAuqNU
Thumbnail
當時間變少之後,看戲反而變得更加重要——這是在成為母親之後,我第一次誠實地面對這一件事:我沒有那麼多的晚上,可以任性地留給自己了。看戲不再只是「今天有沒有空」,而是牽動整個週末的結構,誰應該照顧孩子,我該在什麼時間回到家,隔天還有沒有精神帶小孩⋯⋯於是,我不得不學會一件以前並不擅長的事:挑選。
Thumbnail
當時間變少之後,看戲反而變得更加重要——這是在成為母親之後,我第一次誠實地面對這一件事:我沒有那麼多的晚上,可以任性地留給自己了。看戲不再只是「今天有沒有空」,而是牽動整個週末的結構,誰應該照顧孩子,我該在什麼時間回到家,隔天還有沒有精神帶小孩⋯⋯於是,我不得不學會一件以前並不擅長的事:挑選。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News