C/C++实现动态爱心动画:从数学方程到粒子系统实战
1. 项目概述从一行代码到一颗跳动的“心”如果你对C/C的印象还停留在黑底白字的控制台、枯燥的算法题和复杂的系统底层那今天这个项目可能会彻底改变你的看法。用C/C实现一个动态的爱心动画听起来像是用一把手术刀去雕刻一件艺术品既考验着你对这门“硬核”语言的掌控力也激发着将冰冷逻辑转化为温暖视觉的创作欲。这不仅仅是写一段“会动”的代码更是一次融合了数学之美、图形学基础和编程技巧的综合性实践。这个项目的核心目标很明确在屏幕上绘制一个符合数学定义的爱心形状并让它“活”起来产生诸如缩放、脉动、粒子扩散等动态效果。它适合所有阶段的C/C学习者。对于初学者这是一个绝佳的、目标感极强的练手项目能让你快速看到图形化输出的成就感理解事件循环、坐标系统等基础概念。对于有一定经验的开发者你可以深入探究贝塞尔曲线优化、多线程渲染、物理模拟等高级主题将其打造成一个技术展示的“名片”。网络上流传着各种版本的“爱心代码”从最简单的字符拼图到复杂的3D渲染都有。但很多代码要么过于简陋效果生硬要么封装过度像个黑盒让人知其然不知其所以然。我打算带你从头开始一步步拆解不仅给你能直接运行出效果的代码更重要的是讲清楚每一个参数、每一行代码背后的“为什么”。我们会从最基础的爱心函数方程讲起探讨如何在不同的图形库如EasyX, SDL, OpenGL中实现它并最终赋予它生命。你会发现实现一颗“会呼吸”的爱心其技术内核与游戏开发、数据可视化等领域是相通的。2. 核心思路与方案选型为何不用现成的轮子在动手写第一行代码之前我们必须先解决两个根本问题爱心的形状从哪里来我们用什么工具把它画出来并让它动起来这两个问题的答案直接决定了项目的技术路径和最终效果的天花板。2.1 爱心形状的数学本源参数方程 vs 隐函数方程爱心不是一个标准的几何图形但它有一个被广泛认可的数学表达。最常见的有两种方式参数方程这是最直观、最适合逐点绘制的方法。一个经典的爱心参数方程如下x 16 * sin³(t) y 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t)其中t是参数通常取值范围是[0, 2π)。当t从0变化到2π时(x, y)的轨迹就会画出一个爱心。这种方法的优点是与绘图逻辑循环t计算(x, y)画点完美契合易于理解和实现缩放、旋转等变换。隐函数方程例如(x² y² - 1)³ x² * y³。这个方程描述的是平面上所有满足该等式的点(x, y)的集合其图形也是一个爱心。隐函数方程的优点是数学上更“优雅”但绘制起来比较麻烦通常需要数值方法如Marching Squares来求解不适合初学者直接用于动态绘图。为什么我们选择参数方程对于动态动画来说我们需要频繁地计算图形上大量点的位置。参数方程直接给出了坐标关于参数t的显式表达式计算效率极高。而隐函数方程需要解方程计算成本高不适用于实时渲染。因此绝大多数动态爱心动画都基于参数方程实现。2.2 图形库选型控制台、2D库还是3D库C/C标准库本身没有图形功能我们必须借助第三方库。选择哪个库取决于你的目标平台、性能要求和学习曲线。控制台“伪图形”完全使用cout和空格、字符来拼凑。这种方法极度受限只能做出非常简单的“跳动”效果比如清屏重绘不同大小的字符爱心几乎无法实现平滑动画和复杂效果。不推荐用于真正的动态动画项目但可以作为理解循环和坐标的第一次尝试。EasyX / Graphics.h (Windows)这是国内许多C语言教学使用的图形库封装了Windows GDI接口接口简单极易上手。它提供了initgraph,circle,line,putpixel等函数。对于本项目而言它完全够用且学习成本最低。缺点是平台锁死Windows且功能相对基础性能一般。SDL2 (Simple DirectMedia Layer)一个跨平台的多媒体库支持Windows, macOS, Linux等。它提供了窗口管理、2D渲染软件和硬件加速、输入事件、音频等一套完整的游戏开发基础功能。SDL2更现代、更强大性能优于EasyX且是工业标准之一。如果你想做更流畅的动画或未来向游戏开发发展SDL2是更好的选择。OpenGL这是专业的跨平台图形API。用它来做2D爱心属于“大炮打蚊子”但如果你想实现带有3D景深、高级光照和粒子系统的终极华丽爱心OpenGL是唯一的选择。它的学习曲线非常陡峭。我的选择与理由 为了兼顾教学性、可实现性和效果上限我将以EasyX作为主要演示环境。因为它能让读者最快地看到成果聚焦于动画逻辑本身而不是复杂的库配置和管线设置。在后续的进阶部分我会对比性地介绍如何将核心算法迁移到SDL2上让大家看到不同库下的实现差异。对于绝大多数希望快速实现一个美观动态爱心的朋友来说EasyX方案是性价比最高的。2.3 动画驱动逻辑如何让爱心“动”起来动画的本质是一系列静态帧的快速连续播放。我们需要一个游戏主循环来驱动这个过程。循环内每一帧主要做三件事处理输入如检测按键退出。更新状态根据时间计算爱心新的形状、大小、颜色等属性。渲染输出清空上一帧画面绘制新的爱心状态。关键在于“更新状态”。让爱心动态化通常有以下几种思路缩放动画让参数方程中的x和y乘以一个随时间t周期性变化如sin(t)的缩放因子scale。这样爱心就会规律性地放大和缩小模拟“跳动”。颜色渐变让爱心的填充色或边框色的RGB值随时间变化可以产生呼吸灯般的色彩流动效果。粒子系统这是实现高级效果的关键。不直接画一个实心爱心而是用成百上千个微小的粒子点来组成爱心轮廓。每个粒子除了位置还可以拥有速度、生命周期、颜色等属性。通过更新每个粒子的状态可以实现爱心“迸发”、“消散”、“星光流转”等复杂特效。在本项目中我们将由浅入深先实现一个基础的缩放跳动爱心再升级为一个简单的粒子系统爱心让你完整掌握从静态到动态从简单到炫酷的完整技术链条。3. 基础实现绘制静态爱心与简单缩放动画让我们先从最简单的开始在EasyX环境中绘制一个静态的红色爱心然后让它按照正弦规律跳动起来。这是理解整个项目基础框架的最佳切入点。3.1 开发环境搭建与第一个窗口首先你需要一个支持EasyX的IDE。推荐使用Visual Studio或Dev-C需安装EasyX库。安装EasyX访问EasyX官网下载对应你编译器的安装包直接运行安装即可。对于VS它会自动集成到开发环境中。创建项目在VS中创建一个新的空C项目。包含头文件与入口函数#include graphics.h // EasyX图形库头文件 #include conio.h // 用于 _kbhit, _getch #include cmath // 用于 sin, cos, pow 等数学函数 #include ctime // 用于 time如果需要随机数 int main() { // 初始化图形窗口宽度800高度600 initgraph(800, 600); // 设置绘图颜色为红色 setcolor(RED); // 设置填充颜色为红色 setfillcolor(RED); // TODO: 在这里绘制爱心 // 按任意键关闭图形窗口 _getch(); closegraph(); return 0; }运行这段代码你应该能看到一个红色的窗口。这是我们动画的“画布”。3.2 实现爱心绘制函数我们将基于参数方程来实现一个绘制填充爱心的函数。思路是采样足够多的t值计算出一系列点然后用这些点构成一个多边形并填充。// 绘制一个填充爱心 // 参数中心坐标 (centerX, centerY), 大小缩放因子 scale, 颜色 color void DrawHeart(int centerX, int centerY, float scale, COLORREF color) { const int POINTS 100; // 采样点数量越多轮廓越光滑 POINT pts[POINTS 2]; // 存储多边形顶点2是为了闭合曲线 const double PI 3.141592653589793; // 生成爱心轮廓上的点 for (int i 0; i POINTS; i) { double t (double)i / POINTS * 2 * PI; // t 从 0 到 2π // 爱心参数方程 double x 16 * pow(sin(t), 3); double y 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t); // 应用缩放和偏移将爱心放置到屏幕中心 pts[i].x (long)(centerX x * scale); pts[i].y (long)(centerY - y * scale); // 注意屏幕Y轴向下为正所以用减号翻转 } // 为了使多边形闭合将最后一个点指向第一个点polygon函数会自动闭合 pts[POINTS 1] pts[0]; // 设置填充颜色并绘制填充多边形 setfillcolor(color); fillpolygon(pts, POINTS 2); }关键细节解析POINT是Windows API中定义的结构体包含x和y成员。EasyX兼容它。pow(sin(t), 3)即sin³(t)。使用pow函数计算立方。pts[i].y centerY - y * scale;这里的-号至关重要。因为数学坐标系Y轴向上为正而屏幕坐标系Y轴向下为正。这个减法操作完成了一次Y轴翻转让爱心在屏幕上正常显示。fillpolygon函数用于填充一个多边形区域。我们提供了顶点数组和顶点数量。现在在main函数的TODO处调用DrawHeart(400, 300, 10.0, RED);运行后你就能在屏幕中央看到一个红色的实心爱心了。3.3 引入时间与动画循环静态爱心完成了接下来让它动起来。我们需要引入时间概念并创建一个持续运行的循环游戏循环。int main() { initgraph(800, 600); setbkcolor(WHITE); // 设置背景色为白色 cleardevice(); // 用背景色清空屏幕 // 动画控制变量 float scale 10.0f; // 初始大小 float scaleSpeed 0.05f; // 缩放速度 float scaleRange 2.0f; // 缩放幅度 float time 0.0f; // 时间变量 // 开启批量绘图避免闪烁 BeginBatchDraw(); while (!_kbhit()) { // 当没有按键按下时循环 // 1. 清空上一帧画面 cleardevice(); // 2. 更新状态基于时间计算当前缩放比例 // 使用sin函数产生[-1, 1]的周期性变化映射到[scale - range, scale range] float currentScale scale sin(time) * scaleRange; time scaleSpeed; // 更新时间 // 3. 渲染绘制当前帧的爱心 DrawHeart(400, 300, currentScale, RED); // 4. 刷新显示完成一帧 FlushBatchDraw(); // 5. 控制帧率休眠一小段时间单位毫秒 Sleep(10); // 大约100 FPS } EndBatchDraw(); closegraph(); return 0; }动画核心原理与避坑指南BeginBatchDraw()和FlushBatchDraw()这是EasyX中实现双缓冲的关键。所有绘图指令在FlushBatchDraw调用前都只在内存中进行调用时才一次性更新到屏幕。这能有效消除动画闪烁。务必成对使用。sin(time)这是产生平滑周期性变化的核心。sin函数的输出在[-1, 1]之间平滑振荡乘以scaleRange后得到缩放的变化量加上基础大小scale就得到了脉动的效果。Sleep(10)用于控制帧率。每帧休眠10毫秒理论帧率约为100 FPS。帧率太高会浪费CPU太低则动画卡顿。常见的坑是忘记调用Sleep导致单核CPU占用率飙升至100%。while (!_kbhit())这是最简单的退出条件按任意键退出循环。在实际项目中你可能需要更复杂的事件处理。运行现在的程序你应该能看到一颗在屏幕上规律跳动缩放的红色爱心了。基础的动态效果已经实现。4. 进阶实现粒子系统爱心与高级视觉效果基础缩放动画虽然简单但略显单调。接下来我们实现一个更炫酷的粒子系统爱心。每个粒子都是一个独立的点拥有位置、速度、颜色、生命周期等属性通过模拟简单的物理规则如吸引力、随机扰动可以创造出非常生动的“跳动”、“发散”、“星光”效果。4.1 设计粒子数据结构与系统框架首先我们定义单个粒子的属性并管理一个粒子数组。#include vector // 使用动态数组管理粒子 #include cstdlib // 用于 rand() #include ctime // 用于 srand() struct Particle { double x, y; // 当前位置 double vx, vy; // 当前速度 double ax, ay; // 当前加速度可选用于更复杂模拟 COLORREF color; // 颜色 float life; // 生命周期从1.0到0.0 float maxLife; // 最大生命周期 // 目标位置爱心轮廓上的点 double targetX, targetY; }; class ParticleSystem { private: std::vectorParticle particles; int centerX, centerY; float heartScale; public: ParticleSystem(int cx, int cy, float scale, int count); void Update(float deltaTime); // 更新所有粒子状态 void Draw() const; // 绘制所有粒子 void ResetParticle(Particle p); // 重置或初始化一个粒子 };4.2 初始化粒子系统让粒子“长”在爱心上在构造函数中我们创建指定数量的粒子并将它们的初始位置和“目标位置”都设置在爱心轮廓上。ParticleSystem::ParticleSystem(int cx, int cy, float scale, int count) : centerX(cx), centerY(cy), heartScale(scale) { srand(static_castunsigned(time(nullptr))); // 初始化随机种子 particles.resize(count); const double PI 3.141592653589793; for (int i 0; i count; i) { Particle p particles[i]; // 为每个粒子分配爱心轮廓上的一个随机角度作为目标位置 double t (rand() / (double)RAND_MAX) * 2 * PI; p.targetX 16 * pow(sin(t), 3) * heartScale; p.targetY (13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)) * heartScale; // 初始位置就在目标位置附近添加一点随机偏移 p.x p.targetX (rand() / (double)RAND_MAX * 10 - 5); p.y p.targetY (rand() / (double)RAND_MAX * 10 - 5); // 初始速度为零 p.vx p.vy 0; // 随机颜色偏向红色系 int r 200 rand() % 55; // 200-255 int g 50 rand() % 100; // 50-150 int b 80 rand() % 100; // 80-180 p.color RGB(r, g, b); // 随机生命周期 p.maxLife 1.0f (rand() / (float)RAND_MAX) * 2.0f; // 1-3秒 p.life p.maxLife; // 也可以调用 ResetParticle(p) 来初始化 } }4.3 更新粒子状态模拟物理与生命周期这是粒子系统的核心。每一帧我们根据物理规则更新粒子的位置并减少其生命周期。void ParticleSystem::Update(float deltaTime) { for (auto p : particles) { // 1. 计算朝向目标位置的吸引力类似弹簧力 double dx p.targetX - p.x; double dy p.targetY - p.y; double distance sqrt(dx*dx dy*dy); // 吸引力系数距离越远力越大但避免除零 const double ATTRACTION_FORCE 0.5; double forceX dx / (distance 0.1) * ATTRACTION_FORCE; double forceY dy / (distance 0.1) * ATTRACTION_FORCE; // 2. 添加一些随机扰动让粒子看起来更“活泼” const double RANDOM_FORCE 0.05; forceX (rand() / (double)RAND_MAX - 0.5) * RANDOM_FORCE; forceY (rand() / (double)RAND_MAX - 0.5) * RANDOM_FORCE; // 3. 应用力到速度简单欧拉积分 p.vx p.vx * 0.95 forceX; // 乘以0.95模拟空气阻力 p.vy p.vy * 0.95 forceY; // 4. 应用速度到位置 p.x p.vx; p.y p.vy; // 5. 更新生命周期 p.life - deltaTime; // 6. 如果粒子生命周期结束重置它 if (p.life 0.0f) { ResetParticle(p); } } } void ParticleSystem::ResetParticle(Particle p) { // 重置到爱心轮廓上的一个新随机位置 double t (rand() / (double)RAND_MAX) * 2 * PI; p.targetX 16 * pow(sin(t), 3) * heartScale; p.targetY (13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)) * heartScale; p.x p.targetX (rand() / (double)RAND_MAX * 40 - 20); // 更大的初始随机偏移 p.y p.targetY (rand() / (double)RAND_MAX * 40 - 20); p.vx p.vy 0; p.life p.maxLife; // 使用之前随机的maxLife }物理模拟要点吸引力模型我们模拟了一个简单的“弹簧”力粒子受到一个指向其目标位置爱心轮廓上某点的力。力的大小与距离成正比方向指向目标。阻力模拟p.vx p.vx * 0.95 forceX;这一行在更新速度时将旧速度乘以一个小于1的系数如0.95这模拟了空气阻力或能量损耗防止粒子速度无限增大或过度振荡。欧拉积分新位置 旧位置 速度 * 时间新速度 旧速度 加速度 * 时间。这是最简单的数值积分方法对于本项目足够用。deltaTime是上一帧到这一帧的时间差用于保证动画速度与帧率无关。4.4 绘制粒子与主循环集成绘制粒子很简单遍历所有粒子根据其生命周期设置透明度可选然后画点。void ParticleSystem::Draw() const { for (const auto p : particles) { // 可以根据生命周期调整颜色亮度或透明度EasyX原生不支持透明度可通过混合模式或预乘alpha模拟这里简化处理 // 简单做法根据life比例调整颜色 float ratio p.life / p.maxLife; int r GetRValue(p.color) * ratio; int g GetGValue(p.color) * ratio; int b GetBValue(p.color) * ratio; setfillcolor(RGB(r, g, b)); // 画一个小圆点代表粒子 solidcircle((int)(centerX p.x), (int)(centerY - p.y), 2); // 注意Y轴翻转 } }现在修改主循环使用粒子系统int main() { initgraph(800, 600); setbkcolor(BLACK); // 黑色背景更能突出粒子效果 cleardevice(); // 创建粒子系统中心(400,300)缩放15粒子数800 ParticleSystem ps(400, 300,ÿc15.0f, 800); // 计时相关用于计算deltaTime clock_t lastTime clock(); float deltaTime 0.0f; BeginBatchDraw(); while (!_kbhit()) { cleardevice(); // 计算上一帧到这一帧的时间差秒 clock_t currentTime clock(); deltaTime (currentTime - lastTime) / (float)CLOCKS_PER_SEC; lastTime currentTime; // 防止deltaTime过大比如调试时断点 if (deltaTime 0.1f) deltaTime 0.1f; // 更新并绘制粒子系统 ps.Update(deltaTime); ps.Draw(); // 可以额外画一个轮廓爱心作为参考可选 setcolor(LIGHTGRAY); // 这里可以调用一个画空心爱心的函数... FlushBatchDraw(); Sleep(10); } EndBatchDraw(); closegraph(); return 0; }运行这个程序你会看到由数百个彩色粒子组成的一颗爱心粒子们会微微颤动生命周期结束后会“死亡”并“重生”在爱心轮廓的其他位置形成了持续流动、闪烁的生动效果。这远比简单的缩放动画要炫酷得多。5. 性能优化与跨平台迁移思考当粒子数量上升到数千时你可能会在EasyX中感到帧率下降。这是因为我们使用的是CPU进行逐个粒子的计算和绘制软件渲染。此外EasyX的solidcircle画实心圆函数在数量巨大时开销较大。5.1 EasyX环境下的优化技巧减少绘制调用solidcircle是相对耗时的。可以尝试用putpixel画单点或者对于非常小的粒子直接操作显存GetImageBuffer/SetImageBuffer但这会提高代码复杂度。优化粒子算法距离计算优化sqrt开方运算很慢。在计算吸引力时如果不需要精确的距离值可以比较距离的平方(dx*dx dy*dy)来节省开销。使用查表法sin,cos,pow在循环中被频繁调用。可以预先计算好一个周期内t对应的sin(t),cos(t)等值存储到数组中更新时直接查表用空间换时间。分帧更新如果粒子数量极大比如上万不必每帧更新所有粒子。可以将粒子分成若干组每帧只更新其中一组。虽然单个粒子的更新频率降低了但人眼不易察觉却能显著提升帧率。使用双缓冲我们已经用了BeginBatchDraw这是最重要的防闪烁和优化手段之一务必确保。5.2 迁移到SDL2拥抱硬件加速与跨平台如果你对性能有更高要求或者希望你的爱心动画能在Mac或Linux上运行迁移到SDL2是明智的选择。SDL2可以通过渲染器利用GPU进行2D加速渲染性能有数量级的提升。迁移的核心变化在于初始化从initgraph变为SDL_Init,SDL_CreateWindow,SDL_CreateRenderer。绘图从solidcircle变为SDL_RenderDrawPoint或SDL_RenderFillRect画小方块代替圆点。更高效的方式是使用**纹理Texture和精灵Sprite**批量渲染。主循环从while(!_kbhit())变为处理SDL_Event的事件循环。颜色从COLORREF和RGB()变为SDL_Color结构体。一个简化的SDL2粒子绘制循环片段如下// 初始化SDL略 SDL_Renderer* renderer ...; while (running) { // 处理事件略 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // 黑色背景 SDL_RenderClear(renderer); for (const auto p : particles) { SDL_SetRenderDrawColor(renderer, p.r, p.g, p.b, 255); // 画一个2x2的矩形代替圆点 SDL_Rect rect { (int)p.x, (int)p.y, 2, 2 }; SDL_RenderFillRect(renderer, rect); } SDL_RenderPresent(renderer); // 相当于FlushBatchDraw SDL_Delay(10); }SDL2的渲染器在启用硬件加速后即使绘制上万个矩形也能保持极高的帧率。此外SDL2还提供了更精确的计时器SDL_GetTicks64、纹理、混色等高级功能可以做出更炫的效果。6. 常见问题与调试心得在实际编码和调试过程中你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方法总结一下希望能帮你节省大量时间。6.1 图形窗口一闪而过或根本不显示问题程序编译运行后黑色窗口控制台可能一闪而过或者图形窗口根本没出现。原因与解决入口函数错误EasyX项目必须是main函数而不是WinMain。检查你的项目设置如果是Windows桌面应用程序要改成控制台应用程序。阻塞等待在main函数末尾必须有像_getch(),system(“pause”)或一个while循环来阻止程序立即结束。我们的动画主循环while(!_kbhit())本身就起到了这个作用。库链接问题确保你的项目正确链接了EasyX库。在VS中通常安装后会自动配置。6.2 动画闪烁严重问题爱心在跳动时画面有明显的闪烁感。原因这是没有使用双缓冲的典型症状。直接在屏幕上绘图用户会看到绘制过程。解决务必确保BeginBatchDraw()和FlushBatchDraw()成对出现并且将所有绘图操作放在它们之间。这是消除闪烁的关键。6.3 爱心形状扭曲或位置不对问题画出来的爱心像被压扁了或者位置不在屏幕中心。原因宽高比屏幕像素不是正方形时直接使用数学坐标绘图会导致拉伸。EasyX的setaspectratio函数可以调节纵横比但通常我们通过调整参数方程的系数来适配。Y轴方向忘记对Y坐标进行翻转centerY - y导致爱心上下颠倒。缩放因子scale参数太小或太大爱心可能看不见或超出屏幕。调试在绘制爱心轮廓线而不是填充时先关闭填充用setlinecolor和polyline画一下轮廓确认形状和位置正确后再进行填充。6.4 粒子系统运行越来越慢问题程序运行一段时间后帧率明显下降。原因内存泄漏如果你用new动态创建粒子数组却没有delete会导致内存泄漏。推荐使用std::vector它自动管理内存。计算复杂度爆炸检查你的更新函数中是否有嵌套循环的复杂度是 O(N²) 的。例如每个粒子都计算与其他所有粒子的相互作用引力/斥力。对于粒子系统通常应避免全连接计算。绘图效率如5.1节所述大量solidcircle调用是瓶颈。排查可以使用任务管理器观察程序的内存和CPU占用。如果CPU占用率持续很高且帧率低多半是计算或绘图瓶颈如果内存持续增长则是内存泄漏。6.5 如何添加交互如鼠标互动需求让爱心跟随鼠标移动或者鼠标点击时爱心产生“爆炸”效果。实现思路获取输入在while循环中使用GetMouseMsg()函数EasyX获取鼠标消息。影响系统状态跟随鼠标将粒子系统的centerX,centerY更新为鼠标坐标。为了让过渡平滑可以使用线性插值LerpcenterX (mouseX - centerX) * 0.1f;。点击爆炸当检测到鼠标点击时遍历所有粒子给它们一个从点击位置向外发散的初始速度vx, vy。这需要修改ResetParticle或增加一个Explode函数。6.6 让颜色动起来HSV色彩空间的应用我们之前的粒子颜色是随机生成的静态色。要实现平滑的彩虹色渐变RGB空间很不方便。HSV色相、饱和度、明度色彩空间更适合。原理让色相Hue值随时间线性增加然后转换为RGB进行绘制。实现你需要一个HSVtoRGB转换函数。网上有标准算法。然后在更新粒子时hue fmod(hue deltaTime * speed, 360.0f);再转换成RGB赋值给粒子颜色。这样所有粒子就会同步进行彩虹色循环视觉效果非常棒。从一颗静态的爱心到规律跳动再到拥有生命般的粒子系统最后考虑性能优化和跨平台这个项目几乎触及了一个小型图形动画demo的所有核心知识点。它不仅仅是浪漫的代码更是学习C/C图形编程、数学应用和实时系统设计的绝佳练手场。当你最终看到自己创造的爱心在屏幕上流畅舞动时那种将严谨逻辑转化为生动视觉的成就感是任何教科书都无法给予的。动手去试去调参去加入你自己的创意这才是编程最大的乐趣所在。