《Flappy Bird》是一款2013年鳥飛類遊戲,由越南河內獨立遊戲開發者阮河東(Dong Nguyen)開發,另一個獨立遊戲開發商GEARS Studios發佈。此遊戲原本於2013年5月發佈,平台為iPhone 5。同年9月,作者將遊戲更新至iOS 6。於2014年1月,此遊戲成為美國及中國iTunes最受歡迎免費應用軟件,並在同月被英國App Store描述為「新憤怒鳥」。此遊戲更成為1月下載次數最多的免費應用軟件。此遊戲的下載次數至今已超過5000萬次。

Flappy Bird0. 本章重點1. 開始遊戲2. 讓鳥自由落體3. 讓鳥懂得飛起4. 製作水管4.1 物件導向與class5. 測試gameover6. 測試gameover(續)7. 新增多條水管8. 清空和減少水管列表9. 水管計分10. 考考你
複習如何用歐拉方法模擬物理加速度
物件導向, python class
Python for的另一個列表語法
程式常用技巧: 只執行一次
x1birdPos = PVector()2
3def setup():4 global birdPos5
6 size(800, 600)7 birdPos = PVector(50, height/2)8
9def draw():10 background("#70C6D5")11
12 fill("#D5BB06")13 ellipse(birdPos.x, birdPos.y, 25, 25)
首先我們如常,一個processing.py的程式,基本包括了宣告區,setup()和draw()。開一個800x600的畫面,開一個鳥的位置(birdPos)的向量變數,在setup()區中設定初始值,之後在draw()區中繪畫。
xxxxxxxxxx221birdPos = PVector() #鳥的位置2birdVec = PVector() #鳥的速度3birdAccel = PVector(0, 0.3) #鳥的向下加速度4
5def setup():6 global birdPos, birdVec7
8 size(800, 600)9 frameRate(60)10 birdPos = PVector(50, height/2)11 birdVec = PVector(0, 0)12
13def draw():14 global birdPos, birdVec15
16 background("#70C6D5")17
18 birdVec = birdVec.add(birdAccel)19 birdPos = birdPos.add(birdVec)20
21 fill("#D5BB06")22 ellipse(birdPos.x, birdPos.y, 25, 25)
跟上一章一樣,開兩個向量變數,分別是鳥的速度和加速度。在setup()區中設定初速為0,而加速度因為由始至終都不變,可以直接在宣告區中宣告其值。
1birdVec = birdVec.add(birdAccel)2birdPos = birdPos.add(birdVec)在draw()中,每次更新時都將鳥的速度加上加速度,而位置則加上速度。你可以自行調整一下加速度和frameRate(),抄到一個不會出現殘影而又夠快的速度。
xxxxxxxxxx271birdPos = PVector() #鳥的位置2birdVec = PVector() #鳥的速度3birdAccel = PVector(0, 0.3) #鳥的向下加速度4
5def setup():6 global birdPos, birdVec7
8 size(800, 600)9 frameRate(60)10 birdPos = PVector(50, height/2)11 birdVec = PVector(0, 0)12
13def draw():14 global birdPos, birdVec15
16 background("#70C6D5")17
18 birdVec = birdVec.add(birdAccel)19 birdPos = birdPos.add(birdVec)20
21 fill("#D5BB06")22 ellipse(birdPos.x, birdPos.y, 25, 25)23
24
25def keyPressed():26 if (key == ' '):27 birdVec.y = -8
xxxxxxxxxx31def keyPressed():2 if (key == ' '):3 birdVec.y = -8要令鳥懂得"飛"起來,其實只要每次按下控制按鍵時,將鳥速度的y方向變成一個常數,就可以做到類似突然升起的效果,但升起後又會因為加速度再次慢慢降下。
1birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = 08
9
10def setup():11 global birdPos, birdVec, myPipe12
13 size(800, 600)14 frameRate(60)15 birdPos = PVector(50, height/2)16 birdVec = PVector(0, 0)17
18 myPipe = Pipe()19
20
21def draw():22 global birdPos, birdVec23
24 background("#70C6D5")25
26 birdVec = birdVec.add(birdAccel)27 birdPos = birdPos.add(birdVec)28
29 fill("#D5BB06")30 ellipse(birdPos.x, birdPos.y, 25, 25)31
32 myPipe.update()33 myPipe.show()34
35
36def keyPressed():37 if (key == ' '):38 birdVec.y = -839 if (key == 'R' or key == 'r'):40 setup()41 42# =======================Pipe Object=======================================43
44class Pipe(object):45 x = 046 y = 047 w = 80 #水管的寬度48 h = 0 #水管的高度49 gap = 80 #兩水管中間的間隔50
51 def __init__(self):52 self.x = width + self.w #令水管再後一點一點53 self.y = random(100, height - 100) #下水管的左上角位置54 self.h = height55
56 def update(self):57 self.x -= panSpeed58
59 def show(self):60 fill(0, 204, 0)61 rect(self.x, self.y, self.w, self.h) #上水管62 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管
要製作水管,就要先討論一下本章最重點的學習內容:物件導向和class。
詳見這裡。
xxxxxxxxxx191class Pipe(object):2 x = 03 y = 04 w = 80 #水管的寬度5 h = 0 #水管的高度6 gap = 80 #兩水管中間的間隔7
8 def __init__(self):9 self.x = width + self.w #令水管再後一點一點10 self.y = random(100, height - 100) #下水管的左上角位置11 self.h = height12
13 def update(self):14 self.x -= panSpeed15
16 def show(self):17 fill(0, 204, 0)18 rect(self.x, self.y, self.w, self.h) #上水管19 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管建立一個class來做水管的類別。水管包括有它的x位置,y位置,寬度和高度,還有上下水管的縫隙間距。設定水管一開始在畫面外的右側再多一點點,下水管我們用-self.h令劃水管時的方向不是向下而是向上。
xxxxxxxxxx711birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = 08
9
10def setup():11 global birdPos, birdVec, myPipe12
13 size(800, 600)14 frameRate(60)15 birdPos = PVector(50, height/2)16 birdVec = PVector(0, 0)17
18 myPipe = Pipe()19
20
21def draw():22 global birdPos, birdVec23
24 background("#70C6D5")25
26 birdVec = birdVec.add(birdAccel)27 birdPos = birdPos.add(birdVec)28
29 fill("#D5BB06")30 ellipse(birdPos.x, birdPos.y, 25, 25)31
32 myPipe.update()33 myPipe.show()34 35 if myPipe.collide(birdPos) == True or birdPos.y > height:36 GAMEOVER = True37 println("Game Over")38
39
40def keyPressed():41 if (key == ' '):42 birdVec.y = -843 if (key == 'R' or key == 'r'):44 setup()45 46# =======================Pipe Object=======================================47
48class Pipe(object):49 x = 050 y = 051 w = 80 #水管的寬度52 h = 0 #水管的高度53 gap = 80 #兩水管中間的間隔54
55 def __init__(self):56 self.x = width + self.w #令水管再後一點一點57 self.y = random(100, height - 100) #下水管的左上角位置58 self.h = height59
60 def update(self):61 self.x -= panSpeed62
63 def show(self):64 fill(0, 204, 0)65 rect(self.x, self.y, self.w, self.h) #上水管66 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管67 68 def collide(self, _birdPos):69 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):70 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):71 return True
xxxxxxxxxx41def collide(self, _birdPos):2 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):3 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):4 return True在Pipe的class中新增一個函數叫collide(),這個函數有1個輸入(self不是輸入是格式),輸入鳥的位置,如果鳥的位置和水管的範圍重疊,則return True。
xxxxxxxxxx31if myPipe.collide(birdPos) == True or birdPos.y > height:2 GAMEOVER = True3 println("Game Over")之後就可以去到draw()的最後,加入這句,如果鳥撞到柱或者y座標大於height就println("Game Over")。
xxxxxxxxxx831birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = 08
9GAMEOVER = False10
11def setup():12 global birdPos, birdVec, myPipe, GAMEOVER13
14 size(800, 600)15 frameRate(60)16 birdPos = PVector(50, height/2)17 birdVec = PVector(0, 0)18
19 myPipe = Pipe()20 21 GAMEOVER = False22
23
24def draw():25 global GAMEOVER26 if (GAMEOVER == False):27 runGame()28 else:29 textSize(64)30 textAlign(CENTER, CENTER)31 fill(255, 255, 0)32 text("GAME OVER", width/2, height/2)33
34def runGame():35 global birdPos, birdVec, GAMEOVER36
37 background("#70C6D5")38
39 birdVec = birdVec.add(birdAccel)40 birdPos = birdPos.add(birdVec)41
42 fill("#D5BB06")43 ellipse(birdPos.x, birdPos.y, 25, 25)44
45 myPipe.update()46 myPipe.show()47 48 if myPipe.collide(birdPos) == True or birdPos.y > height:49 GAMEOVER = True50 println("Game Over")51
52def keyPressed():53 if (key == ' '):54 birdVec.y = -855 if (key == 'R' or key == 'r'):56 setup()57 58# =======================Pipe Object=======================================59
60class Pipe(object):61 x = 062 y = 063 w = 80 #水管的寬度64 h = 0 #水管的高度65 gap = 80 #兩水管中間的間隔66
67 def __init__(self):68 self.x = width + self.w #令水管再後一點一點69 self.y = random(100, height - 100) #下水管的左上角位置70 self.h = height71
72 def update(self):73 self.x -= panSpeed74
75 def show(self):76 fill(0, 204, 0)77 rect(self.x, self.y, self.w, self.h) #上水管78 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管79 80 def collide(self, _birdPos):81 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):82 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):83 return True
xxxxxxxxxx171def runGame():2 global birdPos, birdVec, GAMEOVER3
4 background("#70C6D5")5
6 birdVec = birdVec.add(birdAccel)7 birdPos = birdPos.add(birdVec)8
9 fill("#D5BB06")10 ellipse(birdPos.x, birdPos.y, 25, 25)11
12 myPipe.update()13 myPipe.show()14 15 if myPipe.collide(birdPos) == True or birdPos.y > height:16 GAMEOVER = True17 println("Game Over")接著,跟之前的教學一樣,開一個函數叫runGame(),將原本在draw()的內容複製到這個runGame()中。
xxxxxxxxxx81def draw():2 if not GAMEOVER:3 runGame()4 else:5 textSize(64)6 textAlign(CENTER, CENTER)7 fill(255, 255, 0)8 text("GAME OVER", width/2, height/2)而原本在draw()中,改寫為當GAMEOVER是False時遊戲才運行,否則gameover的話就在畫面上寫上文字。記得在最上面開一個新的boolean變數為GAMEOVER,而且在每個需要更新GAMEOVER的函數都要加入global
xxxxxxxxxx911birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = []8
9GAMEOVER = False10
11score = 012
13def setup():14 global birdPos, birdVec, myPipe, GAMEOVER, score15
16 size(800, 600)17 frameRate(60)18 birdPos = PVector(50, height/2)19 birdVec = PVector(0, 0)20
21 myPipe.append(Pipe())22 23 GAMEOVER = False24 25 score = 026
27def draw():28 global GAMEOVER29 if (GAMEOVER == False):30 runGame()31 else:32 textSize(64)33 textAlign(CENTER, CENTER)34 fill(255, 255, 0)35 text("GAME OVER", width/2, height/2)36
37def runGame():38 global birdPos, birdVec, GAMEOVER, score39 40 score += 141 if (score % 100 == 0):42 myPipe.append(Pipe())43
44 background("#70C6D5")45
46 birdVec = birdVec.add(birdAccel)47 birdPos = birdPos.add(birdVec)48
49 fill("#D5BB06")50 ellipse(birdPos.x, birdPos.y, 25, 25)51 52 for p in myPipe:53 p.update()54 p.show()55 56 if p.collide(birdPos) == True or birdPos.y > height:57 GAMEOVER = True58 println("Game Over")59
60def keyPressed():61 if (key == ' '):62 birdVec.y = -863 if (key == 'R' or key == 'r'):64 setup()65 66# =======================Pipe Object=======================================67
68class Pipe(object):69 x = 070 y = 071 w = 80 #水管的寬度72 h = 0 #水管的高度73 gap = 80 #兩水管中間的間隔74
75 def __init__(self):76 self.x = width + self.w #令水管再後一點一點77 self.y = random(100, height - 100) #下水管的左上角位置78 self.h = height79
80 def update(self):81 self.x -= panSpeed82
83 def show(self):84 fill(0, 204, 0)85 rect(self.x, self.y, self.w, self.h) #上水管86 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管87 88 def collide(self, _birdPos):89 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):90 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):91 return True
xxxxxxxxxx51myPipe = []2
3GAMEOVER = False4
5score = 0將原本最上方宣告區的myPipe由原本宣告成整數,變成宣告成列表。另外新增一個變數叫score來紀錄鳥飛得有多遠。
xxxxxxxxxx131def setup():2 global birdPos, birdVec, myPipe, GAMEOVER, score3
4 size(800, 600)5 frameRate(60)6 birdPos = PVector(50, height/2)7 birdVec = PVector(0, 0)8
9 myPipe.append(Pipe())10
11 GAMEOVER = False12
13 score = 0在setup()區中,用 myPipe.append(Pipe())將新的Pipe()物件新增到列表中。另外初始化GAMEOVER和score。

