【Pygame】第8章 文字渲染与字体系统(支持中文字体)

摘要

文字是游戏 UI 和信息展示的基础元素。本章将深入介绍 Pygame 的文字渲染功能。我们将学习如何加载字体文件,掌握文字的渲染和对齐方法,探索文字特效如阴影、描边和渐变的实现,以及如何实现文字换行和自动缩放。通过本章的学习,读者将能够在游戏中呈现美观、专业的文字效果。


8.1 字体系统概述

Pygame 的字体功能由 pygame.font 模块提供,支持 TrueType 字体文件的加载和渲染。

8.1.1 支持的字体格式

Pygame 主要支持以下字体格式:

格式 扩展名 说明
TrueType .ttf 最常用,跨平台
OpenType .otf 现代字体格式
系统字体文件 .ttf / .ttc / .otf 从操作系统字体文件路径直接加载

8.1.2 初始化字体系统

python 复制代码
import pygame

pygame.init()

# 或者单独初始化
pygame.font.init()

# 检查是否初始化成功
if pygame.font.get_init():
    print("字体系统已初始化")

8.2 基本文字渲染

8.2.1 从字体文件加载字体

由于某些环境下 pygame.font.SysFont 可能存在兼容性问题,推荐直接使用字体文件路径加载字体。

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\msyh.ttf",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

font = get_font(36)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    text_surface = font.render("你好,Pygame!", True, (255, 255, 255))
    screen.blit(text_surface, (300, 250))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.2.2 使用字体文件

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

custom_font = get_font(36)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    text_surface = custom_font.render("自定义字体", True, (255, 255, 255))
    screen.blit(text_surface, (300, 250))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.2.3 渲染模式

python 复制代码
# 抗锯齿渲染,推荐
text_surface = font.render("文字", True, (255, 255, 255))

# 非抗锯齿渲染,适合像素风格
text_surface = font.render("文字", False, (255, 255, 255))

# 带背景色的渲染
text_surface = font.render("文字", True, (255, 255, 255), (0, 0, 0))

8.3 文字对齐与布局

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

font = get_font(24)

def draw_text_aligned(surface, text, font, color, rect, align="left"):
    text_surface = font.render(text, True, color)
    text_rect = text_surface.get_rect()

    if align == "left":
        text_rect.left = rect.left
        text_rect.centery = rect.centery
    elif align == "center":
        text_rect.center = rect.center
    elif align == "right":
        text_rect.right = rect.right
        text_rect.centery = rect.centery
    elif align == "top":
        text_rect.centerx = rect.centerx
        text_rect.top = rect.top
    elif align == "bottom":
        text_rect.centerx = rect.centerx
        text_rect.bottom = rect.bottom

    surface.blit(text_surface, text_rect)
    return text_rect

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    rect = pygame.Rect(100, 100, 600, 400)
    pygame.draw.rect(screen, (100, 100, 100), rect, 2)

    draw_text_aligned(screen, "左对齐", font, (255, 255, 255), rect, "left")
    draw_text_aligned(screen, "居中", font, (255, 255, 255), rect, "center")
    draw_text_aligned(screen, "右对齐", font, (255, 255, 255), rect, "right")
    draw_text_aligned(screen, "顶部", font, (255, 255, 255), rect, "top")
    draw_text_aligned(screen, "底部", font, (255, 255, 255), rect, "bottom")

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.4 文字特效

8.4.1 阴影效果

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

def render_text_with_shadow(surface, text, font, text_color, shadow_color, pos, shadow_offset=(2, 2)):
    shadow_surface = font.render(text, True, shadow_color)
    shadow_pos = (pos[0] + shadow_offset[0], pos[1] + shadow_offset[1])
    surface.blit(shadow_surface, shadow_pos)

    text_surface = font.render(text, True, text_color)
    surface.blit(text_surface, pos)

font = get_font(48)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    render_text_with_shadow(
        screen,
        "阴影文字效果",
        font,
        (255, 255, 255),
        (0, 0, 0),
        (250, 250)
    )

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.4.2 描边效果

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

def render_text_with_outline(surface, text, font, text_color, outline_color, pos, outline_width=2):
    outline_surface = font.render(text, True, outline_color)

    for dx in range(-outline_width, outline_width + 1):
        for dy in range(-outline_width, outline_width + 1):
            if dx != 0 or dy != 0:
                surface.blit(outline_surface, (pos[0] + dx, pos[1] + dy))

    text_surface = font.render(text, True, text_color)
    surface.blit(text_surface, pos)

