【Python】那些年我們一起玩過的遊戲(三)-打磚塊

閱讀時間約 20 分鐘
Arkanoid,時間回到20多年前一個夜黑風高的深夜,有位少年正坐在一台剛買不久的386電腦前,面前那本軟體世界雜誌正攤開在一篇叫”教您如何用BASIC語言製作打磚塊遊戲”的文章,他正努力的用一指神功慢慢地照著上面的範例一個字母一個字母的將指令打入電腦。。在花了數小時努力後,終於將雜誌上的所有指令輸入完畢,並帶著興奮無比的心情一邊幻想著終於可以玩到人生中第一個親手製作的遊戲,同時一邊將手指慢慢的朝執行按鍵按下。。。
在電腦發出幾聲嗶嗶聲後,畫面上顯示出N條錯誤訊息,少年驚訝的看著螢幕,腦海中浮現出幾天前在書局購買電腦書時,老闆一邊用雞毛毯子清除書上灰塵一邊淡淡的說”你嗶嗶聲聽的夠多嗎?如果聽的不夠多記的多帶幾本教如何Debugs的書!!”。。。少年這時才驚覺原來那句話是這個意思啊!!於是他立馬找出前幾天購買的書翻閱了起來,可是事情並沒想像中順利,少年又整整努力了3天,就在快要放棄的那霎那,一顆滾動的小球出現在了他那14吋的黑白螢幕中,少年感動的手足揮舞了起來。。。
有些風景需要親自經歷後才能了解箇中滋味,這初衷,也可能讓您在20幾年後的今天,被現實打擊到身心疲憊時為了鼓舞自己大聲吶喊莫忘初衷後可以立即回憶起的經歷。
以上故事純屬虛構,如有雷同實屬巧合,以下開始本次教學。
依照慣例我們一樣先來看看完成後的的遊玩影片:
以下會以”A、提案企劃 > B、執行企劃 > C、製作日誌”的順序寫作,各部份代表的分工如下:
A、提案企劃:主要用來紀錄點子,平常想到就可以陸續不斷的寫,等有專案要執行時就從中挑選出一份合適的企劃來執行。
B、執行企劃:在確定要執行的提案企劃後,接下來會在針對需要執行的內容作更完整的細節規格與製作規畫,以方便讓製作人員了解到要如何執行此專案。
C、製作日誌執行專案時,每位製作人員會將製作時的心得、經驗與規劃方式紀錄下來,以方便往後維護時可以給自己或接手的人參考。

A、提案企劃

一句話形容這個遊戲

  • 每個人心中都有一款打磚塊

遊戲類型

  • 休閒類

遊戲特色

  • 簡單易懂的玩法

發想概念

  • 藉以重製打磚塊遊戲學習Python語言

遊戲玩法

  • 玩家控制板子左右移動接住往下掉落中的球使其不要掉落到地面,並讓移動中的球擊毀畫面中的磚塊
  • 清除掉畫面上所有磚塊就算過關

目標族群

  • 想用Python學做遊戲的人

發行平台

  • Windows
  • MAC
  • Linux

預計製作期

  • 7天

美術風格

  • 區塊顏色風格

遊戲周邊

  • 手機吊飾
  • 扭蛋掌機

製作人員需求

  • 企劃 x 1
  • 美術 x 1
  • 程式 x 1

收費方式

  • 自由捐贈

製作預算

  • 繼續燒熱血當預算

參考資料

B、執行企劃

前言

本企劃主要是以製作打磚塊遊戲為動力來學習Python語言,讓在學習程式語言時除了可以了解到遊戲的運作原理外也讓學程式不在那麼的枯燥無趣。

使用解析度

  • 800 x 600

遊戲玩法

遊戲流程
遊戲畫面示意圖
細節說明
  • 磚塊區的磚塊總量為11x9共99塊。
  • 球會持續以45度角方向持續前進,碰到磚塊、牆壁、板子後會以45度反彈。
  • 板子只能左右移動,無法上下移動。
  • 玩家的任務就是控制板子接住掉落中的球,讓球反彈去擊毀畫面上的所有磚塊。
  • 畫面上的全部磚塊被擊毀後,遊戲重新開始。
  • 板子如果沒接到球(使其掉落板子下),球會自動重新產生在板子上。

遊戲操作

鍵盤
Esc:結束遊戲
滑鼠
左右移動:移動板子
滑鼠左鍵:開球

美術元件列表

磚塊
58x16
半徑8
板子
100x24

C、開發日誌

開發工具

使用語言與版本
Python 3.7.0
安裝套件
在安裝套件前建議先檢查一下pip管理工具並更新,請在命令列輸入以下指令:
python -m pip install --upgrade pip
以下為所需安裝套件:
  • PyGame - 遊戲開發套件
請在命令列輸入以下指令以進行安裝:
pip install pygame
第2~3天
系統分析與設計
關於繪製圖形
因為打磚塊圖形比較單純只有方形跟圓,所以決定使用pygame的基本繪圖函數就可以搞定。
關於球的移動
球的移動方向有下列四種:
右上移動
  • 可用公式 (x=x+1), (y=y-1)達成
