上一篇討論OOP入門技巧,本篇則以遊戲為例使用OOP開發程式,讀者也許會覺得突兀,本系列文章不是在討論【開發中大型系統的必備技巧】嗎? 【Python錦囊㊙️技10】介紹OOA先描繪實際現狀(Physical current status),再邏輯化,將實體轉化為角色(Role),而遊戲通常是由各種角色組成,在不同場景中運行,與OOA/OOD/OOP精神不謀而合,因此,有人說【遊戲開發是學習OOP最好的方式】。
圖一. 設計思維
【打磚塊】(Breakout)是一個經典的遊戲,如上圖,以乒乓球拍擊球,將所有磚塊打光就算贏了,【Breakout Tutorial using Pygame】一系列的文章以逐步加強遊戲功能的方式,帶領讀者由淺入深理解OOP概念與實作,是很棒的學習教材,筆者就草船借箭,濃縮精華,分享閱讀心得。
遊戲開發採用Pygame套件,故須先安裝套件,指令如下:
pip install pygame
範例1. Pygame最簡程式,程式名稱為pygame_0.py。
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()
python pygame_0.py
範例2. 美化視窗,建立記分板,程式名稱為pygame_1.py。
pygame.display.set_caption("打磚塊(Breakout)")
# 控制畫面更新頻率(frame per second, FPS)
clock = pygame.time.Clock()
# 監聽鍵盤及滑鼠事件
while True:
...
# 更新畫面
pygame.display.flip()
# 設定每秒60幀(FPS)
clock.tick(60)
# 設定視窗背景顏色
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))
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)
接著開始以OOP製作各種物件或角色,包括反擊板(Paddle)、球(Ball)及磚塊(Brick)類別,一律繼承pygame.sprite.Sprite,Sprite負責物件更新與移動,可確保畫面更新不會有閃爍的現象。
範例3. 創建並佈置各種物件,程式名稱為pygame_2.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
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
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)
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()
# 創建動畫(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。
python pygame_2.py
接著再加上碰撞偵測,包括球與磚塊/反擊板碰撞及撞牆的偵測,即可完成作品。
範例4. 最終成果,程式名稱為pygame_3.py,。
增加的程式碼請參見第83~130行,實作碰撞偵測,Pygame提供collide_mask、spritecollide函數偵測碰撞。
測試:
python pygame_3.py
【打磚塊】遊戲使用OOP實作反擊板(Paddle)、球(Ball)及磚塊(Brick)類別,再由主程式組合起來,就可以很簡單的寫出一個完整的遊戲,開發應用系統也是如此,透過類似積木組合的方式,元件模組化,系統就會具有彈性,例如我們要增加球的個數或改變顏色,只要多創建一個物件或改變創建的顏色屬性即可。
本系列的程式碼會統一放在GitHub,本篇的程式放在src/12資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。