font = get_font(48)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    render_text_with_outline(
        screen,
        "描边文字效果",
        font,
        (255, 255, 0),
        (255, 0, 0),
        (250, 250),
        3
    )

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.4.3 渐变文字

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

def create_gradient_text(text, font, color1, color2):
    text_surface = font.render(text, True, (255, 255, 255))
    width, height = text_surface.get_size()

    gradient = pygame.Surface((width, height), pygame.SRCALPHA)

    for y in range(height):
        ratio = y / height
        r = int(color1[0] + (color2[0] - color1[0]) * ratio)
        g = int(color1[1] + (color2[1] - color1[1]) * ratio)
        b = int(color1[2] + (color2[2] - color1[2]) * ratio)
        pygame.draw.line(gradient, (r, g, b), (0, y), (width, y))

    result = pygame.Surface((width, height), pygame.SRCALPHA)
    result.blit(gradient, (0, 0))
    result.blit(text_surface, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)

    return result

font = get_font(48)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    gradient_text = create_gradient_text("渐变文字", font, (255, 0, 0), (0, 0, 255))
    screen.blit(gradient_text, (250, 250))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.5 文字换行与自动缩放

python 复制代码
import pygame
import sys
import os

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

def get_font(size):
    font_paths = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]

    for path in font_paths:
        if os.path.exists(path):
            try:
                return pygame.font.Font(path, size)
            except:
                pass

    return pygame.font.Font(None, size)

def render_wrapped_text(surface, text, font, color, rect, line_spacing=5):
    words = text.split(' ')
    lines = []
    current_line = []

    for word in words:
        test_line = ' '.join(current_line + [word])
        test_surface = font.render(test_line, True, color)

        if test_surface.get_width() <= rect.width:
            current_line.append(word)
        else:
            if current_line:
                lines.append(' '.join(current_line))
            current_line = [word]

    if current_line:
        lines.append(' '.join(current_line))

    y = rect.y
    for line in lines:
        line_surface = font.render(line, True, color)
        surface.blit(line_surface, (rect.x, y))
        y += font.get_height() + line_spacing

        if y + font.get_height() > rect.bottom:
            break

def render_fitted_text(surface, text, font_path, color, rect, max_font_size=72):
    for size in range(max_font_size, 8, -1):
        test_font = pygame.font.Font(font_path, size)
        text_surface = test_font.render(text, True, color)

        if text_surface.get_width() <= rect.width and text_surface.get_height() <= rect.height:
            surface.blit(text_surface, rect.topleft)
            return test_font

    return pygame.font.Font(font_path, 12)

font_path = r"C:\Windows\Fonts\simhei.ttf"
if not os.path.exists(font_path):
    font_path = r"C:\Windows\Fonts\msyh.ttc"

font = pygame.font.Font(font_path, 24) if os.path.exists(font_path) else pygame.font.Font(None, 24)

long_text = "这是一段很长的文字,需要自动换行显示。Pygame 的文字渲染功能虽然基础,但通过适当的封装可以 实现丰富的效果。"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    text_rect = pygame.Rect(100, 100, 600, 200)
    pygame.draw.rect(screen, (100, 100, 100), text_rect, 2)
    render_wrapped_text(screen, long_text, font, (255, 255, 255), text_rect)

    fit_rect = pygame.Rect(250, 400, 300, 100)
    pygame.draw.rect(screen, (100, 100, 100), fit_rect, 2)
    render_fitted_text(screen, "自适应", font_path if os.path.exists(font_path) else None, (255, 255, 0), fit_rect)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.6 文字工具类示例

下面给出一个更稳妥的文字渲染工具类示例,统一使用字体文件加载。

python 复制代码
import pygame
import os

