iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Python

Python 錦囊密技系列 第 12

【Python錦囊㊙️技12】OOP 實作(2) -- 遊戲開發

  • 分享至 

  • xImage
  •  

遊戲開發是學習OOP最好的方式

上一篇討論OOP入門技巧,本篇則以遊戲為例使用OOP開發程式,讀者也許會覺得突兀,本系列文章不是在討論【開發中大型系統的必備技巧】嗎? 【Python錦囊㊙️技10】介紹OOA先描繪實際現狀(Physical current status),再邏輯化,將實體轉化為角色(Role),而遊戲通常是由各種角色組成,在不同場景中運行,與OOA/OOD/OOP精神不謀而合,因此,有人說【遊戲開發是學習OOP最好的方式】。
https://ithelp.ithome.com.tw/upload/images/20240923/20001976TaqSu19gfz.png
圖一. 設計思維

打磚塊 (Breakout)

https://ithelp.ithome.com.tw/upload/images/20240925/20001976ZUjT4DRwHd.png

【打磚塊】(Breakout)是一個經典的遊戲,如上圖,以乒乓球拍擊球,將所有磚塊打光就算贏了,【Breakout Tutorial using Pygame】一系列的文章以逐步加強遊戲功能的方式,帶領讀者由淺入深理解OOP概念與實作,是很棒的學習教材,筆者就草船借箭,濃縮精華,分享閱讀心得。

邁開第一步

遊戲開發採用Pygame套件,故須先安裝套件,指令如下:

pip install pygame

範例1. Pygame最簡程式,程式名稱為pygame_0.py。

  1. Pygame 初始化:呼叫pygame.init()。
  2. 建立視窗:呼叫pygame.display.set_mode。
  3. 監聽鍵盤及滑鼠事件:收到【關閉視窗】的訊息,關閉Pygame,結束程式。
import sys, pygame

# Pygame 初始化
pygame.init()

# 建立視窗
screen = pygame.display.set_mode((320, 240))

# 監聽鍵盤及滑鼠事件
while True:
    for event in pygame.event.get():
        # 關閉視窗,程式即結束
        if event.type == pygame.QUIT: 
            pygame.quit()
            sys.exit()
  1. 測試:
python pygame_0.py
  1. 執行結果:
    https://ithelp.ithome.com.tw/upload/images/20240925/20001976x8L56XDpmU.png

範例2. 美化視窗,建立記分板,程式名稱為pygame_1.py。

  1. 設定視窗標題。
pygame.display.set_caption("打磚塊(Breakout)")
  1. 使用pygame.time.Clock,控制畫面更新頻率(frame per second, FPS)。
# 控制畫面更新頻率(frame per second, FPS)
clock = pygame.time.Clock()
 
# 監聽鍵盤及滑鼠事件
while True:
    ...
    # 更新畫面
    pygame.display.flip()
     
    # 設定每秒60幀(FPS)
    clock.tick(60)    
  1. 在迴圈內建立記分板。
    # 設定視窗背景顏色 
    screen.fill(DARKBLUE)
    # 設定視窗背景顏色 
    pygame.draw.line(screen, WHITE, [0, 38], [800, 38], 2)

    # 建立記分板
    font = pygame.font.Font(None, 34)
    text = font.render("Score: " + str(score), 1, WHITE)
    screen.blit(text, (20,10))
    text = font.render("Lives: " + str(lives), 1, WHITE)
    screen.blit(text, (650,10))
  1. 完整程式如下:
import pygame, sys

# Pygame 初始化
pygame.init()

# 設定各種顏色的RGB
WHITE = (255,255,255)
DARKBLUE = (36,90,190)
LIGHTBLUE = (0,176,240)
RED = (255,0,0)
ORANGE = (255,100,0)
YELLOW = (255,255,0)

score = 0
lives = 3

# 建立視窗
screen = pygame.display.set_mode((800, 600))
# 設定視窗標題
pygame.display.set_caption("打磚塊(Breakout)")

# 控制畫面更新頻率(frame per second, FPS)
clock = pygame.time.Clock()
 