在runGame()中,新增每次運行時都將score加1,用來紀錄鳥飛行了多少影格。if (score % 100 == 0):的意思是score除以100,餘數為0時就執行。即每100影格就新增一條水管。
下面16-21行,跟之前幾乎是一樣的,但新增了一行for p in myPipe:。
for常見有兩種格式,一種是for i in range(n):,就是直接告訴程式將以下的程式重覆n次,每次重覆時都會將i加1。而另一種常見的格式是for obj in list:,就是直接重覆list的長度,每次重覆時,obj的內容就是對應的list[i]。

即,for obj in list:跟for i in range(len(list)):是一樣的。

但如果你按下r鍵重設遊戲時,你會發現會有上面的bugs。原因是myPipe這個列表只有新增,沒有減少,也沒有清空。
xxxxxxxxxx1011birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = []8
9GAMEOVER = False10
11score = 012
13def setup():14 global birdPos, birdVec, myPipe, GAMEOVER, score15
16 size(800, 600)17 frameRate(60)18 birdPos = PVector(50, height/2)19 birdVec = PVector(0, 0)20 21 myPipe = []22 myPipe.append(Pipe())23 24 GAMEOVER = False25 26 score = 027
28def draw():29 global GAMEOVER30 if (GAMEOVER == False):31 runGame()32 else:33 textSize(64)34 textAlign(CENTER, CENTER)35 fill(255, 255, 0)36 text("GAME OVER", width/2, height/2)37
38def runGame():39 global birdPos, birdVec, GAMEOVER, score40 41 score += 142 if (score % 100 == 0):43 myPipe.append(Pipe())44
45 background("#70C6D5")46
47 birdVec = birdVec.add(birdAccel)48 birdPos = birdPos.add(birdVec)49
50 fill("#D5BB06")51 ellipse(birdPos.x, birdPos.y, 25, 25)52 53 for p in myPipe:54 p.update()55 p.show()56 57 # if p.collide(birdPos) == True or birdPos.y > height:58 # GAMEOVER = True59 # println("Game Over")60 61 if (p.x < - p.w):62 myPipe.remove(p)63 64 # delete me after testing=============65 for p in myPipe:66 print(p)67 println(' ')68 #=====================================69
70def keyPressed():71 if (key == ' '):72 birdVec.y = -873 if (key == 'R' or key == 'r'):74 setup()75 76# =======================Pipe Object=======================================77
78class Pipe(object):79 x = 080 y = 081 w = 80 #水管的寬度82 h = 0 #水管的高度83 gap = 80 #兩水管中間的間隔84
85 def __init__(self):86 self.x = width + self.w #令水管再後一點一點87 self.y = random(100, height - 100) #下水管的左上角位置88 self.h = height89
90 def update(self):91 self.x -= panSpeed92
93 def show(self):94 fill(0, 204, 0)95 rect(self.x, self.y, self.w, self.h) #上水管96 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管97 98 def collide(self, _birdPos):99 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):100 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):101 return True
你可以留意一下console的位置,我將每次runGame()時將所有的myPipe物件的id都列印出來,然後每次都隔一行,你可以見到,每次列印出來時也只有2個id,證明程式現在懂得將超過畫面的Pipe()物件從myPipe列表中移除。
xxxxxxxxxx11myPipe = []在setup()中新增了一句。每次初始化時,都將myPipe的內容清空變成空的列表。
xxxxxxxxxx311def runGame():2 global birdPos, birdVec, GAMEOVER, score3
4 score += 15 if (score % 100 == 0):6 myPipe.append(Pipe())7
8 background("#70C6D5")9
10 birdVec = birdVec.add(birdAccel)11 birdPos = birdPos.add(birdVec)12
13 fill("#D5BB06")14 ellipse(birdPos.x, birdPos.y, 25, 25)15
16 for p in myPipe:17 p.update()18 p.show()19
20 # if p.collide(birdPos) == True or birdPos.y > height:21 # GAMEOVER = True22 # println("Game Over")23 24 if (p.x < - p.w):25 myPipe.remove(p)26 27 # delete me after testing=============28 for p in myPipe:29 print(p)30 println(' ')31 #=====================================在runGame()中,20-22行,暫時停止gameover功能方便debug,之後24-25行新增了當水管已經移出了畫面時,就在myPipe列表中移除當下這個物件。要注意的是,移除一定要在最後才執行,否則移除後下面繼續執行的都是針對下一個id)。
之後就在每次runGame()中,將所有myPipe列表的內容都列印出來,在python中列印一件物件,只會列印其id,這個id是電腦儲存這個物件的暫存記憶體地址。這一段是用來debug的,測試後沒有問題就可以刪掉。
xxxxxxxxxx1071birdPos = PVector() # 鳥的位置2birdVec = PVector() # 鳥的速度3birdAccel = PVector(0, 0.3) # 鳥的向下加速度4
5panSpeed = 56
7myPipe = []8
9GAMEOVER = False10
11score = 012pipeCount = 013
14def setup():15 global birdPos, birdVec, myPipe, GAMEOVER, score, pipeCount16
17 size(800, 600)18 frameRate(60)19 birdPos = PVector(50, height/2)20 birdVec = PVector(0, 0)21 22 myPipe = []23 myPipe.append(Pipe())24 25 GAMEOVER = False26 27 score = 028 pipeCount= 029
30def draw():31 global GAMEOVER32 if (GAMEOVER == False):33 runGame()34 else:35 textSize(64)36 textAlign(CENTER, CENTER)37 fill(255, 255, 0)38 text("GAME OVER", width/2, height/2)39
40def runGame():41 global birdPos, birdVec, GAMEOVER, score42 43 score += 144 if (score % 100 == 0):45 myPipe.append(Pipe())46
47 background("#70C6D5")48
49 birdVec = birdVec.add(birdAccel)50 birdPos = birdPos.add(birdVec)51
52 fill("#D5BB06")53 ellipse(birdPos.x, birdPos.y, 25, 25)54 55 for p in myPipe:56 p.update(birdPos)57 p.show()58 59 # if p.collide(birdPos) == True or birdPos.y > height:60 # GAMEOVER = True61 # println("Game Over")62 63 if (p.x < - p.w):64 myPipe.remove(p)65 66 textSize(60)67 fill(255, 255, 0)68 textAlign(CENTER, CENTER)69 text(pipeCount, width/2, 50)70
71def keyPressed():72 if (key == ' '):73 birdVec.y = -874 if (key == 'R' or key == 'r'):75 setup()76 77# =======================Pipe Object=======================================78
79class Pipe(object):80 x = 081 y = 082 w = 80 #水管的寬度83 h = 0 #水管的高度84 gap = 80 #兩水管中間的間隔85 isPass = False86
87 def __init__(self):88 self.x = width + self.w #令水管再後一點一點89 self.y = random(100, height - 100) #下水管的左上角位置90 self.h = height91
92 def update(self, _birdPos):93 global pipeCount94 self.x -= panSpeed95 if (self.isPass == False and _birdPos.x > self.x+self.w):96 self.isPass = True97 pipeCount += 198
99 def show(self):100 fill(0, 204, 0)101 rect(self.x, self.y, self.w, self.h) #上水管102 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管103 104 def collide(self, _birdPos):105 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):106 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):107 return True
xxxxxxxxxx291class Pipe(object):2 x = 03 y = 04 w = 80 #水管的寬度5 h = 0 #水管的高度6 gap = 80 #兩水管中間的間隔7 isPass = False8
9 def __init__(self):10 self.x = width + self.w #令水管再後一點一點11 self.y = random(100, height - 100) #下水管的左上角位置12 self.h = height13
14 def update(self, _birdPos):15 global pipeCount16 self.x -= panSpeed17 if (self.isPass == False and _birdPos.x > self.x+self.w):18 self.isPass = True19 pipeCount += 120
21 def show(self):22 fill(0, 204, 0)23 rect(self.x, self.y, self.w, self.h) #上水管24 rect(self.x, self.y - self.gap, self.w, -self.h) #下水管25 26 def collide(self, _birdPos):27 if (_birdPos.x > self.x and _birdPos.x < self.x+self.w):28 if (_birdPos.y > self.y or _birdPos.y < self.y - self.gap):29 return True在水管的class當中,加入一個變數isPassed,顧名思義是判斷鳥有否通過了水管。在update()中,加入,如果isPassed是False而且鳥的x位置大於水管的x位置加水管寬度,就將isPassed設成True並且pipeCount加一。另外你可以留意到, update()函數中間,除了self外,今次多了一個輸入是_birdPos,所以在之後更新runGame(),也要記得更新這runGame()中的b.update()變成b.update(birdPos)
P.S. : 一個編程常用的小技巧,如果你只想某段程式只執行一次,那就可以設定一個boolean變數,例如今次是False,在if的條件中,加入判斷這個變數,當變數是False時而且有其他你想要的特定條件,就執行你想要的內容,並且將這個變數設成True,那麼下次程式再執行時,由於這個變數已經是True了,就不會再執行。例如下面的例子:

xxxxxxxxxx31# if p.collide(birdPos) == True or birdPos.y > height:2 # GAMEOVER = True3 # println("Game Over")現在你可以將runGame()中,原本暫停了的這兩句重新啟動,遊戲就可以運行了。
改變變數,遊戲一開始時,水管的縫隙寬一點容易玩一點,例如是160,但當通過的水管數量越來越多,例如每通過5條水管,水管的縫隙就越來越窄,每次減少20,直到縫隙只有60
將所有有關鳥的變數和函數都打包變成一個class,跟水管的做法一樣,之後有機會教教大家用神經網絡和遺傳演算法,自製一個AI去自動玩這個遊戲。不過第一步,你可以嘗試同一時間有2隻鳥在玩,player1按spacebar來控制,player2按ENTER鍵來控制。