【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,支持头像、名字和对话内容。

下章预告

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

相关推荐
_MyFavorite_5 小时前
JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)
java·开发语言·设计模式
A.A呐5 小时前
【C++第二十三章】C++11
开发语言·c++
:mnong5 小时前
全图纸语义理解升级分析
python·openvino·paddleocr·qt6.3·paddleocr-vl
qh0526wy5 小时前
pathlib 核心功能一览
python
014-code5 小时前
Java SPI 实战:ServiceLoader 的正确打开方式(含类加载器坑)
java·开发语言
lifewange5 小时前
Go语言-开源编程语言
开发语言·后端·golang
jimy16 小时前
C语言函数指针
c语言·开发语言
白毛大侠6 小时前
深入理解 Go:用户态和内核态
开发语言·后端·golang
Hello eveybody6 小时前
PyCharm性能调优避坑录
python·pycharm