2. 2D图形绘制:使用Python的Pygame库绘制基本图形

好,咱们正式开始动手了。这一章我带你用Pygame画点东西。你想想看,一个桌面应用如果连个图形都画不出来,那跟记事本有什么区别?图形绘制是多媒体开发的基石,说白了就是给屏幕上的像素点「下命令」。

我个人习惯把Pygame当作一个快速验证想法的工具。它不像OpenGL那样底层,也不像Qt那样臃肿。对于2D图形,Pygame的API设计得相当直觉。嗯,咱们先从最基础的开始——点、线、矩形、圆。

2.1 初始化与基本设置

在画任何东西之前,你得先有个画布。Pygame里这个画布叫Surface,而显示在屏幕上的那个叫display Surface。我刚开始学的时候总搞混这两个概念,后来发现其实很简单:display就是最终你看到的那个窗口。

import pygame
import sys

# 初始化Pygame
pygame.init()

# 设置窗口大小,单位是像素
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("2D图形绘制 - 第2章")

# 定义颜色常量(RGB元组)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# 主循环
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # 填充背景色
    screen.fill(WHITE)
    
    # 在这里绘制图形...
    
    pygame.display.flip()

pygame.quit()
sys.exit()
小提示:pygame.display.flip() 和 pygame.display.update() 的区别?flip会刷新整个窗口,update可以只刷新指定区域。性能敏感的场景下,用update更合适。

2.2 绘制点与线

点是最基本的图形元素。在Pygame里,画一个点其实就是画一个1像素宽的线段。我在项目中遇到过一个问题:用画点的方式去绘制大量粒子效果,性能会急剧下降。后来改用画矩形替代,效率提升了好几倍。

# 画一个点(实际上是画一条长度为1的线)
pygame.draw.line(screen, BLACK, (100, 100), (101, 100), 1)

# 画一条直线
pygame.draw.line(screen, RED, (50, 50), (200, 50), 3)

# 画多条连续线(折线)
points = [(300, 100), (400, 200), (500, 100), (600, 200)]
pygame.draw.lines(screen, BLUE, False, points, 2)
注意:draw.lines的第三个参数是closed,设为True会连接首尾形成封闭图形。我曾经在这里踩过坑——画多边形时忘了设True,结果图形缺了一条边,排查了半天。

2.3 绘制矩形与圆

矩形和圆是UI开发中最常用的图形。按钮、面板、头像,几乎都离不开它们。Pygame的矩形绘制支持圆角,但要注意——圆角矩形在旧版本里是不支持的,升级到2.0以上才有。

# 绘制矩形(无填充)
pygame.draw.rect(screen, GREEN, (100, 100, 200, 150), 2)

# 绘制填充矩形
pygame.draw.rect(screen, RED, (350, 100, 200, 150))

# 绘制圆角矩形(Pygame 2.0+)
pygame.draw.rect(screen, BLUE, (600, 100, 150, 150), 0, 20)

# 绘制圆(无填充)
pygame.draw.circle(screen, BLACK, (200, 350), 50, 3)

# 绘制填充圆
pygame.draw.circle(screen, RED, (400, 350), 60)

# 绘制椭圆
pygame.draw.ellipse(screen, GREEN, (550, 300, 200, 100), 2)
函数 参数说明 width=0时
pygame.draw.rect surface, color, rect, width=0 完全填充
pygame.draw.circle surface, color, center, radius, width=0 完全填充
pygame.draw.ellipse surface, color, rect, width=0 完全填充
pygame.draw.polygon surface, color, points, width=0 完全填充

2.4 颜色与填充

颜色在Pygame里用RGB元组表示,每个分量0-255。但你知道吗?Pygame还支持带透明度的RGBA。alpha通道从0(完全透明)到255(完全不透明)。

我建议你在项目里定义一个颜色常量文件,而不是到处写(255, 0, 0)。为什么?因为后期改主题色的时候,你会感谢我的。

# 带透明度的颜色
TRANSPARENT_RED = (255, 0, 0, 128)

# 创建带透明度的Surface
alpha_surface = pygame.Surface((200, 200), pygame.SRCALPHA)
alpha_surface.fill((0, 0, 255, 100))  # 半透明蓝色

