# Python Pygame 語法介紹(1)
## 簡介
**Pygame**是**Python**的一個遊戲開發函式庫,提供了簡單的介面來處理視窗管理、圖形渲染、事件處理及音效播放等功能
## 安裝 Pygame
在開始使用**Pygame**前,我們得先安裝他,在終端機輸入以下指令來安裝
```
pip install pygame
```
>若安裝失敗請確認`pip`是否有正常執行
## Pygame 語法
### 導入和初始化
在程式的一開始,我們需要導入並初始化`pygame`,否則系統可能會報錯
```python
#導入pygame
import pygame
#初始化pygame
pygame.init()
```
### 建立與管理視窗
在`pygame`中,主要由`pygame.display`負責管理視窗的顯示,提供了許多方法來處理畫面更新、視窗控制、全螢幕模式等。以下是相關指令的介紹
- `pygame.display.init()`:初始化`pygame.display`(通常`pygame.init()`會自動執行)
- `pygame.display.get_init`:檢查`pygame.display`是否已初始化,回傳`True`或`False`
- `pygame.display.set_mode(size, flags=0, depth=0, display=0, vsync=0)`:設定視窗的大小和類型
- `size`:必填,設定視窗的大小
- 格式: `(寬度, 高度)`(單位:像素)
- 範例:`pygame.display.set_mode((800, 600))`
>→ 創建一個 800×600 的視窗
- `flags`:選填,控制視窗模式,且可同時存在多個`flags`
- 預設值`0`:普通視窗
- 可用的`flags`:
|`flags`|功能|
|-------------------|---------------------|
|`pygame.FULLSCREEN`|全螢幕模式 |
|`pygame.RESIZABLE` |允許使用者調整視窗大小 |
|`pygame.NOFRAME` |無邊框視窗 |
|`pygame.SCALED` |自動縮放畫面以適應視窗大小|
|`pygame.HWSURFACE` |使用硬體加速(僅限 `pygame.FULLSCREEN`可用)|
|`pygame.DOUBLEBUF` |啟用雙緩衝(建議與`pygame.HWSURFACE`或`pygame.OPENGL`一起使用)|
|`pygame.OPENGL` |使用 OpenGL 渲染 |
>[!Note]名詞補充
>雙緩衝 (Double Buffering) 是一種圖形渲染技術,目的是減少畫面閃爍並提升視覺流暢度
>OpenGL(Open Graphics Library)渲染是指使用 OpenGL API 來將 3D 模型與場景轉換成影像並顯示在螢幕上的過程
- `depth`:選填,設定色彩深度(單位:bit)
- 預設值`0`: 讓系統自動選擇最佳的色彩深度
- `display`:選填,指定使用哪一個顯示器(僅在**多螢幕系統**上適用)
- 預設值`0`:使用主顯示器
- `vsync`:選填,啟用或停用**垂直同步(VSync)**
- 可用的值:
- `0`:預設,不使用垂直同步
- `1`:啟用垂直同步
- `-1`:讓系統自動決定是否啟用(部分系統和驅動支援)
>[!Important]注意
>在設定參數時許注意格式統一,否則系統將會報錯
>以下為錯誤示範
>```
>pygame.display.set_mode(size=(800,800), pygame.FULLSCREEN)
>```
>
>另外,也要注意各個參數之間的搭配,否則可能會引起系統故障
- `pygame.display.quit()`:關閉視窗(不等於關閉`pygame`)
- `pygame.display.flip()`:更新整個視窗
- `pygame.display.update(rect_list)`:更新視窗內部份區域
- `rect_list`:
- `rect_list=None`:預設值,若**不提供`rect_list`參數**,則效果與`pygame.display.flip()`相同,會更新整個畫面
- `rect_list`是一個`Rect`物件列表:當`rect_list`是一個 list(包含多個`pygame.Rect`物件)時,只會更新這些區域,提升效能
- `pygame.display.set_caption(title)`:設定視窗名稱
- `pygame.display.get_caption()`:取得視窗名稱,回傳`(title, icontitle)`
- `pygame.display.set_icon(icon_surface)`:設定視窗圖示,接受一個`pygame.Surface`物件或圖片作為圖示,步驟如下
1. 準備圖示圖片:首先,你需要準備一張 小尺寸的圖示圖片(如 icon.png),建議尺寸為 32×32 或 64×64 像素,並且圖片格式為 PNG(建議使用透明背景)
2. 載入圖片並設定圖示:
```python
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# 載入圖示,確保 icon.png 放在程式的相同目錄下
# 也可以直接給予路徑
icon = pygame.image.load("icon.png")
# 設定視窗圖示
pygame.display.set_icon(icon)
```
>[!Note]補充
>如果你的圖示太大(例如 256x256),Pygame不會自動縮小,可能會出現問題,這時可以使用`pygame.transform.scale()`來縮小圖示
>```python
>icon = pygame.image.load("icon.png")
>#調整為 32×32
>icon = pygame.transform.scale(icon, (32, 32))
>pygame.display.set_icon(icon)
>```
>這樣可以確保你的圖示適合視窗
- `pygame.display.get_surface()`:取得目前視窗本身的`Surface`(用於直接繪製)
>[!Note]視窗本身的`Surface`是什麼?
>Pygame的**視窗本身就是一個`Surface`物件**,它來自`pygame.display.set_mode()`
>當你執行:
>```python
>screen = pygame.display.set_mode((800, 600))
>```
>這行程式碼會:
>1. 建立一個 800×600 的Pygame視窗
>2. 回傳一個`Surface`物件,存入變數`screen`
>3. `screen`就是這個**視窗的`Surface`**,你可以在上面畫東西
>
>這個`Surface`代表遊戲視窗上的所有像素。你可以把它想像成一張**可以繪製的畫布**,所有的圖案、圖片和文字都必須畫在這個`Surface`上,最後再透過`pygame.display.update()`或 `pygame.display.flip()`顯示到螢幕上
>
>當你使用`pygame.display.get_surface()`,你取得的是**視窗本身的`Surface`物件**,這表示:
>1. 你可以直接在視窗上畫圖,無需建立新的`Surface`
>2. 你畫的內容會直接影響視窗內容(但仍需更新視窗才能顯示)
- `pygame.display.iconify()`:最小化視窗
- `pygame.display.toggle_fullscreen()`:切換全螢幕模式,主要適用於 Linux(X11 視窗系統)
>[!Caution]警告
>在 Windows 和 macOS 上可能無法正常運作,甚至會導致視窗異常,建議改用`pygame.display.set_mode()`來切換全螢幕模式
:::success
- `.fill(color)`:將`Surface`物件填滿顏色,在前面我們有提到視窗本身就是一個`Surface`物件,所以我們可以用`.fill()`來改變視窗的顏色
- 範例:
```python
screen = pygame.display.set_mode((500, 300))
screen.fill((0, 0, 0))# 黑色背景
```
:::
### 繪製圖形
在`pygame`中,主要由`pygame.draw`來負責繪製圖形,提供了一系列用於繪製基本圖形的函式
>[!Note]螢幕座標系統
>在開始介紹函式前我們要先來說明一下螢幕座標系統
>螢幕座標系統是一種 二維 (2D) 坐標系統,用來定位電腦螢幕上的像素 (pixel) 位置
>
>螢幕座標的基本特性:
>- 原點 (0,0) 在左上角
> 與數學坐標系統(原點通常在中心)不同,螢幕座標系統的原點通常定義在**螢幕的左上角**
>- X軸向右遞增
> 從左往右,X值增加
>- Y軸向下遞增
> 從上往下,Y值增加,這與數學座標系統相反,數學座標系統通常是Y軸向上遞增
>
>示意圖
>|0,0|1,0|2,0|
>|---|---|---|
>|0,1|1,1|2,1|
>|0,2|1,2|2,2|
>[!Tip]`pygame.draw`繪圖函式的特性
>這些函式的第一個參數通常是`surface`,表示要繪製的目標畫布,顏色則使用`(R, G, B)`或`(R, G, B, A)`格式,`A`為透明度,另外,**所有繪圖函式都會回傳`pygame.Rect`物件**,代表繪製區域的範圍,可配合`pygame.display.update()`使用
- `pygame.draw.line(surface, color, start_pos, end_pos, width=1)`:畫一條線
- `start_pos`:線段的起點座標`(x, y)`
- `end_pos`:線段的終點座標`(x, y)`
- `width`:線條寬度,預設`1`
- `pygame.draw.lines(surface, color, closed, point_list, width=1)`:畫多條相連的線
- `closed`:若為`True`,則最後一點會連回第一點形成封閉區域
- `point_list`:點座標的列表`[(x1, y1), (x2, y2), ...]`
- `width`:線條寬度
- `pygame.draw.aaline(surface, color, start_pos, end_pos, blend=1)`:畫一條抗鋸齒的線(較平滑)
- `blend`:若為`1`,則啟用抗鋸齒效果
- `pygame.draw.aalines(surface, color, closed, point_list, blend=1)`:畫多條相連的抗鋸齒線
>[!Note]什麼是抗鋸齒?
>抗鋸齒(Anti-Aliasing,AA)是一種圖形技術,主要用來減少電腦圖像中因解析度限制而產生的鋸齒狀(Jagged Edges)效果,使邊緣看起來更平滑。這種鋸齒現象通常出現在低解析度或當對角線、曲線無法完美對應像素點時,導致邊緣看起來不自然或鋸齒化
>
>以下是幾種常見的抗鋸齒技術:
>- 多重取樣抗鋸齒(MSAA, Multi-Sample Anti-Aliasing)
> - 透過對場景的多個樣本進行計算,並根據像素的顏色加權平均,使邊緣平滑
> - 只對幾何邊緣進行抗鋸齒,因此效能較好,但對透明紋理無效
>- 快速近似抗鋸齒(FXAA, Fast Approximate Anti-Aliasing)
> - 透過後處理(Post-Processing)的方法模糊鋸齒邊緣,使畫面變得平滑
> - 效能較好,適合低端硬體,但畫面可能會變模糊
>- 超級採樣抗鋸齒(SSAA, Super-Sampling Anti-Aliasing)
> - 先以高解析度渲染畫面,然後縮小至目標解析度,以獲得更平滑的圖像
> - 效果較佳,但效能消耗較高
>- 時間性抗鋸齒(TAA, Temporal Anti-Aliasing)
> - 使用多幀資訊來平滑動態場景中的鋸齒,能減少閃爍問題
> - 效果較佳,但可能會產生畫面模糊或鬼影(Ghosting)現象
>- 深度學習抗鋸齒(DLAA, Deep Learning Anti-Aliasing)
> - 由 NVIDIA 開發,人工智慧加持的反鋸齒技術,讓影像品質更臻完美。DLAA 採用專為 DLSS 開發的超解析技術,以原生解析度締造更栩栩如生的高品質影像
> - 透過機器學習預測高解析度畫面,達到較好的效果,效能影響較小,但犧牲幀率來提升來提高圖像質量
- `pygame.draw.rect(surface, color, rect, width=0, border_radius=0, border_top_left_radius=-1, border_top_right_radius=-1, border_bottom_left_radius=-1, border_bottom_right_radius=-1)`:繪製矩形
- `rect`:矩形的 Rect 物件`(x, y, width, height)`
- `width`:選填,邊框粗細
- `width=0`:填滿矩形(畫出實心矩形)
- `width>0`:只畫邊框,值決定邊框的厚度(像素)
- `width`大於矩形寬度或高度時:會變成實心矩形
- `border_radius`:選填,預設為`0`,控制圓角半徑
- `border_*_radius`:選填,預設為`-1`,分別控制四個角的圓角半徑
>[!Note]圓角半徑(border radius)是什麼?
>圓角半徑(border_radius)指的是矩形四個角的圓滑程度,即矩形的角如何從直角變成圓弧。當 border_radius 設為 0(預設值)時,矩形的角是標準的 90° 直角;當 border_radius 設為正數時,矩形的角就會變圓
>[!Note]`border_radius`的效果
>數值`0`(預設) → 標準直角矩形
>數值`>0` → 圓角矩形,數值越大,圓角越圓
>數值`> min(width, height) / 2` → 會變成橢圓或半圓
>數值`-1`→ 無效(不會影響矩形)
- `pygame.draw.circle(surface, color, center, radius, width=0)`:畫一個圓
- `center`:圓心座標`(x, y)`
- `radius`:圓半徑
- `pygame.draw.ellipse(surface, color, rect, width=0)`:畫一個橢圓(基於矩形範圍)
- `rect`:決定橢圓形狀的矩形區域
- `pygame.draw.arc(surface, color, rect, start_angle, end_angle, width=1)` :畫一條弧線(基於矩形範圍)
- `start_angle`、`end_angle`:弧線的起始與結束角度,單位為弧度
>[!Note]弧度
>在`pygame.draw.arc()`中,起始角度(start_angle)和結束角度(end_angle)的單位是弧度(radian),而不是一般常見的度數(degree)。這可能會讓人感到困惑,所以我們來簡單解釋一下
>
>#### 什麼是弧度?
>
>弧度(radian)是角度的另一種測量方式:
>- 360° = 2𝜋 弧度
>- 180° = 𝜋弧度
>- 90° = $\frac{𝜋}{2}$弧度
>- 45° = $\frac{𝜋}{4}$弧度
>
>換算公式:
>$$弧度=度數×\frac{𝜋}{180}$$
>
>Python內建的`math.radians(degrees)`可以幫助我們轉換,例如:
>```python
>import math
>#轉換 90° 為弧度,結果是 𝜋/2
>print(math.radians(90)) # 轉換 90° 為弧度,結果是 𝜋/2
>#輸出 1.5707963267948966
>```
>
>畫一條從 0° 到 180°(半圓)的弧線
>```python
>pygame.draw.arc(screen, RED, rect, math.radians(0), math.radians(180), width=3)
>```
>
>#### 角度方向
>
>在`pygame.draw.arc()`中,角度是按照**數學標準方向**計算的:
>- 0 弧度(0°) → 從右側開始
>- π/2 弧度(90°) → 上方
>- π 弧度(180°) → 左側
>- 3π/2 弧度(270°) → 下方
>由上可知,**`pygame.draw.arc()`的繪製方向是「逆時針」的**,如果你想畫出從 90°(上方)到 270°(下方)的半圓弧,應該這樣設定:
>```python
>pygame.draw.arc(screen, RED, rect, math.radians(90), math.radians(270), width=3)
>```
>>高二上數學
- `pygame.draw.polygon(surface, color, point_list, width=0)`:畫一個多邊形
- `point_list`:頂點座標的列表`[(x1, y1), (x2, y2), ...]`
>[!Tip]小技巧
>看到現在不知道大家有沒有發現,不論是畫哪種形狀,開頭都會先用到`pygame.draw`,然後再接要畫的形狀函數,而如果是使用VScode來寫程式的話,系統都會自動提示你有那些函數可接,所以在記指令時我們只要記得每個函數大概的意思就可以了,剩下的系統會提示你
>
>我們剛才說的九種函數都在這裡
>
>參數忘了也沒關係,系統會提示
### 文字處理
在`pygame`中,主要由`pygame.font`來處理文字相關的操作,主要有載入字型、設定大小、渲染文字等功能
- `pygame.font.init()`:初始化`pygame.font`(通常`pygame.init()`會自動執行)
- `pygame.font.get_init()`:檢查`pygame.font`是否已初始化,回傳 `True`或`False`
- `pygame.font.quit()`:關閉`pygame.font`
- `pygame.font.get_default_font()`:取得`pygame`預設字型的名稱,通常是`"freesansbold.ttf"`
- `pygame.font.get_fonts()`:回傳系統上可用的字型名稱清單
- `pygame.font.match_font(name, bold=False, italic=False)`:查找字型是否存在,若存在則回傳路徑,若找不到則回傳`None`
- `name`:字型名稱
- `bold`:選填,指定是否查找**加粗**(**bold**)版本的字型
- `italic`:選填,指定是否查找**斜體**(**italic**)版本的字型
- `pygame.font.SysFont(name, size, bold=False, italic=False)`:載入一個字型並創建字型物件
- `name`:字型名稱,若設為`None`則使用預設字型
- `size`:字型大小
- `bold`:選填,粗體
- `italic`:選填,斜體
- `pygame.font.Font(file, size)`:載入一個字型檔案並創建字型物件
- `file`:檔案路徑,若設為`None`則使用預設
- `size`:字型大小
>[!Important]字型物件
>當你創建好一個字型物件後,記得要將他保存起來,也方便你接下來的操作,例如:
>```python
>font = pygame.font.SysFont("microsoftjhenghei", 100)
>```
- `text_surface = font.render(text, antialias, color, background=None)`:將文字渲染為`Surface`物件
- `text`:要輸出的文字
- `antialias`:是否開啟抗鋸齒,`True`是開啟,`False`是不開啟
- `color`:字體顏色
- `background`:選填,背景顏色,若為`None`則透明
- `text_rect = text_surface.get_rect(center=(x, y))`:取得文字的矩形區域,回傳為`Rect`物件
- 可以在`get_rect()`裡加參數來**設定圖片的初始位置,在定位的同時移動到指定位置**,以下是可用參數
|參數|作用|參數|作用|
|-------------|----------------|-----------|------------------|
|`topleft` |設定矩形的左上角座標|`midtop` |設定矩形的頂部中央座標|
|`topright` |設定矩形的右上角座標|`midbottom`|設定矩形的底部中央座標|
|`bottomleft` |設定矩形的左下角座標|`midleft` |設定矩形的左側中央座標|
|`bottomright`|設定矩形的右下角座標|`midright` |設定矩形的右側中央座標|
|`center` |設定矩形的中心點座標|
- `screen.blit(text_surface, text_rect)`:將文字渲染到視窗上,仍須更新視窗才會顯示,另外`text_rect`也可以換成`(x, y)`座標
>[!Tip]完整流程
>```python
>font = pygame.font.SysFont("microsoftjhenghei", 100)
>text_surface = font.render("文字", True, (0, 255, 255))
>text_rect = text_surface.get_rect(center=(350, 350))
>screen.blit(text_surface, text_rect)
>pygame.display.flip()
>```
- `font.size(text)`:測量指定文字的寬度和高度,回傳`(width, height)`
>[!Important]以下指令適用於`Surface`物件
- `set_bold(value)`:設定是否加粗,`value=True/False`
- `get_bold()`:檢查字型是否加粗,回傳`True`或`False`
- `set_italic(value)`:設定是否斜體
- `get_italic()`:檢查字型是否斜體
- `set_underline(value)`:設定是否有底線
- `get_underline()`:檢查字型是否有底線
- `metrics(text)`:取得指定文字的字元度量資訊,回傳列表`(min_x, max_x, min_y, max_y, advance_x)`,若某個字元在字型中找不到將回傳`None`
- |參數|意義|
|---|---|
|`min_x`|字元左側邊界的 X 座標|
|`max_x`|字元右側邊界的 X 座標|
|`min_y`|字元底部邊界的 Y 座標|
|`max_y`|字元頂部邊界的 Y 座標|
|`advance_x`|下一個字元的起始位置(表示字元的寬度加上間距)|
>[!Note]常用的字型名稱
>- `microsoftjhenghei`:微軟正黑體,推薦使用
>- `microsoftyahei`:微軟雅黑體
>- `mingliu`:細明體,傳統風格
>- `pmingliu`:新細明體,繁體中文
>- `simsun`:中易宋體,簡體中文
>- `simhei`:中易黑體,簡體中文
>- `dengxian`:等線體
>- `fangsong`:仿宋體,古典風格
>- `kaiti`:楷體,手寫風格
### 圖像處理
在`pygame`中,主要由`pygame.image`來負責圖像方面的操作,提供了圖片縮放、色彩透明度以及像素碰撞等功能
- `pygame.image.load("path/to/image.png")`:載入圖片
- `"path/to/image.png"`:檔案路徑,要記得`\`要換成`\\`或`/`
- 支援格式:PNG、JPG、GIF(靜態)、BMP、TGA
- 如果圖片格式不支援,會拋出`pygame.error: Unsupported image format`錯誤
>[!Note]可以使用`convert()`或`convert_alpha()`來提高效能
>在`pygame`中,使用`.convert()`或`.convert_alpha()`**提高效能的主要原因**在於**顯存格式的匹配**。以下是它們如何提升效能的細節:
>- 內部格式轉換
> 當你用`pygame.image.load()`載入圖片時,圖片的格式可能與螢幕的顯存格式不同,這會導致`blit()`(繪製圖片)時,每次都需要額外的計算來匹配格式,從而降低效能
> - `.convert()`:將圖片的像素格式轉換為與顯示畫面(Surface)的格式相同,這樣`blit()`可以直接複製記憶體區塊,而不需要即時轉換格式,因此顯示速度大幅提升
> - `.convert_alpha()`:同樣會轉換格式,但會保留 Alpha(透明通道)資訊,適用於 PNG 這類有透明部分的圖片
>- 減少 CPU/GPU 運算開銷
> - **如果不使用**`.convert()`,每次`blit()`都要即時進行格式轉換,這會增加 CPU/GPU 的運算負擔,導致遊戲掉幀或卡頓
> - 使用`.convert()`後,圖片格式與顯示畫面相匹配,繪製時可以直接使用顯存中的數據,減少額外的運算開銷,提高`blit()`的執行效率
>```python
>image = pygame.image.load("path/to/image.png").convert() # 移除透明度,加快顯示
>image_alpha = pygame.image.load("path/to/image.png").convert_alpha() # 保留透明度
>```
- `pygame.transform.scale(image, (new_width, new_height))`:前面提到過的圖片縮放
- `pygame.transform.smoothscale(image, (new_width, new_height))`:**平滑縮放**(適用於 PNG、JPG),**減少鋸齒化,但效能消耗較高**
- `pygame.transform.rotate(image, angle)`:旋轉圖片,但旋轉後圖片大小可能會改變
- `angle`:角度(順時針旋轉)
- `pygame.transform.rotozoom(image, angle, scale)`:旋轉並縮放
- `scale`:縮放倍率,`1.0`表示不變
- `pygame.transform.flip(image, flip_x, flip_y)`:水平和垂直翻轉
- `flip_x`:水平翻轉(`True`/`False`)
- `flip_y`:垂直翻轉(`True`/`False`)
:::success
- `image.set_alpha(alpha_value)`:改變透明度,適用於`.convert_alpha()` 圖片
- `alpha_value`:`0~255`,`0`表示完全透明,`255`表示完全不透明
- `image.set_colorkey((R, G, B))`:使某個顏色變為透明
- `image.get_size()`:取得圖片尺寸,回傳為`(w, h)`
- `image.get_rect()`:取得圖片的矩形區域,回傳為`Rect`物件,與前面提到過的`text_surface.get_rect()`類似
- `screen.blit(image, (x, y))`:在`(x, y)`位置繪製圖片,仍須更新視窗才會顯示,與前面提到的`screen.blit(text_surface, text_rect)`類似
:::
### 結束Pygame
當程式結束時,應確保Pygame正確關閉
- `pygame.quit()`:關閉Pygame
## [下一篇](https://hackmd.io/@Huanyu763/PythonPygame事件處理)
## [回到主頁](https://hackmd.io/@Huanyu763/home)