右下移動
  • 可用公式 (x=x+1), (y=y+1)達成
左下移動
  • 可用公式 (x=x-1), (y=y+1)達成
如何讓球反彈
可以簡單利用數學公式來作到這點,舉一個例子如果要作到以下反彈:
(1)處的移動公式為(x=x+1) (y=y+1),碰撞到牆壁後其公式變為(2)(x=x+1) (y=y-1),可以看出x持續不變(持續由左往右移動)只有y的正負號變了,由此可證我們可以簡單用正負號來處理球的反彈。
理解後我們用程式來實作,首先定義變數dxdy來表示球移動的速度與方向,在發生左右牆碰撞的時候只要執行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上,點選以下連結查看:

【drew.py程式碼】

程式碼內已寫上詳細說明
  • 3 ~29:畫矩形物件程式碼。
建立說明
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上,大部分的功能說明也都在程式碼內,後面會再針對細節在個別作詳細解說:
點選以下連結查看程式原始碼:

【play.py程式碼】

  • 31 ~ 37:秀圖函數
# 函數:秀字. 
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))
  • 39 ~48:碰撞判斷
# 函數:碰撞判斷. 
# 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)):
。。。
  • 50 ~72:重新開始遊戲
# 函數:初始遊戲、重新開始遊戲. 
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)與設定球移動速度。
  • 86 ~89:建立板子
# 設定板子初始位置. 
paddle_x = 0 
paddle_y = (canvas_height — 48) 
# 建立板子
paddle = Box(pygame, canvas, “paddle”, [paddle_x, paddle_y, 100, 24], 
(255,255,255))
  • 91 ~94:建立球
# 設定球初始位置.
ball_x = paddle_x 
ball_y = paddle_y 
# 建立球
ball = Circle(pygame, canvas, “ball”, [ball_x, ball_x], 8,
 (255,255,255))
  • 96 ~107:建立磚塊
以迴圈產生所有磚塊並將產生的磚塊放入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
  • 140~157:判斷球是否碰撞到磚塊
在球移動中,我們需要一直與畫面上的所有磚塊作碰撞判斷,運作方式如下:
# 利用迴圈取的畫面上的所有磚塊 
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
  • 166~169:球碰到板子處理讓球反彈