# 渐变填充(手动实现)
for i in range(256):
    color = (i, 0, 255 - i)  # 从蓝色渐变到红色
    pygame.draw.line(screen, color, (i, 450), (i, 500))
核心要点:填充的本质就是width=0。所有draw函数都遵循这个规则——width大于0画边框,等于0画实心。记住这一点,你就能灵活控制任何图形的填充状态。

2.5 抗锯齿技术

好,接下来聊点进阶的。你仔细看看上面画的圆,是不是边缘有锯齿?这就是所谓的「走样」现象。抗锯齿(Anti-Aliasing)就是通过边缘像素的渐变过渡,让图形看起来更平滑。

Pygame提供了gfxdraw模块来处理抗锯齿。不过要注意,gfxdraw不是默认安装的,需要额外安装pygame.gfxdraw库。我个人习惯用这个库来画高质量的图形。

# 需要安装:pip install pygame-gfxdraw
import pygame.gfxdraw

# 抗锯齿画圆
pygame.gfxdraw.aacircle(screen, 200, 550, 50, BLACK)

# 抗锯齿画线
pygame.gfxdraw.line(screen, 300, 500, 500, 600, RED)

# 抗锯齿填充圆
pygame.gfxdraw.filled_circle(screen, 400, 550, 50, BLUE)

# 手动实现抗锯齿(简单版)
# 原理:在边缘绘制半透明像素
def draw_aa_line(screen, color, start, end, width):
    # 实际项目中会用更复杂的算法
    # 这里展示思路
    pygame.draw.line(screen, color, start, end, width)
    # 在端点处画半透明圆来柔化边缘
    pygame.draw.circle(screen, (*color, 128), start, width//2)
    pygame.draw.circle(screen, (*color, 128), end, width//2)
避坑指南:我曾经在游戏项目中大量使用gfxdraw的抗锯齿圆,结果发现帧率从60掉到了30。后来排查发现,gfxdraw的填充圆性能远不如pygame.draw.circle。所以我的建议是:静态界面用gfxdraw,动态场景用普通draw,或者自己实现简单的抗锯齿。

2.6 综合示例:绘制一个简单的仪表盘

理论说完了,咱们来点实战。下面这个例子画了一个简单的仪表盘,用到了本章所有知识点。

import pygame
import math

pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()

# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
RED = (255, 50, 50)
BLUE = (50, 50, 255)

running = True
angle = 0

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    screen.fill(WHITE)
    
    # 画表盘外框(抗锯齿圆)
    pygame.gfxdraw.aacircle(screen, 200, 200, 150, BLACK)
    pygame.gfxdraw.aacircle(screen, 200, 200, 148, GRAY)
    
    # 画刻度线
    for i in range(12):
        rad = math.radians(i * 30 - 90)
        x1 = 200 + int(130 * math.cos(rad))
        y1 = 200 + int(130 * math.sin(rad))
        x2 = 200 + int(140 * math.cos(rad))
        y2 = 200 + int(140 * math.sin(rad))
        pygame.draw.line(screen, BLACK, (x1, y1), (x2, y2), 2)
    
    # 画指针(动态旋转)
    rad = math.radians(angle - 90)
    end_x = 200 + int(100 * math.cos(rad))
    end_y = 200 + int(100 * math.sin(rad))
    pygame.draw.line(screen, RED, (200, 200), (end_x, end_y), 4)
    
    # 画中心圆
    pygame.draw.circle(screen, BLUE, (200, 200), 10)
    
    angle = (angle + 1) % 360
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
经验之谈:这个仪表盘虽然简单,但涵盖了点、线、圆、颜色、抗锯齿等所有知识点。你可以试着扩展它——加上数值显示、多指针、或者换成深色主题。动手改一改,比看十遍代码都管用。

嗯,这一章的内容就到这儿。记住,图形绘制的核心就三件事:位置、颜色、样式。Pygame把这些封装得足够简单,但真正用好它,需要你在项目中不断积累经验。下一章咱们聊图像加载与处理,到时候你会看到,这些基础图形知识会派上大用场。