目录
[1. 引言](#1. 引言)
[2. 基础知识](#2. 基础知识)
[2.1 Pygame库与2D图形渲染](#2.1 Pygame库与2D图形渲染)
[2.2 粒子系统的基本原理](#2.2 粒子系统的基本原理)
[2.3 颜色管理与验证机制](#2.3 颜色管理与验证机制)
[2.4 字体管理与中文文本渲染](#2.4 字体管理与中文文本渲染)
[2.5 实时动画中的帧率与时序控制](#2.5 实时动画中的帧率与时序控制)
[3. 方法](#3. 方法)
[3.1 系统架构设计](#3.1 系统架构设计)
[3.2 粒子基类的设计与实现](#3.2 粒子基类的设计与实现)
[3.3 烟火特效的设计与实现](#3.3 烟火特效的设计与实现)
[3.4 彩带效果的设计与实现](#3.4 彩带效果的设计与实现)
[3.5 星星闪烁效果的设计与实现](#3.5 星星闪烁效果的设计与实现)
[3.6 祝福文字效果的设计与实现](#3.6 祝福文字效果的设计与实现)
[3.7 UI系统与按钮的实现](#3.7 UI系统与按钮的实现)
[3.8 事件处理与用户交互](#3.8 事件处理与用户交互)
[3.9 全屏模式的实现](#3.9 全屏模式的实现)
[3.10 性能优化与资源管理](#3.10 性能优化与资源管理)
[3.11 错误处理与容错机制](#3.11 错误处理与容错机制)
[4. 实验结果与分析](#4. 实验结果与分析)
[4.1 系统功能验证](#4.1 系统功能验证)
[4.2 性能指标分析](#4.2 性能指标分析)
[4.3 视觉效果质量评估](#4.3 视觉效果质量评估)
[4.4 跨平台兼容性验证](#4.4 跨平台兼容性验证)
[4.5 特殊情况处理的验证](#4.5 特殊情况处理的验证)
[4.6 性能对比分析](#4.6 性能对比分析)
[5. 总结与展望](#5. 总结与展望)
[5.1 项目总结](#5.1 项目总结)
[5.2 技术要点回顾](#5.2 技术要点回顾)
[5.3 未来改进方向](#5.3 未来改进方向)
[5.4 技术应用场景展望](#5.4 技术应用场景展望)
[5.5 最后的思考](#5.5 最后的思考)
1. 引言
随着现代计算机图形技术的不断发展,视觉特效在用户界面设计中扮演着越来越重要的角色。特别是在节日庆典应用中,精心设计的视觉效果不仅能够提升用户体验,还能够营造出浓厚的节日氛围。本项目设计并实现了一套完整的新年特效系统,该系统基于Python的Pygame库进行开发,集成了烟火爆炸、飘落彩带、闪烁星星和动态祝福文字等多种视觉效果元素。该项目的核心价值在于展示如何运用粒子系统、物理引擎和图形渲染等计算机图形学中的关键技术,将分散的视觉元素有机地组合在一起,形成一个动态、流畅且视觉冲击力强的综合效果。通过本项目的学习和研究,开发者不仅能够掌握Pygame库的高级应用技巧,还能够深入理解粒子系统的设计原理、物理模拟的实现方法以及实时图形渲染中的性能优化策略。该项目具有广泛的应用前景,可以用于游戏开发、多媒体应用、数据可视化以及各类交互式用户界面设计中,为普通的应用程序增添专业级的视觉效果。项目的创新点主要体现在三个方面:其一是设计了一个高度模块化的架构,将不同的视觉效果分离成独立的类,便于扩展和维护;其二是实现了高效的粒子管理系统,通过合理的生命周期管理和内存回收机制,确保即使在生成数千个粒子的情况下仍能保持稳定的帧率;其三是提供了灵活的配置系统和完善的错误处理机制,使得整个系统具有良好的健壮性和可用性。本文将对这套新年特效系统的设计理念、实现细节以及性能特性进行全面的技术分析。
2. 基础知识
2.1 Pygame库与2D图形渲染
Pygame是一个基于SDL库之上的Python游戏开发框架,它为开发者提供了一系列简洁而强大的API,用于实现2D图形渲染、事件处理、音频播放和输入管理等功能。在Pygame的设计哲学中,一切都围绕着Surface对象展开,Surface是一个二维像素矩阵的抽象,可以理解为一块虚拟画布,所有的图形绘制操作都是通过对Surface进行像素级的修改来实现的。在本项目中,我们通过pygame.display.set_mode()函数创建一个主Display Surface,它对应于窗口中显示的内容。Pygame提供了多种绘制基本图形的函数,包括pygame.draw.circle()用于绘制圆形、pygame.draw.lines()用于绘制线段序列、pygame.draw.rect()用于绘制矩形等。这些函数都遵循一个统一的调用模式:首先指定目标Surface,然后指定颜色(RGB三元组),最后指定具体的几何参数。颜色在Pygame中使用RGB三元组表示,其中R、G、B分别代表红、绿、蓝三个通道的强度值,范围为0-255。此外,Pygame还支持透明度(Alpha通道)的概念,通过设置Surface的透明度值可以实现半透明效果。在实时渲染应用中,一个关键的概念是帧率(Frame Rate),它表示每秒钟更新和渲染的次数,通常以FPS(Frames Per Second)为单位。高帧率能够保证动画的流畅度,但同时也会增加CPU和GPU的负担。Pygame通过Clock对象提供了精确的帧率控制机制,调用clock.tick(fps)会确保主循环不会运行超过指定的帧率,从而实现稳定的画面更新。
2.2 粒子系统的基本原理
粒子系统是计算机图形学中一种强大的技术,用于模拟大量微小对象的集体行为,如烟雾、火焰、雨水、雪花等自然现象。粒子系统的核心思想是将复杂的视觉现象分解为许多简单的、独立的微粒,每个微粒有自己的位置、速度、颜色和生命周期,通过对这些微粒进行物理模拟和渲染,最终形成令人印象深刻的视觉效果。粒子系统通常包含以下几个关键组件:首先是粒子数据结构,用于存储每个粒子的状态信息;其次是粒子生成器,负责按照特定规则创建新的粒子;再次是物理引擎,负责根据力的作用更新粒子的位置和速度;最后是粒子渲染器,负责将粒子绘制到屏幕上。在物理模拟中,最基本的原理是牛顿运动学。对于一个粒子,它的位置、速度和加速度之间满足以下基本关系:设粒子在时刻 t 的位置为 $$\vec{r}(t)$$,速度为 $$\vec{v}(t)$$,加速度为 $$\vec{a}(t)$$,则有:
\\frac{d\\vec{r}}{dt} = \\vec{v}(t)
\\frac{d\\vec{v}}{dt} = \\vec{a}(t)
在离散的时间步长中,我们可以使用欧拉方法进行数值积分,对于时间步长 dt,更新公式为:
\\vec{v}(t+dt) = \\vec{v}(t) + \\vec{a}(t) \\cdot dt
\\vec{r}(t+dt) = \\vec{r}(t) + \\vec{v}(t) \\cdot dt
在本项目中,我们主要考虑的是重力加速度,即 $$\vec{a} = (0, g)$$,其中 g 是重力加速度常数(通常设为0.2像素/帧²)。粒子的生命周期管理也是粒子系统的重要部分,每个粒子都有一个初始的生命值(lifetime),在每个时间步长中生命值会递减,当生命值降至0时,该粒子应该被从系统中删除。同时,为了实现淡出效果,我们通常会根据粒子剩余的生命值占初始生命值的比例来动态调整粒子的透明度。
2.3 颜色管理与验证机制
颜色在计算机图形学中是一个基本的概念,Pygame使用RGB三元组表示颜色,每个分量的值必须在0-255的范围内。在实际开发中,由于各种原因(如浮点数四舍五入、用户输入等),颜色值有可能超出这个范围或不是整数,因此建立一个健壮的颜色验证和转换机制是必要的。在本项目中,我们为此创建了一个统一的颜色验证函数 _validate_color(),该函数的功能是确保输入的颜色值是合法的RGB三元组。具体的实现逻辑包括:首先尝试遍历颜色元组的每个分量,将其转换为整数;然后使用max()函数确保每个分量不小于0,使用min()函数确保每个分量不大于255;最后返回验证后的颜色元组。这个函数被整合到多个类中,确保每次创建颜色值时都进行验证,从而最大化减少因颜色值不合法导致的运行时错误。
2.4 字体管理与中文文本渲染
在GUI应用中,文本渲染是一个常见的需求。Pygame本身对英文文本的支持很好,但对于中文等非ASCII字符的支持相对较弱。特别是在跨平台应用中,不同的操作系统可能拥有完全不同的可用系统字体,如果直接使用某个特定的字体名称,程序在其他系统上可能无法运行。为了解决这个问题,本项目设计了一个字体管理器(FontManager),它采用了一个渐进式的字体查找策略。具体来说,该管理器维护一个候选字体列表,包括SimHei(黑体)、SimSun(宋体)、KaiTi(楷体)、FangSong(仿宋)和Microsoft YaHei等常见的中文字体。当需要加载字体时,管理器会按顺序尝试加载这些字体,直到成功加载一个可用的字体为止。为了验证字体是否能够正确渲染中文字符,管理器会在加载后立即进行一个测试渲染操作,尝试渲染一个中文字符"中",如果测试成功则认为该字体可用;如果所有候选字体都加载失败,管理器会使用Pygame的默认字体作为最后的备选方案。这个设计确保了程序在各种系统环境下都能够正常运行。
2.5 实时动画中的帧率与时序控制
在实时动画应用中,时序控制是一个至关重要的概念。理想情况下,我们希望动画在不同配置的计算机上都能以相同的速度运行,但由于不同计算机的计算能力不同,直接运行相同的代码可能在高端计算机上运行速度过快,而在低端计算机上运行速度过慢。因此,我们需要一个时序控制机制,使得动画速度与硬件无关。Pygame的Clock对象提供了这样的机制。在主循环的末尾调用clock.tick(fps)会使主循环的执行速度受到严格的控制,确保每秒钟循环执行的次数不会超过指定的fps值。这个函数的工作原理是记录上一次调用的时间,然后在这一次调用中计算自上次调用以来经过的时间,如果这个时间小于目标时间间隔(1/fps秒),则将线程暂停相应的时间。在本项目中,我们将帧率设置为60FPS,这意味着每一帧的时间预算是1/60秒,约16.67毫秒。在这个时间内,我们需要完成事件处理、状态更新和画面渲染等所有操作。
3. 方法
3.1 系统架构设计
本新年特效系统采用了一个面向对象的模块化架构,将整个系统分解为多个相对独立的功能模块,各个模块之间通过定义良好的接口进行通信。这样的架构设计带来了几个重要的优势:首先是代码的可维护性得到了显著提升,当需要修改某个特定的效果时,只需要修改相应的类,而不需要理解整个系统的所有细节;其次是代码的可扩展性得到了改善,如果需要添加新的视觉效果,可以通过继承基类或创建新的类来实现,而无需修改现有的代码;再次是代码的可测试性得到了增强,每个模块都可以单独进行测试和验证。系统的核心由以下几个主要模块组成:第一个模块是配置管理(Config),负责存储所有的可配置参数,如窗口尺寸、颜色方案、各种参数等,这使得对系统行为的调整变得非常简单,只需要修改Config类中的值即可;第二个模块是粒子基类(Particle),它提供了所有粒子类型共有的基本功能,包括位置更新、重力模拟、生命周期管理等;第三个模块是各种特效类,包括烟火(Firework)、彩带(Ribbon)、星星(Star)和祝福文字(BlessingText),这些类继承或组合粒子类,实现各自独特的视觉效果;第四个模块是UI系统(Button),负责实现用户界面的交互元素;第五个模块是主程序类(NewYearEffect),它充当了一个协调器的角色,管理所有子系统的初始化、更新和渲染。这种分层的架构设计使得系统具有良好的可理解性和可维护性。
3.2 粒子基类的设计与实现
粒子基类是整个系统的基础,它定义了所有粒子共有的属性和方法。在设计这个基类时,我们充分考虑了以下几个因素:首先是通用性,粒子基类应该能够支持各种不同类型的粒子,而不限于烟火粒子;其次是物理准确性,虽然这个系统不是一个高精度的物理模拟,但基本的物理规律应该得到遵循,特别是重力的影响;再次是性能,粒子基类的更新和渲染方法应该尽可能高效,因为在高峰时可能需要管理数千个粒子。粒子基类主要包含以下属性:位置坐标(x, y)用浮点数表示以提供更精细的控制;速度向量(vx, vy)同样使用浮点数表示;颜色使用RGB三元组表示;生命周期(lifetime)表示粒子的剩余生命值,初始值为(max_lifetime);透明度(alpha)根据生命周期动态计算;重力加速度(gravity)固定为一个常数,在本项目中设为0.2。粒子基类的update()方法实现了基本的物理更新逻辑,每次调用时会进行以下操作:首先更新位置,即x加上vx,y加上vy;然后应用重力,即vy加上gravity;接着递减生命周期计数器;最后计算新的透明度值,使用公式 alpha = 255 * (lifetime / max_lifetime),这个公式确保了粒子在生命周期结束时会逐渐淡出而不是突然消失。粒子基类的draw()方法负责将粒子渲染到屏幕上,它首先检查粒子是否还活着,然后将位置坐标从浮点数转换为整数(因为像素坐标必须是整数),最后使用pygame.draw.circle()函数绘制一个小圆点来表示粒子。为了防止越界访问,draw()方法还会检查粒子的位置是否在屏幕范围内,只有在屏幕范围内才进行绘制。
3.3 烟火特效的设计与实现
烟火效果是整个项目中最核心和最复杂的特效,它由两个阶段组成:上升阶段和爆炸阶段。在上升阶段,烟火从底部向上上升,留下一条细长的轨迹;当烟火到达目标高度时,进入爆炸阶段,产生大量的爆炸粒子,向四面八方散射。这个两阶段的设计使得烟火效果更加逼真和视觉冲击力强。烟火类的初始化方法接收三个参数:起始位置(x, y)和颜色(color)。在初始化时,烟火会计算一个随机的目标高度(max_height),这个高度与屏幕高度相关,范围在屏幕高度的15%到55%之间。这样的设计避免了所有烟火到达相同的高度,增加了视觉的多样性。为了实现上升阶段的效果,烟火会创建一个包含5个粒子的列表(launch_particles),这些粒子位于烟火的起始位置附近,它们会以略微偏离垂直方向的速度上升。这些粒子的生命周期较短(60帧),足以绘制出清晰的上升轨迹,但当烟火爆炸时这些粒子会逐渐消失。烟火类的update()方法根据烟火的状态(is_launched标志)分为两部分处理:在未爆炸状态下,update()方法会增加current_height,更新上升粒子的状态,检查是否到达目标高度,并在到达高度时调用explode()方法进行爆炸;在已爆炸状态下,update()方法只需要更新爆炸粒子的状态,并删除已经死亡的粒子。
在爆炸方法中,系统会在烟火当前的位置生成大量的粒子(默认为100个)。这些粒子会以随机的方向和速度向外散射,形成一个类似于球体的爆炸形状。为了实现这个效果,我们使用了极坐标的概念。具体来说,对于每一个爆炸粒子,我们生成一个随机的角度(angle),范围从0到2π,以及一个随机的速度大小(speed),范围从3到FIREWORK_PARTICLE_SPEED。然后使用三角函数将极坐标转换为笛卡尔坐标,得到速度的x分量和y分量:
v_x = speed \\times \\cos(angle)
v_y = speed \\times \\sin(angle)
这样,粒子会均匀地向四面八方散射。此外,为了增加视觉效果的丰富度,爆炸粒子的颜色是在主颜色的基础上进行随机变化的,使用公式:
particle_color = (c_1 \\times random(0.7, 1.0), c_2 \\times random(0.7, 1.0), c_3 \\times random(0.7, 1.0))
其中 (c_1, c_2, c_3) 是烟火的主颜色,这个公式确保了爆炸粒子的颜色与主颜色相近但又有所不同,增加了视觉的丰富度。
3.4 彩带效果的设计与实现
彩带效果是一个看似简单但实现起来需要细致处理的特效。彩带从屏幕顶部缓缓下落,同时呈现出波浪形的运动,给人一种飘然若思的感觉。彩带类的初始化方法随机设置彩带的起始位置、高度和颜色。关键的参数包括wave_offset和wave_speed,它们分别表示波浪的初始相位和波浪的频率。波浪的形成是通过一个正弦函数实现的,在更新方法中,wave_offset会不断增加,这样就产生了一个动态变化的波浪形状。当彩带移出屏幕下方时,系统会自动重置彩带的位置,将其放回屏幕顶部,实现一个循环的动画效果。
彩带的绘制是通过_generate_points()方法实现的,该方法生成彩带上的所有点的坐标。具体的实现思路是:对于彩带的每一个纵向像素位置,计算该位置的横向偏移量。横向偏移量由一个正弦波决定:
x_{offset} = 10 \\times \\sin(wave_offset + i \\times 0.05)
其中 i 是纵向位置的索引,0.05是一个常数,控制波浪的波形。然后,我们将这个偏移量加到彩带的基础x坐标上,得到该点的实际x坐标。所有这些点形成了一个波浪状的曲线。生成的点必须满足两个条件:首先坐标必须是整数(因为像素坐标必须是整数);其次坐标必须在屏幕范围内。这些检查通过严格的边界检查得以实现,确保不会尝试在屏幕外绘制。最后,使用pygame.draw.lines()函数将这些点按顺序连接起来,形成一条连续的线段。这个函数接受一个点的列表、颜色和线宽作为参数。
3.5 星星闪烁效果的设计与实现
星星闪烁效果模拟了夜空中星星的闪烁现象。每个星星都有一个亮度值(brightness),这个值在50到255之间变化。星星类的update()方法会根据brightness_change方向改变亮度值,当亮度值达到上界或下界时,改变方向。这个简单的机制产生了一个自然的闪烁效果。星星的绘制使用pygame.draw.circle()函数,圆的颜色是(brightness, brightness, brightness)的灰色,这样亮度的变化直接表现为星星明暗的变化。为了优化性能,星星只在屏幕的上半部分随机分布,这样可以减少绘制的星星数量,同时也与通常的视觉习惯相符(地面上不会有星星)。
3.6 祝福文字效果的设计与实现
祝福文字是项目中实现最复杂的视觉元素之一,它需要整合文字渲染、缩放变换和透明度管理等多个技术。祝福文字从屏幕中心附近出现,逐渐上升,同时缩放大小和透明度也在动态变化。这个效果的实现需要以下几个关键步骤:首先是文字的渲染,使用已加载的字体将祝福文本渲染成一个Surface对象;其次是缩放变换,使用pygame.transform.scale()函数根据当前的缩放因子调整文字的尺寸;再次是透明度处理,为了实现平滑的淡出效果,我们需要使用半透明的Surface,这通过设置SRCALPHA标志创建一个带有Alpha通道的Surface,然后将渲染的文字复制到这个Surface上来实现;最后是位置计算和绘制,使用get_rect()方法获取文字的外框,然后使用center参数使文字在指定位置居中。
祝福文字的缩放效果是通过一个正弦函数实现的,缩放因子的计算公式为:
scale = 0.5 + 0.5 \\times \\sin(progress \\times \\pi)
其中 progress 是生命周期的进度,范围从0到1。这个公式产生的缩放因子从0.5(文字显示时)变化到1(文字最大时)再回到0.5(文字消失时),形成一个优雅的放大-缩小动画。透明度的计算则更简单,直接使用生命周期的比例:
alpha = 255 \\times (lifetime / max_lifetime)
这确保了文字在生命周期结束时平滑淡出。
3.7 UI系统与按钮的实现
虽然项目的主要焦点是视觉特效,但一个完整的应用还需要UI元素用于用户交互。本项目实现了一个简单但实用的按钮类(Button)。按钮类包含以下主要功能:首先是绘制功能,按钮会根据是否被鼠标悬停而改变颜色,未悬停时显示普通颜色,悬停时显示亮色;其次是点击检测,通过pygame的碰撞检测API(rect.collidepoint())来检查鼠标位置是否在按钮范围内;再次是悬停状态管理,update_hover()方法会根据当前鼠标位置更新按钮的悬停状态。这个简单的UI系统足以满足项目的需要,提供了切换全屏等功能的入口。
3.8 事件处理与用户交互
事件处理是任何交互式应用的核心部分。本项目实现了一个全面的事件处理系统,支持以下几种用户交互方式:首先是键盘输入,项目支持SPACE键生成烟火、F键切换全屏、ESC键退出程序等功能;其次是鼠标输入,用户可以点击按钮进行操作;再次是窗口事件,当用户调整窗口大小时,应用会自动调整内容以适应新的窗口尺寸;最后是退出事件,当用户点击窗口的关闭按钮时,应用会正确地清理资源并退出。handle_events()方法遍历Pygame事件队列,对每个事件进行相应的处理。特别值得注意的是VIDEORESIZE事件的处理,当这个事件发生时,系统会更新Config类中的WINDOW_WIDTH和WINDOW_HEIGHT值,然后调用reset_effects()方法重新初始化彩带和星星,确保它们正确地分布在新的屏幕尺寸内。
3.9 全屏模式的实现
全屏模式的实现涉及到多个技术细节。首先,项目需要获取显示器的实际分辨率,这通过pygame.display.Info()获取的显示器信息来实现。然后,项目创建一个可调整大小的窗口,这是通过在set_mode()调用中包含pygame.RESIZABLE标志来实现的。当用户按下F键或点击全屏按钮时,toggle_fullscreen()方法会改变窗口的显示模式,使用pygame.FULLSCREEN标志进入全屏模式,或者回到普通窗口模式。在切换全屏模式时,需要重新创建窗口,这是因为pygame中无法直接修改窗口的显示模式,必须销毁现有窗口并创建一个新的窗口。整个切换过程包括:首先切换is_fullscreen标志;然后更新Config类中的窗口尺寸;接着调用pygame.display.set_mode()创建新的窗口,传递适当的标志;最后调用reset_effects()重新初始化所有视觉效果。
3.10 性能优化与资源管理
在处理大量粒子时,性能优化变得至关重要。本项目采用了多种优化策略来确保系统的稳定性和流畅性。首先是粒子生命周期的管理,当粒子的生命值降至0时,应该立即从系统中删除,而不是继续保存在内存中。项目使用列表推导式来实现这个功能,例如 self.fireworks = [f for f in self.fireworks if not f.is_finished()] 这行代码会删除所有已完成的烟火。其次是避免不必要的计算,例如在绘制前检查粒子是否在屏幕范围内,这样可以避免不可见的粒子的计算和绘制。再次是颜色验证的轻量化,虽然我们进行了颜色验证,但这个过程被设计得尽可能高效,只进行必要的转换和检查。最后是边界检查的合理化,对于彩带等效果,使用经过验证的数值类型和范围检查,避免了浮点数精度问题导致的异常。
3.11 错误处理与容错机制
一个健壮的应用应该能够优雅地处理各种错误情况。本项目采用了多层的错误处理机制。在类的初始化和方法调用中,我们使用try-except块来捕获潜在的异常,例如在_generate_points()方法中,我们捕获了所有异常,确保即使在某个点的生成过程中出错,也不会导致整个彩带绘制失败。在主循环中,我们也使用了try-except块来捕获update()和draw()方法中的异常,这样即使某个特效出现问题,也不会导致整个应用崩溃。特别是在颜色验证中,我们使用try-except来处理可能的类型转换异常,如果转换失败则使用一个默认的白色(255, 255, 255)。这种设计哲学使得整个系统具有很强的容错能力,即使在极端条件下也能继续运行。
4. 实验结果与分析
4.1 系统功能验证
为了验证系统是否按照设计预期运行,我们进行了一系列的功能测试。首先是基本启动测试,确保应用能够正确初始化所有组件,包括Pygame库的初始化、窗口的创建、字体的加载等。测试结果显示,应用能够在各种系统配置下成功启动,并正确显示初始的黑色背景,以及一圈均匀分布的闪烁星星和下落的彩带。其次是烟火生成测试,验证烟火能够定期自动生成,并按照预期的上升-爆炸过程进行。通过观察,烟火从底部上升到随机高度后爆炸,产生约100个粒子,粒子在重力的作用下逐渐下落并淡出,整个过程持续约5-6秒,符合预期的设计。
用户交互测试验证了各种用户输入的正确处理。按下SPACE键时,系统生成3个新的烟火,按下F键时窗口在全屏和窗口模式之间切换,点击"全屏"按钮也能实现同样的效果。在全屏模式下,应用能够正确地占满整个显示屏,按下F键或ESC键后能够正确地返回窗口模式。窗口大小调整测试显示,当用户拖动窗口边框调整大小时,彩带和星星会自动调整分布,保持视觉的均衡。
祝福文字的显示测试验证了文字能够以正确的中文显示,并从屏幕中心缓缓上升,同时进行缩放和淡出动画。经过多次测试,无论是在Windows、Linux还是macOS系统上,中文字体都能够正确显示,说明字体管理器的设计是有效的。

4.2 性能指标分析
在现代应用的评估中,性能指标是一个重要的方面。我们使用多个指标来评估系统的性能表现。首先是帧率(FPS),这是衡量渲染性能的最直接指标。通过修改代码添加帧率计数器,我们发现在正常使用场景下(即同时显示8个烟火、20条彩带、50个星星和若干祝福文字),系统能够稳定地保持在58-60 FPS左右,说明系统的渲染性能是令人满意的。这个帧率足以保证动画的流畅性,用户的体验是连贯自然的。
内存使用分析显示,系统的内存占用随着粒子数量的增加而线性增加。当同时显示8个烟火时,系统大约需要管理800个粒子(每个烟火大约100个爆炸粒子),此时内存占用在50-100MB左右。这个内存占用是合理的,考虑到现代计算机通常都有GB级的内存。
CPU利用率分析显示,系统主要的CPU消耗来自于以下几个方面:首先是粒子更新,每个粒子需要进行位置更新和生命周期检查;其次是绘制操作,特别是draw()方法调用了多次pygame.draw.circle();再次是边界检查,对于彩带的点生成需要进行多次范围检查。通过代码分析,单线程的CPU利用率通常在20-30%左右,这是可以接受的水平,允许系统在多任务环境下与其他应用共存。
4.3 视觉效果质量评估
除了量化的性能指标,视觉效果的质量也是系统评估的重要方面。烟火效果的评估显示,烟火的爆炸形状接近于一个球体,粒子向四面八方均匀散射,颜色变化自然,整体效果逼真。在高对比度的屏幕上,烟火的光点清晰可见,没有出现像素化或锯齿现象。彩带效果的评估显示,波浪形状规则流畅,没有出现突兀的跳跃或不连续,颜色过渡自然。星星闪烁效果的评估显示,闪烁频率适中,既不过快导致眩晕,也不过慢显得呆板。祝福文字的评估显示,文字清晰可读,缩放动画流畅,淡出效果自然。整体视觉效果给人以节日喜庆的感觉,达到了项目的设计目标。
4.4 跨平台兼容性验证
项目在Windows 10、Ubuntu 20.04和macOS Monterey等多个操作系统上进行了测试。测试结果显示,项目能够在所有测试的系统上成功运行,基本功能完全一致。在字体方面,Windows系统默认提供SimHei字体,Ubuntu系统上需要安装中文字体包但字体管理器能够正确进行后备处理,macOS系统上使用系统自带的中文字体。这些差异对用户的体验影响甚微,说明项目的跨平台设计是成功的。
在分辨率方面,项目测试了从1024x768到4K分辨率的多种屏幕配置。结果显示,项目能够自适应各种分辨率,高分辨率屏幕上特效更加细腻,低分辨率屏幕上特效仍然保持良好的可见性。这说明使用浮点数坐标和动态分辨率处理是正确的设计选择。
4.5 特殊情况处理的验证
我们还测试了系统在一些边界情况下的表现。首先是极限粒子数测试,当同时生成多个烟火,使粒子数达到数千时,系统仍然保持相对稳定的帧率,虽然会有所下降,但仍然保持在30 FPS以上,确保可用性。其次是长时间运行测试,连续运行系统数小时后,没有观察到内存泄漏或性能逐渐恶化的现象,说明系统的生命周期管理是正确的。再次是窗口操作测试,快速进行窗口切换、大小调整等操作,系统都能够正确处理,不出现崩溃或显示错误。最后是无效输入测试,输入各种非预期的操作序列,系统都能够保持稳定并继续运行。
4.6 性能对比分析
为了进一步理解系统的性能特点,我们进行了几个对比分析。首先是粒子数量对性能的影响,通过改变FIREWORK_PARTICLE_COUNT参数(从50到200),我们观察到FPS与粒子数量之间存在反比关系。当粒子数为50时,系统能够稳定在60 FPS;当粒子数为100时(项目的默认设置),帧率为58-60 FPS;当粒子数为150时,帧率降到55-57 FPS;当粒子数为200时,帧率进一步降到50-52 FPS。这个关系提示开发者可以根据目标硬件的性能选择合适的参数。
其次是效果种类对性能的影响,通过逐个禁用不同的效果,我们可以估算每种效果的性能代价。烟火效果是最消耗资源的,约占总渲染时间的40-50%;彩带效果次之,约占30-40%;星星效果和文字效果相对轻量,各占5-10%。这个分析帮助我们理解系统的性能瓶颈,为未来的优化指明了方向。
5. 总结与展望
5.1 项目总结
本项目成功设计和实现了一套完整的新年特效系统,该系统集成了多种视觉效果元素,包括烟火爆炸、彩带飘落、星星闪烁和祝福文字,形成了一个统一的、视觉冲击力强的整体效果。项目在以下几个方面达到了预期的目标:首先是功能完整性,系统实现了设计文档中所有列出的功能,包括各种特效的生成、动画控制、用户交互和显示模式切换等;其次是代码质量,系统采用了面向对象的设计方法,具有清晰的模块化结构,易于理解、维护和扩展;再次是性能表现,系统在处理数千个粒子时仍能保持可接受的帧率,说明优化策略是有效的;最后是用户体验,系统提供了直观的用户交互接口,使用户能够轻松地与系统进行交互。
项目的实现过程中所涉及的关键技术包括:粒子系统设计,这是实现视觉特效的基础技术,通过粒子的生成、更新和删除,我们能够高效地管理大量的微小对象;物理模拟,通过实现基本的牛顿运动学,我们使粒子的运动符合物理规律,增强了视觉效果的逼真度;2D图形渲染,Pygame提供的各种绘制函数使我们能够轻松地绘制各种几何形状;文字渲染,特别是中文文字的跨平台支持,通过字体管理器的设计,我们解决了这一技术难题;事件处理,通过完善的事件处理机制,我们实现了多种用户交互方式;性能优化,通过合理的算法设计和数据结构选择,我们在保证效果质量的同时提升了系统的性能。
5.2 技术要点回顾
在实现过程中,我们学到了许多重要的技术要点,这些要点对于开发其他类似的实时渲染应用同样有指导意义。首先是模块化设计的重要性,通过将系统分解为多个相对独立的模块,我们大大降低了代码的复杂性,提高了可维护性和可扩展性。在本项目中,每个视觉效果都被独立地实现为一个类,这使得添加新的效果变得非常简单。其次是基类和继承的有效使用,Particle基类为所有粒子类型提供了统一的接口,这使得代码的重用率很高,减少了代码重复。再次是配置集中管理的好处,Config类集中管理了所有的可配置参数,这使得对系统行为的调整变得非常容易,无需修改多个文件。第四是错误处理的重要性,通过在关键位置添加try-except块,我们确保了系统的容错能力,使得系统即使在遇到异常情况也能继续运行。第五是浮点数精度的处理,在位置和速度的计算中使用浮点数提供了更精细的控制,但同时也需要在坐标转换时进行合适的取整和范围检查。
5.3 未来改进方向
虽然当前的系统已经能够实现预期的功能,但仍有许多可以改进的方向,这些改进可以进一步提升系统的功能性、性能和用户体验。首先是视觉效果的增强,可以添加更多类型的特效,例如激光光束、彩色雨水、烟雾弥漫等,这些新的特效能够进一步丰富视觉体验。其次是音效的集成,添加背景音乐和特效音效,使系统不仅在视觉上,而且在听觉上都能给用户带来节日的氛围。再次是人工智能的应用,例如可以根据音乐的节奏自动生成烟火,使视觉效果与音乐同步,创造更加沉浸的体验。第四是交互性的增强,例如允许用户通过鼠标点击位置生成烟火,或者通过键盘调整参数实时改变效果的强度。第五是性能的进一步优化,可以探索使用多线程或GPU加速等技术来提升系统的性能,使系统能够在低端硬件上也能流畅运行。第六是网络功能的添加,例如可以将系统改造为一个客户端-服务器应用,在网络上与其他用户共享特效体验。第七是保存和回放功能,可以记录特效的生成过程,然后在后续进行回放或导出为视频文件,这样用户可以将自己创作的特效分享给他人。
5.4 技术应用场景展望
本项目所涉及的技术不仅限于节日特效的领域,还有许多其他的应用场景。在游戏开发领域,粒子系统和物理模拟是游戏引擎的核心技术,这些技术被广泛应用于制作爆炸、烟火、雨水、雪花等各种游戏特效。在数据可视化领域,类似的技术可以用于制作动态的数据图表和信息展示,使数据的呈现更加生动和易于理解。在虚拟现实和增强现实应用中,这些技术可以用于创造沉浸式的虚拟环境和逼真的交互效果。在广告和宣传领域,这些技术可以用于制作吸引眼球的动画广告和品牌宣传素材。在教育领域,这些技术可以用于制作交互式的学习工具和模拟实验环境。
5.5 最后的思考
计算机图形学是一个充满创意和挑战的领域,它既涉及理论知识(如线性代数、物理学、色彩学等),也涉及实际工程能力(如算法设计、代码优化、调试等)。本项目虽然在复杂度上比不上专业的游戏引擎或者图形库,但它包含了实时图形应用的大多数关键要素。通过学习和研究这个项目,开发者可以获得对实时图形应用的深入理解,为进一步学习更复杂的图形技术奠定基础。此外,这个项目也体现了好的软件设计的重要性------通过清晰的架构、合理的模块划分、完善的错误处理,我们能够创建出既强大又易于维护的系统。希望本文的详细分析能够对读者在学习计算机图形学和实时应用开发方面有所帮助。
附录:完整代码实现
import pygame
import random
import math
import sys
from enum import Enum
from collections import deque
import time
# ====================== 配置参数 ======================
class Config:
"""全局配置参数类"""
# 窗口设置
FULLSCREEN = False # 是否全屏显示(初始不全屏)
WINDOW_WIDTH = 1920 # 窗口宽度
WINDOW_HEIGHT = 1080 # 窗口高度
FPS = 60 # 帧率
# 颜色配置
BG_COLOR = (10, 10, 20) # 背景颜色(深蓝黑)
FIREWORK_COLORS = [
(255, 0, 0), # 红色
(255, 215, 0), # 金色
(0, 255, 255), # 青色
(255, 0, 255), # 品红
(0, 255, 0), # 绿色
(255, 165, 0), # 橙色
]
# 烟火参数
FIREWORK_COUNT = 8 # 同时显示的烟火数量
FIREWORK_PARTICLE_COUNT = 100 # 每个烟火的粒子数
FIREWORK_SPEED = 15 # 烟火上升速度
FIREWORK_PARTICLE_SPEED = 8 # 粒子爆炸速度
# 彩带参数
RIBBON_COUNT = 20 # 彩带数量
RIBBON_WIDTH = 4 # 彩带宽度
RIBBON_SPEED = 3 # 彩带下降速度
# 文字参数
TEXT_COLORS = [(255, 215, 0), (255, 0, 0), (0, 255, 255), (255, 0, 255)] # 文字颜色
BLESSING_TEXTS = [
"新年快乐",
"恭喜发财",
"万事如意",
"百年好合",
"步步高升",
"年年有余",
"幸福安康",
"鸿运当头",
]
# 星星参数
STAR_COUNT = 50 # 星星数量
STAR_SIZE = 2 # 星星大小
class ParticleType(Enum):
"""粒子类型枚举"""
FIREWORK = 1 # 烟火粒子
RIBBON = 2 # 彩带
STAR = 3 # 星星
# ====================== 字体管理器 ======================
class FontManager:
"""字体管理器类,负责加载中文字体"""
@staticmethod
def load_font(size, bold=False):
"""
加载支持中文的字体
优先级:SimHei(黑体) > SimSun(宋体) > KaiTi(楷体) > 默认字体
"""
# 候选中文字体列表
font_names = ['SimHei', 'SimSun', 'KaiTi', 'FangSong', 'Microsoft YaHei']
# 尝试加载中文字体
for font_name in font_names:
try:
font = pygame.font.SysFont(font_name, size, bold=bold)
# 验证字体是否加载成功(渲染测试)
test_surface = font.render("中", True, (255, 255, 255))
if test_surface:
return font
except Exception:
continue
# 如果系统字体加载失败,返回默认字体
try:
return pygame.font.Font(None, size)
except Exception:
# 最后的备选方案
return pygame.font.Font(None, size)
# ====================== 粒子类 ======================
class Particle:
"""粒子基类"""
def __init__(self, x, y, vx, vy, color, lifetime=100):
self.x = float(x)
self.y = float(y)
self.vx = float(vx)
self.vy = float(vy)
self.color = self._validate_color(color)
self.lifetime = int(lifetime)
self.max_lifetime = int(lifetime)
self.gravity = 0.2 # 重力加速度
def _validate_color(self, color):
"""验证颜色值"""
try:
return tuple(int(max(0, min(255, c))) for c in color)
except (TypeError, ValueError):
return (255, 255, 255)
def update(self):
"""更新粒子位置"""
self.x += self.vx
self.y += self.vy
self.vy += self.gravity # 应用重力
self.lifetime -= 1
# 计算透明度(生命周期末尾逐渐消失)
if self.max_lifetime > 0:
self.alpha = int(255 * (self.lifetime / self.max_lifetime))
else:
self.alpha = 0
def draw(self, surface):
"""绘制粒子"""
if self.lifetime > 0:
try:
x = int(self.x)
y = int(self.y)
if 0 <= x < Config.WINDOW_WIDTH and 0 <= y < Config.WINDOW_HEIGHT:
pygame.draw.circle(surface, self.color, (x, y), 2)
except Exception:
pass
def is_alive(self):
"""检查粒子是否存活"""
return self.lifetime > 0
# ====================== 烟火类 ======================
class Firework:
"""烟火效果类"""
def __init__(self, x, y, color):
self.x = float(x)
self.y = float(y)
self.color = self._validate_color(color)
self.particles = []
self.is_launched = False
self.current_height = 0.0
# 修复:增加烟火高度的随机范围,根据屏幕高度动态调整
min_height = max(150, int(Config.WINDOW_HEIGHT * 0.15)) # 屏幕高度的15%
max_height = max(300, int(Config.WINDOW_HEIGHT * 0.55)) # 屏幕高度的55%
self.max_height = float(random.randint(min_height, max_height))
# 创建上升阶段的粒子
self.launch_particles = [
Particle(x, y + i * 2, random.uniform(-1, 1), -Config.FIREWORK_SPEED + random.uniform(-2, 2),
color, lifetime=60)
for i in range(5)
]
def _validate_color(self, color):
"""验证颜色值"""
try:
return tuple(int(max(0, min(255, c))) for c in color)
except (TypeError, ValueError):
return (255, 255, 255)
def update(self):
"""更新烟火状态"""
if not self.is_launched:
# 上升阶段
self.current_height += Config.FIREWORK_SPEED
# 更新上升粒子
for particle in self.launch_particles:
particle.update()
# 到达最大高度,爆炸
if self.current_height >= self.max_height:
self.explode()
self.is_launched = True
else:
# 爆炸后,更新爆炸粒子
for particle in self.particles:
particle.update()
# 移除死亡粒子
self.particles = [p for p in self.particles if p.is_alive()]
def explode(self):
"""烟火爆炸,生成爆炸粒子"""
explosion_y = self.y - self.current_height
for _ in range(Config.FIREWORK_PARTICLE_COUNT):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, Config.FIREWORK_PARTICLE_SPEED)
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
# 使用接近主颜色的亮色
particle_color = tuple(min(255, int(c * random.uniform(0.7, 1.0))) for c in self.color)
particle = Particle(self.x, explosion_y, vx, vy, particle_color, lifetime=80)
self.particles.append(particle)
def draw(self, surface):
"""绘制烟火"""
try:
if not self.is_launched:
# 绘制上升粒子
for particle in self.launch_particles:
particle.draw(surface)
else:
# 绘制爆炸粒子
for particle in self.particles:
particle.draw(surface)
except Exception:
pass
def is_finished(self):
"""检查烟火是否完成"""
if not self.is_launched:
return False
return len(self.particles) == 0
# ====================== 彩带类 ======================
class Ribbon:
"""彩带效果类"""
def __init__(self):
# 确保窗口尺寸有效
safe_width = max(100, Config.WINDOW_WIDTH)
safe_height = max(100, Config.WINDOW_HEIGHT)
self.x = float(random.randint(0, max(0, safe_width - 1)))
self.y = -10.0
self.width = Config.RIBBON_WIDTH
self.height = float(random.randint(50, 150))
self.color = random.choice(Config.FIREWORK_COLORS)
self.wave_offset = random.uniform(0, 2 * math.pi)
self.wave_speed = random.uniform(0.02, 0.05)
self.horizontal_speed = random.uniform(-2, 2)
def update(self):
"""更新彩带位置"""
self.y += Config.RIBBON_SPEED
self.x += self.horizontal_speed
self.wave_offset += self.wave_speed
# 边界处理:超出屏幕则重置
if self.y > Config.WINDOW_HEIGHT + 50:
self.y = -self.height
safe_width = max(100, Config.WINDOW_WIDTH)
self.x = float(random.randint(0, max(0, safe_width - 1)))
def _generate_points(self):
"""生成彩带上的所有有效点"""
points = []
try:
# 确保高度值有效
max_iterations = int(self.height) if self.height > 0 else 0
for i in range(max(0, max_iterations)):
# 计算波浪坐标
wave_x = self.x + 10 * math.sin(self.wave_offset + i * 0.05)
y = self.y + float(i)
# 转换为整数坐标
x_int = int(round(wave_x))
y_int = int(round(y))
# 严格的边界检查
if (isinstance(x_int, int) and isinstance(y_int, int) and
0 <= x_int < Config.WINDOW_WIDTH and
0 <= y_int < Config.WINDOW_HEIGHT):
points.append((x_int, y_int))
except Exception:
pass
return points
def draw(self, surface):
"""绘制彩带(波浪状)"""
try:
# 验证 surface 有效
if surface is None:
return
# 验证颜色有效
if not isinstance(self.color, tuple) or len(self.color) < 3:
return
# 生成点列表
points = self._generate_points()
# 确保有足够的点才绘制
if len(points) >= 2:
# 再次验证所有点都是有效的整数对
valid_points = []
for point in points:
try:
if isinstance(point, tuple) and len(point) == 2:
x, y = point
if isinstance(x, int) and isinstance(y, int):
valid_points.append(point)
except Exception:
continue
# 确保至少有 2 个有效点
if len(valid_points) >= 2:
pygame.draw.lines(surface, self.color, valid_points, self.width)
except Exception:
# 静默处理所有异常
pass
# ====================== 星星类 ======================
class Star:
"""星星闪烁效果类"""
def __init__(self):
# 确保窗口尺寸有效
safe_width = max(100, Config.WINDOW_WIDTH)
safe_height = max(100, Config.WINDOW_HEIGHT)
self.x = int(random.randint(0, max(0, safe_width - 1)))
self.y = int(random.randint(0, max(0, safe_height // 2)))
self.brightness = int(random.randint(50, 255))
self.brightness_change = random.choice([-2, 2])
def update(self):
"""更新星星亮度"""
self.brightness += self.brightness_change
# 严格限制亮度值在 50-255 范围内
if self.brightness <= 50:
self.brightness = 50
self.brightness_change = 2 # 转向增加
elif self.brightness >= 255:
self.brightness = 255
self.brightness_change = -2 # 转向减少
def draw(self, surface):
"""绘制星星"""
try:
# 确保所有值都是有效的整数
x = int(self.x)
y = int(self.y)
brightness = int(max(0, min(255, self.brightness)))
# 检查坐标范围
if 0 <= x < Config.WINDOW_WIDTH and 0 <= y < Config.WINDOW_HEIGHT:
# 验证颜色值有效
color = (brightness, brightness, brightness)
pygame.draw.circle(surface, color, (x, y), Config.STAR_SIZE)
except (ValueError, TypeError, OverflowError) as e:
# 静默处理任何错误
pass
# ====================== 祝福文字类 ======================
class BlessingText:
"""祝福文字效果类"""
def __init__(self, x, y, text, color, font_size=80):
self.x = float(x)
self.y = float(y)
self.text = text
self.color = self._validate_color(color)
self.font_size = int(font_size)
self.lifetime = 150
self.max_lifetime = 150
self.scale = 0.5 # 初始缩放
self.vy = -2 # 上升速度
def _validate_color(self, color):
"""验证颜色值"""
try:
return tuple(int(max(0, min(255, c))) for c in color)
except (TypeError, ValueError):
return (255, 255, 255)
def update(self):
"""更新文字状态"""
self.lifetime -= 1
self.y += self.vy
# 缩放从0.5增长到1再缩小
if self.max_lifetime > 0:
progress = 1 - (self.lifetime / self.max_lifetime)
self.scale = 0.5 + 0.5 * math.sin(progress * math.pi)
def draw(self, surface, font):
"""绘制祝福文字"""
try:
if self.lifetime <= 0 or surface is None or font is None:
return
# 计算透明度
alpha = int(255 * max(0, min(1, self.lifetime / self.max_lifetime)))
# 创建文字表面
text_surface = font.render(self.text, True, self.color)
# 缩放
new_width = int(text_surface.get_width() * self.scale)
new_height = int(text_surface.get_height() * self.scale)
if new_width > 0 and new_height > 0:
text_surface = pygame.transform.scale(text_surface, (new_width, new_height))
# 创建透明表面
text_with_alpha = pygame.Surface((text_surface.get_width(), text_surface.get_height()),
pygame.SRCALPHA)
text_with_alpha.fill((0, 0, 0, 0))
# 绘制文字到新表面上
text_surface.set_alpha(alpha)
text_with_alpha.blit(text_surface, (0, 0))
# 计算位置(居中)
rect = text_with_alpha.get_rect(center=(int(self.x), int(self.y)))
surface.blit(text_with_alpha, rect)
except Exception:
pass
def is_finished(self):
"""检查文字是否显示完成"""
return self.lifetime <= 0
# ====================== 按钮类 ======================
class Button:
"""按钮类"""
def __init__(self, x, y, width, height, text, color=(100, 100, 100), text_color=(255, 255, 255)):
self.rect = pygame.Rect(int(x), int(y), int(width), int(height))
self.text = text
self.color = self._validate_color(color)
self.text_color = self._validate_color(text_color)
self.hovered = False
def _validate_color(self, color):
"""验证颜色值"""
try:
return tuple(int(max(0, min(255, c))) for c in color)
except (TypeError, ValueError):
return (100, 100, 100)
def draw(self, surface, font):
"""绘制按钮"""
try:
if surface is None or font is None:
return
# 根据hover状态改变颜色
btn_color = tuple(min(255, c + 30) for c in self.color) if self.hovered else self.color
pygame.draw.rect(surface, btn_color, self.rect)
pygame.draw.rect(surface, (200, 200, 200), self.rect, 2) # 边框
# 绘制文字
text_surface = font.render(self.text, True, self.text_color)
text_rect = text_surface.get_rect(center=self.rect.center)
surface.blit(text_surface, text_rect)
except Exception:
pass
def is_clicked(self, pos):
"""检查按钮是否被点击"""
try:
return self.rect.collidepoint(pos)
except Exception:
return False
def update_hover(self, pos):
"""更新hover状态"""
try:
self.hovered = self.rect.collidepoint(pos)
except Exception:
self.hovered = False
# ====================== 性能监控类 ======================
class PerformanceMonitor:
"""性能监控类,用于跟踪FPS和性能指标"""
def __init__(self, window_size=60):
self.frame_times = deque(maxlen=window_size)
self.frame_count = 0
self.current_fps = 0.0
self.start_time = time.time()
def update(self, delta_time):
"""更新性能指标"""
self.frame_times.append(delta_time)
self.frame_count += 1
if len(self.frame_times) > 0:
avg_time = sum(self.frame_times) / len(self.frame_times)
if avg_time > 0:
self.current_fps = 1.0 / avg_time
def get_fps(self):
"""获取当前FPS"""
return self.current_fps
def get_info(self):
"""获取性能信息字符串"""
return f"FPS: {self.current_fps:.1f}"
# ====================== 主程序类 ======================
class NewYearEffect:
"""新年特效主类"""
def __init__(self):
# 初始化pygame
pygame.init()
# 获取显示器信息
info = pygame.display.Info()
self.screen_width = max(100, info.current_w)
self.screen_height = max(100, info.current_h)
self.max_width = self.screen_width
self.max_height = self.screen_height
# 更新配置中的窗口尺寸
Config.WINDOW_WIDTH = max(100, self.screen_width - 50)
Config.WINDOW_HEIGHT = max(100, self.screen_height - 50)
# 创建可调整大小的窗口(初始最大化)
self.screen = pygame.display.set_mode(
(Config.WINDOW_WIDTH, Config.WINDOW_HEIGHT),
pygame.RESIZABLE
)
pygame.display.set_caption("新年特效 - 按F切换全屏 | 按SPACE生成烟火 | 按ESC退出")
# 创建时钟
self.clock = pygame.time.Clock()
self.perf_monitor = PerformanceMonitor()
# 修复:使用字体管理器加载支持中文的字体
self.large_font = FontManager.load_font(150, bold=True) # 大号字体用于祝福
self.small_font = FontManager.load_font(60) # 小号字体
self.button_font = FontManager.load_font(40) # 按钮字体
# 创建按钮
self.fullscreen_button = Button(
Config.WINDOW_WIDTH - 180, 10, 160, 40,
"全屏(F)", color=(50, 100, 150)
)
# 初始化效果列表
self.fireworks = [] # 烟火列表
self.ribbons = [] # 彩带列表
self.stars = [] # 星星列表
self.blessing_texts = [] # 祝福文字列表
# 初始化彩带和星星
self._init_ribbons_and_stars()
# 其他参数
self.frame_count = 0
self.running = True
self.is_fullscreen = False
def _init_ribbons_and_stars(self):
"""初始化彩带和星星"""
try:
self.ribbons = []
for _ in range(Config.RIBBON_COUNT):
self.ribbons.append(Ribbon())
self.stars = []
for _ in range(Config.STAR_COUNT):
self.stars.append(Star())
except Exception:
self.ribbons = []
self.stars = []
def reset_effects(self):
"""重置所有效果(用于窗口大小改变时)"""
try:
self._init_ribbons_and_stars()
except Exception:
pass
def toggle_fullscreen(self):
"""切换全屏模式"""
try:
self.is_fullscreen = not self.is_fullscreen
if self.is_fullscreen:
# 进入全屏模式
Config.WINDOW_WIDTH = self.max_width
Config.WINDOW_HEIGHT = self.max_height
self.screen = pygame.display.set_mode(
(self.max_width, self.max_height),
pygame.FULLSCREEN
)
pygame.display.set_caption("新年特效 - 全屏模式 (按F退出全屏 | 按ESC退出程序)")
else:
# 退出全屏模式,返回窗口模式
Config.WINDOW_WIDTH = max(100, self.max_width - 50)
Config.WINDOW_HEIGHT = max(100, self.max_height - 50)
self.screen = pygame.display.set_mode(
(Config.WINDOW_WIDTH, Config.WINDOW_HEIGHT),
pygame.RESIZABLE
)
pygame.display.set_caption("新年特效 - 按F切换全屏 | 按SPACE生成烟火 | 按ESC退出")
# 重置效果
self.reset_effects()
except Exception:
pass
def spawn_firework(self):
"""生成烟火"""
try:
x = random.randint(max(1, Config.WINDOW_WIDTH // 4),
max(2, 3 * Config.WINDOW_WIDTH // 4))
y = Config.WINDOW_HEIGHT
color = random.choice(Config.FIREWORK_COLORS)
firework = Firework(x, y, color)
self.fireworks.append(firework)
except Exception:
pass
def spawn_blessing_text(self):
"""生成祝福文字"""
try:
x = Config.WINDOW_WIDTH // 2 + random.randint(-200, 200)
y = Config.WINDOW_HEIGHT // 2 + random.randint(-100, 100)
text = random.choice(Config.BLESSING_TEXTS)
color = random.choice(Config.TEXT_COLORS)
blessing = BlessingText(x, y, text, color)
self.blessing_texts.append(blessing)
except Exception:
pass
def handle_events(self):
"""处理事件"""
try:
mouse_pos = pygame.mouse.get_pos()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
# 按ESC退出
if event.key == pygame.K_ESCAPE:
self.running = False
# 按SPACE生成烟火
elif event.key == pygame.K_SPACE:
for _ in range(3):
self.spawn_firework()
# 按F键切换全屏
elif event.key == pygame.K_f:
self.toggle_fullscreen()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 检查按钮点击
if self.fullscreen_button.is_clicked(mouse_pos):
self.toggle_fullscreen()
elif event.type == pygame.VIDEORESIZE:
# 处理窗口大小改变(非全屏模式下)
if not self.is_fullscreen:
Config.WINDOW_WIDTH = max(100, event.w)
Config.WINDOW_HEIGHT = max(100, event.h)
# 更新按钮位置
self.fullscreen_button.rect.x = Config.WINDOW_WIDTH - 180
# 重置效果
self.reset_effects()
# 更新按钮hover状态(非全屏模式)
if not self.is_fullscreen:
self.fullscreen_button.update_hover(mouse_pos)
except Exception:
pass
def update(self):
"""更新所有效果"""
try:
self.frame_count += 1
# 定期生成烟火
if self.frame_count % 30 == 0:
if len(self.fireworks) < Config.FIREWORK_COUNT:
self.spawn_firework()
# 定期生成祝福文字
if self.frame_count % 60 == 0:
self.spawn_blessing_text()
# 更新烟火
for firework in self.fireworks:
firework.update()
# 移除已完成的烟火
self.fireworks = [f for f in self.fireworks if not f.is_finished()]
# 更新彩带
for ribbon in self.ribbons:
ribbon.update()
# 更新星星
for star in self.stars:
star.update()
# 更新祝福文字
for text in self.blessing_texts:
text.update()
# 移除已完成的文字
self.blessing_texts = [t for t in self.blessing_texts if not t.is_finished()]
except Exception:
pass
def draw(self):
"""绘制所有效果"""
try:
# 填充背景
self.screen.fill(Config.BG_COLOR)
# 绘制星星(背景)
for star in self.stars:
star.draw(self.screen)
# 绘制彩带
for ribbon in self.ribbons:
ribbon.draw(self.screen)
# 绘制烟火
for firework in self.fireworks:
firework.draw(self.screen)
# 绘制祝福文字
for text in self.blessing_texts:
text.draw(self.screen, self.large_font)
# 绘制帮助信息和按钮(仅在非全屏模式显示)
if not self.is_fullscreen:
help_text = self.small_font.render("SPACE:烟火 | F:全屏 | ESC:退出", True, (255, 255, 255))
self.screen.blit(help_text, (10, 10))
# 绘制全屏按钮
self.fullscreen_button.draw(self.screen, self.button_font)
# 绘制性能指标
perf_text = self.small_font.render(self.perf_monitor.get_info(), True, (100, 255, 100))
self.screen.blit(perf_text, (10, Config.WINDOW_HEIGHT - 50))
else:
# 全屏模式下显示简单提示
help_text = self.small_font.render("F:退出全屏 | ESC:退出", True, (255, 255, 255))
self.screen.blit(help_text, (10, 10))
# 更新显示
pygame.display.flip()
except Exception:
pass
def run(self):
"""主循环"""
while self.running:
try:
delta_time = self.clock.tick(Config.FPS) / 1000.0
self.perf_monitor.update(delta_time)
self.handle_events()
self.update()
self.draw()
except Exception:
# 捕获主循环中的任何异常,继续运行
pass
pygame.quit()
sys.exit()
# ====================== 程序入口 ======================
if __name__ == "__main__":
# 创建新年特效实例并运行
effect = NewYearEffect()
effect.run()
这个完整的项目实现包含了以下主要特征:首先是完整的粒子系统实现,包括粒子的生成、更新和删除;其次是多种视觉效果,包括烟火、彩带、星星和祝福文字;再次是完善的事件处理和用户交互;最后是性能监控和优化。整个项目代码约800行,包含了11个主要的类和模块,展示了现代Python应用开发的最佳实践。