# 碰撞判斷-球碰板子. 
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 )
為什麼會看到廣告
5會員
16內容數
留言0
查看全部
發表第一個留言支持創作者!
無限升級的沙龍 的其他內容
Tetris,沒錯這次要聊的主題就是俄羅斯方塊(Tetris)也是小老弟我從小玩到大的最愛遊戲之一,每次只要學習新程式語言就會在祭出來在給他狠狠地致敬一下,俄羅斯方塊以其簡單易懂的遊戲規則,往往讓人一玩就無法自拔。。。剛說到哪裡了,什麼教程,先別吵,等我玩完這局俄羅斯方塊再說。。。
可能是初老症發作的的原因,最近玩到這類懷舊遊戲就很容易陷入某種懷舊時光的情緒中,一不小心就會玩到忘了時間,所以就興起了何不自己動手把童年那些有印象的街機遊戲全部實作一遍的念頭呢?不囉嗦以下就是這系列的第一款。。。
Blockade(封鎖線)是一款在1976年街機上發行的對戰遊戲,其簡單易懂的玩法,在當時可讓不少人為之瘋狂,這也是我們本次教學的主角,借由重製這款經典遊戲來學習如何使用Family BASIC開發遊戲,讓經典再現。
緣起 場景回到1986年鄉下的一家遊戲專賣店,一群小朋友圍著老闆正聽他口沫橫飛的說著,我跟你們說阿這卡夾還有這個鍵盤組合叫Family BASIC(註1)是用來開發紅白機遊戲用的,是我託廠商從日本弄回來的。。。
Tetris,沒錯這次要聊的主題就是俄羅斯方塊(Tetris)也是小老弟我從小玩到大的最愛遊戲之一,每次只要學習新程式語言就會在祭出來在給他狠狠地致敬一下,俄羅斯方塊以其簡單易懂的遊戲規則,往往讓人一玩就無法自拔。。。剛說到哪裡了,什麼教程,先別吵,等我玩完這局俄羅斯方塊再說。。。
可能是初老症發作的的原因,最近玩到這類懷舊遊戲就很容易陷入某種懷舊時光的情緒中,一不小心就會玩到忘了時間,所以就興起了何不自己動手把童年那些有印象的街機遊戲全部實作一遍的念頭呢?不囉嗦以下就是這系列的第一款。。。
Blockade(封鎖線)是一款在1976年街機上發行的對戰遊戲,其簡單易懂的玩法,在當時可讓不少人為之瘋狂,這也是我們本次教學的主角,借由重製這款經典遊戲來學習如何使用Family BASIC開發遊戲,讓經典再現。
緣起 場景回到1986年鄉下的一家遊戲專賣店,一群小朋友圍著老闆正聽他口沫橫飛的說著,我跟你們說阿這卡夾還有這個鍵盤組合叫Family BASIC(註1)是用來開發紅白機遊戲用的,是我託廠商從日本弄回來的。。。
你可能也想看
Google News 追蹤
Thumbnail
在讀取檔案時,最怕路徑的問題,常常會有路徑錯誤造成的異常報錯。 為了避免諸如此類的問題發生,明白程式的當前目錄與檔案的路徑是很重要的。 可以利用os 模組是 Python 中的一個標準庫,提供了許多與操作系統的功能。 以下是一些常用的 os 模組基本操作及其範例: 1. os.getcwd
解讀JSON 字串 首先,你需要使用 Python 的 json 模組來解讀JSON 字串。 JSON的基本結構: 由花括號 {} 包圍,內部是鍵值對的集合,每個鍵值對之間用逗號分隔。 鍵是字串類型,值可以是任何JSON支持的資料類型(字串、數字、布林值、陣列、物件或 null)。 {
Thumbnail
在我們正式開始Python串接LINE Bot的教程之前,有必要先為大家準備好所需的工具。實際上,製作LINE Bot的工具有很多選擇,我將會介紹我在製作過程中所使用的具體工具有哪些,以及如何進行事前的準備工作。讓我們一起走進這個有趣的製作過程吧!
Thumbnail
每當要研究投資標的時,你是否也會覺得A網站的財報資料完整,又覺得B網站的選股條件完善,又覺得C網站的新聞比較重要,有時身邊又沒有隨身攜帶筆電怎麼好查詢呢?這時可能就在想怎麼沒有人開發一個程式能完成符合我想要的功能啊~~與其求助於人,不如自己動手做最好,有相同煩惱的人,LINE Bot超適合你!!
Thumbnail
在看官網文件時,看到一份文件:PEP 8 -- Style Guide for Python Code。這份文件是關於Python程式碼風格的指引和建議。
Thumbnail
Selenium WebDriver Page Object Model (POM) 設計模式,若是有開發過 UI 自動化測試或是寫網路爬蟲的多少都有聽過或是看過吧!  最近剛好有幸接觸到某外商的 SDET 面試考試,題目要求使用 POM 設計 UI 自動化測試,又再度幫自己複習一遍。 Why?
Thumbnail
畢竟~我是一個懶人嘛... 估狗之後,大部分的做法就是手動的去下載當前瀏覽器對應版本的 Driver,放到一個特定位置,將 Driver Binary 位置指向它。 在 Google Chrome About Page 你可以看到版本 接著去下載對應的 Chrome Driver ↓ 解決方案:
Thumbnail
在讀取檔案時,最怕路徑的問題,常常會有路徑錯誤造成的異常報錯。 為了避免諸如此類的問題發生,明白程式的當前目錄與檔案的路徑是很重要的。 可以利用os 模組是 Python 中的一個標準庫,提供了許多與操作系統的功能。 以下是一些常用的 os 模組基本操作及其範例: 1. os.getcwd
解讀JSON 字串 首先,你需要使用 Python 的 json 模組來解讀JSON 字串。 JSON的基本結構: 由花括號 {} 包圍,內部是鍵值對的集合,每個鍵值對之間用逗號分隔。 鍵是字串類型,值可以是任何JSON支持的資料類型(字串、數字、布林值、陣列、物件或 null)。 {
Thumbnail
在我們正式開始Python串接LINE Bot的教程之前,有必要先為大家準備好所需的工具。實際上,製作LINE Bot的工具有很多選擇,我將會介紹我在製作過程中所使用的具體工具有哪些,以及如何進行事前的準備工作。讓我們一起走進這個有趣的製作過程吧!
Thumbnail
每當要研究投資標的時,你是否也會覺得A網站的財報資料完整,又覺得B網站的選股條件完善,又覺得C網站的新聞比較重要,有時身邊又沒有隨身攜帶筆電怎麼好查詢呢?這時可能就在想怎麼沒有人開發一個程式能完成符合我想要的功能啊~~與其求助於人,不如自己動手做最好,有相同煩惱的人,LINE Bot超適合你!!
Thumbnail
在看官網文件時,看到一份文件:PEP 8 -- Style Guide for Python Code。這份文件是關於Python程式碼風格的指引和建議。
Thumbnail
Selenium WebDriver Page Object Model (POM) 設計模式,若是有開發過 UI 自動化測試或是寫網路爬蟲的多少都有聽過或是看過吧!  最近剛好有幸接觸到某外商的 SDET 面試考試,題目要求使用 POM 設計 UI 自動化測試,又再度幫自己複習一遍。 Why?
Thumbnail
畢竟~我是一個懶人嘛... 估狗之後,大部分的做法就是手動的去下載當前瀏覽器對應版本的 Driver,放到一個特定位置,將 Driver Binary 位置指向它。 在 Google Chrome About Page 你可以看到版本 接著去下載對應的 Chrome Driver ↓ 解決方案: