Arkanoid,時間回到20多年前一個夜黑風高的深夜,有位少年正坐在一台剛買不久的386電腦前,面前那本軟體世界雜誌正攤開在一篇叫”教您如何用BASIC語言製作打磚塊遊戲”的文章,他正努力的用一指神功慢慢地照著上面的範例一個字母一個字母的將指令打入電腦。。在花了數小時努力後,終於將雜誌上的所有指令輸入完畢,並帶著興奮無比的心情一邊幻想著終於可以玩到人生中第一個親手製作的遊戲,同時一邊將手指慢慢的朝執行按鍵按下。。。
在電腦發出幾聲嗶嗶聲後,畫面上顯示出N條錯誤訊息,少年驚訝的看著螢幕,腦海中浮現出幾天前在書局購買電腦書時,老闆一邊用雞毛毯子清除書上灰塵一邊淡淡的說”你嗶嗶聲聽的夠多嗎?如果聽的不夠多記的多帶幾本教如何Debugs的書!!”。。。少年這時才驚覺原來那句話是這個意思啊!!於是他立馬找出前幾天購買的書翻閱了起來,可是事情並沒想像中順利,少年又整整努力了3天,就在快要放棄的那霎那,一顆滾動的小球出現在了他那14吋的黑白螢幕中,少年感動的手足揮舞了起來。。。
有些風景需要親自經歷後才能了解箇中滋味,這初衷,也可能讓您在20幾年後的今天,被現實打擊到身心疲憊時為了鼓舞自己大聲吶喊莫忘初衷後可以立即回憶起的經歷。
以上故事純屬虛構,如有雷同實屬巧合,以下開始本次教學。
依照慣例我們一樣先來看看完成後的的遊玩影片:
以下會以”A、提案企劃 > B、執行企劃 > C、製作日誌”的順序寫作,各部份代表的分工如下:
A、提案企劃:主要用來紀錄點子,平常想到就可以陸續不斷的寫,等有專案要執行時就從中挑選出一份合適的企劃來執行。
B、執行企劃:在確定要執行的提案企劃後,接下來會在針對需要執行的內容作更完整的細節規格與製作規畫,以方便讓製作人員了解到要如何執行此專案。
C、製作日誌:執行專案時,每位製作人員會將製作時的心得、經驗與規劃方式紀錄下來,以方便往後維護時可以給自己或接手的人參考。
A、提案企劃
一句話形容這個遊戲
遊戲類型
遊戲特色
發想概念
遊戲玩法
- 玩家控制板子左右移動接住往下掉落中的球使其不要掉落到地面,並讓移動中的球擊毀畫面中的磚塊
- 清除掉畫面上所有磚塊就算過關
目標族群
發行平台
預計製作期
美術風格
遊戲周邊
製作人員需求
收費方式
製作預算
參考資料
B、執行企劃
前言
本企劃主要是以製作打磚塊遊戲為動力來學習Python語言,讓在學習程式語言時除了可以了解到遊戲的運作原理外也讓學程式不在那麼的枯燥無趣。
使用解析度
遊戲玩法
遊戲流程
遊戲畫面示意圖
細節說明
- 磚塊區的磚塊總量為11x9共99塊。
- 球會持續以45度角方向持續前進,碰到磚塊、牆壁、板子後會以45度反彈。
- 板子只能左右移動,無法上下移動。
- 玩家的任務就是控制板子接住掉落中的球,讓球反彈去擊毀畫面上的所有磚塊。
- 畫面上的全部磚塊被擊毀後,遊戲重新開始。
- 板子如果沒接到球(使其掉落板子下),球會自動重新產生在板子上。
遊戲操作
鍵盤
Esc:結束遊戲
滑鼠
左右移動:移動板子
滑鼠左鍵:開球
美術元件列表
磚塊
球
板子
C、開發日誌
開發工具
使用語言與版本
Python 3.7.0
安裝套件
在安裝套件前建議先檢查一下pip管理工具並更新,請在命令列輸入以下指令:
python -m pip install --upgrade pip
以下為所需安裝套件:
請在命令列輸入以下指令以進行安裝:
pip install pygame
第2~3天
系統分析與設計
關於繪製圖形
因為打磚塊圖形比較單純只有方形跟圓,所以決定使用pygame的基本繪圖函數就可以搞定。
關於球的移動
球的移動方向有下列四種:
右上移動
右下移動
左下移動
如何讓球反彈
可以簡單利用數學公式來作到這點,舉一個例子如果要作到以下反彈:
(1)處的移動公式為(x=x+1) (y=y+1),碰撞到牆壁後其公式變為(2)(x=x+1) (y=y-1),可以看出x持續不變(持續由左往右移動)只有y的正負號變了,由此可證我們可以簡單用正負號來處理球的反彈。
理解後我們用程式來實作,首先定義變數dx與dy來表示球移動的速度與方向,在發生左右牆碰撞的時候只要執行dx = -dx就可以改變x移動方向(正負號互換),再發生上下牆碰撞的時候只要執行dy = -dy就可以改變y移動方向(正負號互換),表示式如下:
if(發生左右牆碰撞):
dx = -dx
if(發生上下牆碰撞):
dy = -dy
接下來就簡單了,在判斷有無碰撞後再移動球,並一直重複這個流程,這樣就可以在畫面上看到不斷移動的球了。
ball_x += dx
ball_y += dy
關於磚塊與球的碰撞判斷
球每次移動後,就判斷球的中心點是否在某個磚塊的矩形區域內,如果是的話就表示球跟這個磚塊發生碰撞了。
第4~7天
程式碼說明
繪製圖形程式碼
這個物件主要封裝了pygame的兩個基本繪圖函數:
pygame.draw.rect(畫布, 顏色, [x坐標, y坐標, 寬度, 高度], 線寬)
pygame.draw.circle(畫布, 顏色, (x坐標, y坐標), 半徑, 線寬)
已將程式碼放置GitHub Gist上,點選以下連結查看:
程式碼內已寫上詳細說明
建立說明
paddle = Box( pygame物件, 畫布, 物件名稱,
[x坐標, y坐標, 寬度, 高度], 顏色)
建立範例
paddle = Box(pygame, canvas, “paddle”, [0, 0, 100, 24],(255,255,255))
- 24 ~29:判斷self.visible是否為True來更新這個矩形,反之就略過不畫。
- 31 ~59:畫圓形物件程式碼。
建立說明
ball = Circle( pygame物件, 畫布, 物件名稱, (x坐標, y坐標), 半徑, 顏色)
建立範例
ball = Circle(pygame, canvas, “ball”, [ball_x, ball_x], 8,
(255,255,255))
- 54 ~59:判斷self.visible是否為True來更新這個圓形,反之就略過不畫。
主程式
再來就是主程式了,所有打磚塊的邏輯運作與繪圖都在這邊執行,已將程式碼放置GitHub Gist上,大部分的功能說明也都在程式碼內,後面會再針對細節在個別作詳細解說:
點選以下連結查看程式原始碼:
# 函數:秀字.
def showFont( text, x, y, color):
global canvas
text = font.render(text, True, color)
canvas.blit( text, (x,y))
此函數會依照傳入的座標與顏色顯示文字。
使用說明:
showFont( 文字, x, y, 顏色)
使用範例:
showFont( u”磚塊數量” 0, 0, (255, 0, 0))
# 函數:碰撞判斷.
# x : x
# y : y
# boxRect : 矩形
def isCollision( x, y, boxRect):
if (x >= boxRect[0] and x <= boxRect[0] + boxRect[2] and y >=
boxRect[1] and y <= boxRect[1] + boxRect[3]):
return True;
return False;
此函數傳入要判斷碰撞的點座標與矩形,如果發生碰撞函數會回傳True反之回傳False
使用說明:
isCollision( x, y, 矩形)
使用範例:
if(isCollision( ball.pos[0], ball.pos[1], paddle.rect)):
。。。
# 函數:初始遊戲、重新開始遊戲.
def resetGame():
# 宣告使用全域變數.
global game_mode, brick_num, bricks_list, dx, dy
# 磚塊
for bricks in bricks_list:
# 亂數磚塊顏色
r = random.randint(100,200)
g = random.randint(100,200)
b = random.randint(100,200)
bricks.color = [r,g,b]
# 開啟磚塊.
bricks.visivle = True
# 0:等待開球
game_mode = 0
# 磚塊數量.
brick_num = 99
# 移動速度.
dx = 8
dy = -8
主要處理重新亂數產生磚塊顏色並開啟磚塊,初始遊戲模式(game_mode = 0)、初始磚塊數量(brick_num = 99)與設定球移動速度。
# 設定板子初始位置.
paddle_x = 0
paddle_y = (canvas_height — 48)
# 建立板子
paddle = Box(pygame, canvas, “paddle”, [paddle_x, paddle_y, 100, 24],
(255,255,255))
# 設定球初始位置.
ball_x = paddle_x
ball_y = paddle_y
# 建立球
ball = Circle(pygame, canvas, “ball”, [ball_x, ball_x], 8,
(255,255,255))
以迴圈產生所有磚塊並將產生的磚塊放入bricks_list陣列內。
# 初始產生磚塊變數
brick_num = 0
brick_x = 70
brick_y = 60
brick_w = 0
brick_h = 0
# 使用迴圈產生99個磚塊
for i in range( 0, 99):
# 每列11個磚塊
if((i % 11)==0):
brick_w = 0
# 每行區隔18像素
brick_h = brick_h + 18
# 建立磚塊並將其加入bricks_list陣列內
bricks_list.append (Box(pygame, canvas, “brick_”+str(i), [
brick_w + brick_x, brick_h+ brick_y, 58, 16],
[255,255,255]))
# 每個磚塊區隔60像素
brick_w = brick_w + 60
- 111~200:主程式迴圈
- 116~134:判斷鍵盤與滑鼠輸入
# 判斷輸入.
for event in pygame.event.get():
# 離開遊戲.
if event.type == pygame.QUIT:
running = False
# 判斷按下按鈕
if event.type == pygame.KEYDOWN:
# 判斷按下ESC按鈕
if event.key == pygame.K_ESCAPE:
running = False
# 判斷Mouse.
if event.type == pygame.MOUSEMOTION:
paddle_x = pygame.mouse.get_pos()[0] — 50
if event.type == pygame.MOUSEBUTTONDOWN:
if(game_mode == 0):
game_mode = 1
取得鍵盤輸入的方式為先利用以下指令取的鍵盤輸入事件:
for event in pygame.event.get():
然後再判斷事件的type是否為”按下鍵盤”事件:
if event.type == pygame.KEYDOWN:
接下來在透過事件的key來判斷玩家是按下哪個鍵盤按鈕:
if event.key == pygame.K_ESCAPE:
如上表示判斷玩家按下Esc按鈕。
再來說說判斷滑鼠的輸入事件,使用以下方式判斷滑鼠游標是否正在移動中,然後用pygame.mouse.get_pos()[0]來取得滑鼠游標移動到的X軸位置數值,並將數值指定給板子,會-50主要是讓游標指定在板子中間。
if event.type == pygame.MOUSEMOTION:
paddle_x = pygame.mouse.get_pos()[0] - 50
使用以下方式來判斷玩家是否按下滑鼠按鍵,玩家按下滑鼠按鍵後會再判斷game_mode變數是否為 0(表示等待開球中),然後將其改變為1(表示開球並繼續進行遊戲)。
if event.type == pygame.MOUSEBUTTONDOWN:
if(game_mode == 0):
game_mode = 1
在球移動中,我們需要一直與畫面上的所有磚塊作碰撞判斷,運作方式如下:
# 利用迴圈取的畫面上的所有磚塊
for bricks in bricks_list:
# 判斷球是否到碰磚塊.
if(isCollision( ball.pos[0], ball.pos[1], bricks.rect)):
# 判斷磚塊是否為顯示中.
if(bricks.visivle):
在確定球碰到畫面上的磚塊後會處理以下幾件事:
# 1.扣除磚數量.
brick_num = brick_num -1
# 2.如果畫面上的磚塊數量已經為0就執行resetGame()函數(重新開始遊戲).
if(brick_num <= 0):
resetGame()
break
# 3.處理球反彈.
dy = -dy;
# 4.關閉碰撞到的磚塊.
bricks.visivle = False
# 碰撞判斷-球碰板子.
if(isCollision( ball.pos[0], ball.pos[1], paddle.rect)):
# 球反彈.
dy = -dy;
- 171~190:依照game_mode狀態進行邏輯處理
等待開球狀態(game_mode = 0),在這個狀態下只要讓球一直跟著板子移動就可以了,如下:
# 依照板子中心位置為參考來設定球位置
# 設定球x座標
ball.pos[0] = ball_x = paddle.rect[0] + ( (paddle.rect[2] —
ball.radius) >> 1 )
# 設定球y座標
ball.pos[1] = ball_y = paddle.rect[1] — ball.radius
遊戲進行中狀態(game_mode = 1),在這狀態下主要處理讓球持續移動如下:
ball_x += dx
ball_y += dy
然後判斷球是否掉落板子後(表示死亡),其判斷方式如下:
if(ball_y + dy > canvas_height — ball.radius):
上式(ball_y + dy)表示將球移動dy後在判斷是否超出視窗高邊界(canvas_height — ball.radius),其中canvas_height標示視窗高,ball.radius表示球半徑,讓視窗高減球半徑,主要是讓其以球中心為判斷點。
接下來就是判斷上下左右是否發生碰撞並讓球反彈,其方法為以球的中心點為主,然後判斷是否超出視窗的上下左右邊界就可達成,程式碼如下:
# 右牆或左牆碰撞.
if(ball_x + dx > canvas_width — ball.radius or ball_x + dx <
ball.radius):
dx = -dx
# 下牆或上牆碰撞
if(ball_y + dy > canvas_height — ball.radius or ball_y + dy <
ball.radius):
dy = -dy
執行遊戲
- 請在命令列下輸入python play.py 以執行遊戲
GitHub下載原始碼
後記
終於又完成一篇了,雖然還不是很滿意,也覺得很多地方表達還是有待加強,加上也還在調整文章的編寫風格,所以只好請大家免強看看,相信只要持續堅持下去會越來越好的,謝謝大家,砸們下次見囉。。。
版本更新
2020/4/19
更新灰階懷舊版本(已將相關檔案更新至GitHub)
請在命令列下輸入python play_gray_scale.py以執行遊戲
主要是將繪製磚塊的顏色改成灰階色。
關於全螢幕顯示
如果要改為全畫面顯示模式只要找到以下程式碼:
# 建立畫佈大小.
canvas = pygame.display.set_mode((canvas_width, canvas_height))
並改成以下即可:
# 建立畫佈大小.
canvas = pygame.display.set_mode((canvas_width, canvas_height),
pygame.DOUBLEBUF and pygame.FULLSCREEN )