class TextRenderer:
    def __init__(self, font_path, font_size):
        self.font_path = font_path
        self.font_size = font_size
        self.font = pygame.font.Font(font_path, font_size)
        self.cache = {}

    def render_text(self, text, color):
        key = (text, color)
        if key not in self.cache:
            self.cache[key] = self.font.render(text, True, color)
        return self.cache[key]

    def draw(self, surface, text, color, pos):
        text_surface = self.render_text(text, color)
        surface.blit(text_surface, pos)
        return text_surface.get_rect(topleft=pos)

    def draw_shadow(self, surface, text, text_color, shadow_color, pos, offset=(2, 2)):
        shadow = self.font.render(text, True, shadow_color)
        surface.blit(shadow, (pos[0] + offset[0], pos[1] + offset[1]))
        text_surface = self.render_text(text, text_color)
        surface.blit(text_surface, pos)

    def draw_outline(self, surface, text, text_color, outline_color, pos, width=2):
        outline_surface = self.font.render(text, True, outline_color)
        for dx in range(-width, width + 1):
            for dy in range(-width, width + 1):
                if dx != 0 or dy != 0:
                    surface.blit(outline_surface, (pos[0] + dx, pos[1] + dy))
        text_surface = self.render_text(text, text_color)
        surface.blit(text_surface, pos)

8.7 综合示例:文字系统演示

python 复制代码
import pygame
import sys
import os

class TextRenderer:
    def __init__(self, font_path, font_size):
        self.font = pygame.font.Font(font_path, font_size)
        self.cache = {}

    def render(self, surface, text, color, pos, shadow=False, outline=False):
        key = (text, color, shadow, outline)
        if key in self.cache:
            text_surface = self.cache[key]
        else:
            base_surface = self.font.render(text, True, color)
            width, height = base_surface.get_size()
            pad = 4 if outline else (2 if shadow else 0)

            result = pygame.Surface((width + pad * 2, height + pad * 2), pygame.SRCALPHA)

            if shadow:
                shadow_surface = self.font.render(text, True, (0, 0, 0))
                result.blit(shadow_surface, (pad + 2, pad + 2))

            if outline:
                outline_surface = self.font.render(text, True, (0, 0, 0))
                for dx in range(-2, 3):
                    for dy in range(-2, 3):
                        if dx != 0 or dy != 0:
                            result.blit(outline_surface, (pad + dx, pad + dy))

            result.blit(base_surface, (pad, pad))
            text_surface = result
            self.cache[key] = text_surface

        surface.blit(text_surface, pos)
        return text_surface.get_rect(topleft=pos)

def get_font_path():
    candidates = [
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simsun.ttc",
    ]
    for p in candidates:
        if os.path.exists(p):
            return p
    return None

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

font_path = get_font_path()
if font_path is None:
    font_path = pygame.font.match_font("arial") if False else None

if font_path is None:
    font_path = r"C:\Windows\Fonts\arial.ttf"

renderer = TextRenderer(font_path, 36)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((50, 50, 50))

    renderer.render(screen, "普通文字", (255, 255, 255), (50, 50))
    renderer.render(screen, "阴影文字", (255, 255, 255), (50, 120), shadow=True)
    renderer.render(screen, "描边文字", (255, 255, 0), (50, 190), outline=True)
    renderer.render(screen, "阴影 + 描边", (0, 255, 255), (50, 260), shadow=True, outline=True)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

8.8 本章总结

本章详细介绍了 Pygame 的文字渲染系统。我们学习了字体加载方法、基本文字渲染、对齐布局技巧,以及阴影、描边、渐变等文字特效的实现。通过文字换行和自动缩放功能,可以适应不同的显示需求。文字渲染工具类的示例展示了如何组织和优化文字渲染代码。良好的文字呈现是游戏 UI 品质的重要体现。

本章知识点回顾

知识点 主要内容
字体加载 Font 类、字体文件路径加载
文字渲染 render 方法、抗锯齿
对齐布局 矩形对齐、居中显示
文字特效 阴影、描边、渐变
高级功能 自动换行、字体缩放

课后练习

  1. 实现一个富文本渲染系统,支持同一段文字中不同颜色和大小。
  2. 创建一个打字机效果组件,逐字显示文字。
  3. 实现文字发光效果。
  4. 使用文字粒子效果实现动态标题。
  5. 实现一个对话系统 UI,支持头像、名字和对话内容。

下章预告

在下一章中,我们将学习动画系统的实现,包括帧动画、补间动画和骨骼动画的基础知识。

相关推荐
夏幻灵4 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码4 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx6 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
程序员老陆6 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光6 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
码云数智-园园6 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西7 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨7 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
全栈练习生7 小时前
AI Agent 沙箱
python·ai
徐小黑ACG8 小时前
Golang 基础05 结构体struct
开发语言·算法·golang