# 監聽鍵盤及滑鼠事件
while True:
    for event in pygame.event.get(): # User did something
        # 關閉視窗,程式即結束
        if event.type == pygame.QUIT: 
              pygame.quit()
              sys.exit()
 
    # 設定視窗背景顏色 
    screen.fill(DARKBLUE)
    # 設定視窗背景顏色 
    pygame.draw.line(screen, WHITE, [0, 38], [800, 38], 2)

    # 建立記分板
    font = pygame.font.Font(None, 34)
    text = font.render("Score: " + str(score), 1, WHITE)
    screen.blit(text, (20,10))
    text = font.render("Lives: " + str(lives), 1, WHITE)
    screen.blit(text, (650,10))
 
    # 更新畫面
    pygame.display.flip()
     
    # 設定每秒60幀(FPS)
    clock.tick(60)
  1. 執行結果:
    https://ithelp.ithome.com.tw/upload/images/20240925/20001976fTCGcBwMSx.png

第二步:OOP起步

接著開始以OOP製作各種物件或角色,包括反擊板(Paddle)、球(Ball)及磚塊(Brick)類別,一律繼承pygame.sprite.Sprite,Sprite負責物件更新與移動,可確保畫面更新不會有閃爍的現象。
https://ithelp.ithome.com.tw/upload/images/20240926/20001976LzaCqw2Zis.png
範例3. 創建並佈置各種物件,程式名稱為pygame_2.py,。

  1. common.py定義各種常數。
# 設定各種顏色的RGB
BLACK = (0,0,0)
WHITE = (255,255,255)
DARKBLUE = (36,90,190)
LIGHTBLUE = (0,176,240)
RED = (255,0,0)
ORANGE = (255,100,0)
YELLOW = (255,255,0)

SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
  1. 反擊板(Paddle)類別:原本使用dataclass開發,出現【unhashable type: 'Paddle'】錯誤,應該是Pygame套件還未跟上Python腳步。Paddle只能向左/右移動,故設計兩個方法對應鍵盤左/右鍵,程式名稱為paddle_class.py。
import pygame
from common import *

# 反擊板(Paddle)類別 
class Paddle(pygame.sprite.Sprite):
    def __init__(self, color, width, height): # 顏色、寬度、高度
        # 呼叫父類別的方法
        super().__init__()
        
        # 設定反擊板顏色
        self.image = pygame.Surface([width, height])
        self.image.fill(BLACK)
        self.image.set_colorkey(BLACK) # 遇到黑色表透明(transparent)
 
        # 繪製反擊板(Paddle)
        pygame.draw.rect(self.image, color, [0, 0, width, height])
        
        # 儲存Paddle物件至self.rect
        self.rect = self.image.get_rect()
    
    # 向左移動
    def moveLeft(self, pixels):
        self.rect.x -= pixels
        if self.rect.x < 0:
          self.rect.x = 0
          
    # 向右移動
    def moveRight(self, pixels):
        self.rect.x += pixels
        if self.rect.x >= SCREEN_WIDTH-self.rect.width:
          self.rect.x = SCREEN_WIDTH-self.rect.width-1
  1. 球(Ball)類別:與Paddle大同小異,會按velocity變數移動,設計update方法計算球的最新位置,另外,還要計算碰壁後的反彈速度,程式名稱為ball_class.py。
import pygame
from random import randint
from common import *

class Ball(pygame.sprite.Sprite):
    def __init__(self, color, width, height):
        # 呼叫父類別的方法
        super().__init__()
        
        # 設定球顏色
        self.image = pygame.Surface([width, height])
        self.image.fill(BLACK)
        self.image.set_colorkey(BLACK)
 
        # 繪製球
        pygame.draw.rect(self.image, color, [0, 0, width, height])
        
        self.velocity = [randint(4,8),randint(-8,8)] # 隨機移動x、y軸距離
        
        # 儲存Ball物件至self.rect
        self.rect = self.image.get_rect()
        
    # 每一幀更新球的位置
    def update(self):
        self.rect.x += self.velocity[0]
        self.rect.y += self.velocity[1]
          
    # 設定球反彈的速度
    def bounce(self):
        self.velocity[0] = -self.velocity[0]
        self.velocity[1] = randint(-8,8)
  1. 磚塊(Brick)類別:與Paddle的處理大同小異,因位置固定,不需方法,程式名稱為brick_class.py。
import pygame
from common import *

class Brick(pygame.sprite.Sprite):
    def __init__(self, color, width, height):
        # 呼叫父類別的方法
        super().__init__()

        # 設定磚塊(Brick)顏色
        self.image = pygame.Surface([width, height])
        self.image.fill(BLACK)
        self.image.set_colorkey(BLACK)

        # 繪製磚塊(Brick)
        pygame.draw.rect(self.image, color, [0, 0, width, height])

        # 儲存Brick物件至self.rect
        self.rect = self.image.get_rect()
  1. 主程式:程式名稱為pygame_2.py,分為幾個步驟。
  • 創建動畫(sprite)群組,將反擊板(Paddle)、球(Ball)物件加入群組,以利Pygame一次更新,提高更新效率。磚塊(Brick)不加入群組,因為他們固定不動。
# 創建動畫(sprite)群組
all_sprites_list = pygame.sprite.Group()

# 創建 Paddle 物件
paddle = Paddle(LIGHTBLUE, 100, 10)
paddle.rect.x = 350
paddle.rect.y = 560

# 創建 Ball 物件
ball = Ball(WHITE,10,10)
ball.rect.x = 345
ball.rect.y = 195

# 創建三排 Brick 物件
all_bricks = pygame.sprite.Group()
for i in range(7):
    brick = Brick(RED,80,30)
    brick.rect.x = 60 + i* 100
    brick.rect.y = 60
    all_sprites_list.add(brick)
    all_bricks.add(brick)
for i in range(7):
    brick = Brick(ORANGE,80,30)
    brick.rect.x = 60 + i* 100
    brick.rect.y = 100
    all_sprites_list.add(brick)
    all_bricks.add(brick)
for i in range(7):
    brick = Brick(YELLOW,80,30)
    brick.rect.x = 60 + i* 100
    brick.rect.y = 140
    all_sprites_list.add(brick)
    all_bricks.add(brick)

# 將物件加入動畫(sprite)群組
all_sprites_list.add(paddle)
all_sprites_list.add(ball)
  • 更新動畫元件
    # 監聽鍵盤及滑鼠事件,移動反擊板(Paddle)、球(Ball)位置
    ...
    all_sprites_list.update()
    ...
    # 更新畫面
    all_sprites_list.draw(screen)
    pygame.display.flip()

完整程式請參閱pygame_2.py。

  1. 測試:可使用左右鍵移動反擊板。
python pygame_2.py
  1. 執行結果:
    https://ithelp.ithome.com.tw/upload/images/20240925/20001976rD9Mhg55v1.png

第三步:成品

接著再加上碰撞偵測,包括球與磚塊/反擊板碰撞及撞牆的偵測,即可完成作品。
範例4. 最終成果,程式名稱為pygame_3.py,。

  1. 增加的程式碼請參見第83~130行,實作碰撞偵測,Pygame提供collide_mask、spritecollide函數偵測碰撞。

  2. 測試:

python pygame_3.py
  1. 執行結果:
    https://ithelp.ithome.com.tw/upload/images/20240925/20001976rZq4DjaliN.png

結語

【打磚塊】遊戲使用OOP實作反擊板(Paddle)、球(Ball)及磚塊(Brick)類別,再由主程式組合起來,就可以很簡單的寫出一個完整的遊戲,開發應用系統也是如此,透過類似積木組合的方式,元件模組化,系統就會具有彈性,例如我們要增加球的個數或改變顏色,只要多創建一個物件或改變創建的顏色屬性即可。

本系列的程式碼會統一放在GitHub,本篇的程式放在src/12資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。


上一篇
【Python錦囊㊙️技11】OOP 實作(1) -- 入門
下一篇
【Python錦囊㊙️技13】OOP 實作(3) -- 資料庫ORM
系列文
Python 錦囊密技30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言