使用C语言创建动态心形动画

CCBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在这个项目中,你将学习如何使用C编程语言创建一个迷人的动态心形动画。该项目利用X Window系统来渲染动画视觉效果。按照逐步说明,你将设置项目、生成数据,并创建一个引人入胜的动画,让动态的心形在屏幕上栩栩如生。

👀 预览

动态心形

🎯 任务

在这个项目中,你将学习:

  • 如何设置一个C编程项目来创建动态心形动画
  • 如何使用X Window系统库来创建和管理图形窗口
  • 如何生成随机点并将它们动画化为心形
  • 如何控制动画进行扩展和收缩,创造出引人入胜的视觉效果

🏆 成果

完成这个项目后,你将能够:

  • 在C语言中使用X Window系统库进行图形编程
  • 在C语言中生成和操作随机点
  • 结合数据生成和渲染技术创建动态动画

创建项目文件

确保你已安装所需的库。你需要X11开发库。你可以使用以下命令安装它们:

sudo apt update
sudo apt-get install libx11-dev

然后,创建一个名为dynamic_heart.c的新文件,并在你喜欢的代码编辑器中打开它。

cd ~/project
touch dynamic_heart.c
✨ 查看解决方案并练习

定义必要的变量

首先,我们需要编写C代码。第一步是包含头文件:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>

定义用于存储点信息的结构体,包括其x坐标、y坐标和颜色。用于在图形中表示点。

struct Point {
    double x, y;
    unsigned long color;
};

然后,定义一些全局变量:

unsigned long colors[7] = {0xff1f53, 0xfcdefa, 0xff0000, 0xff0000, 0xff0202, 0xff0008, 0xff0505};
const int xScreen = 1200;
const int yScreen = 800;
const double PI = 3.1426535159;
const double e = 2.71828;
const double average_distance = 0.162;
const int quantity = 506;
const int circles = 210;
const int frames = 20;
struct Point* origin_points;
struct Point* points;

使用与X Window系统相关的变量来创建和管理图形窗口及图形上下文。

Display *display;
Window win;
GC gc;

display表示X服务器连接,win表示窗口,gc表示图形上下文,用于绘制图形元素。

✨ 查看解决方案并练习

实现屏幕坐标函数

double screen_x(double x) {
    x += xScreen / 2;
    return x;
}

double screen_y(double y) {
    y = -y + yScreen / 2;
    return y;
}

screen_xscreen_y函数的目的是将实际坐标映射到屏幕坐标,以确保动画中的图形元素在屏幕的可见区域内,并显示在正确的位置。具体来说:

  1. screen_x
  • 此函数将实际的x坐标值作为参数,然后对其进行调整,以便在屏幕上正确绘制。它将输入的x坐标值加上xScreen的一半,这样它将位于屏幕的水平中心,因为xScreen表示屏幕的宽度,除以2可找到屏幕的水平中心。
  • 该函数返回调整后的x坐标值,用于在屏幕上绘制点或形状。
  1. screen_y
  • 此函数与screen_x类似,但处理y坐标。它将实际的y坐标值作为参数,并将其转换为屏幕坐标系。首先,它反转y坐标,使坐标系的原点位于屏幕的左上角,然后将其加上yScreen的一半,以便坐标位于屏幕的垂直中心。
  • 该函数返回调整后的y坐标值,以便在屏幕上正确绘制点或形状。
✨ 查看解决方案并练习

实现随机数生成函数

int create_random(int x1, int x2) {
    if (x2 > x1) {
        return rand() % (x2 - x1 + 1) + x1;
    }
    return 0;
}

这是一个用于在指定范围内生成随机整数的函数,它用于使生成点的颜色和位置随机化,以增加动画的视觉多样性。

其中x1x2是作为参数传递的两个整数。该函数返回x1x2之间的一个随机整数,包括x1x2。如果x2大于x1,则使用rand()函数生成一个随机整数,并通过取模运算将其限制在有效范围内。rand()函数通常返回一个介于0RAND_MAX(通常为32767)之间的随机整数。最后,将取模运算的结果加到x1上,以确保随机整数落在指定范围内。

✨ 查看解决方案并练习

初始化并生成心形点集:

create_data函数为动画生成数据。它根据定义的算法计算心形的点并对其进行动画处理。

void create_data() {
    int index = 0;
    double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
    for (double radian = 0.1; radian <= 2 * PI; radian += 0.005) {
        // 计算心形的x和y坐标
        x2 = 16 * pow(sin(radian), 3);
        y2 = 13 * cos(radian) - 5 * cos(2 * radian) - 2 * cos(3 * radian) - cos(4 * radian);
        double distance = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
        if (distance > average_distance) {
            // 符合条件的心形点被存储
            x1 = x2;
            y1 = y2;
            origin_points[index].x = x2;
            origin_points[index++].y = y2;
        }
    }
  • 使用循环遍历一系列弧度值(从0.12 * PI),并计算每个点的x和y坐标。

  • 计算两点之间的距离,如果距离大于average_distance,则将该点存储在origin_points数组中。

✨ 查看解决方案并练习

为点上色以生成动画

// 在create_data()函数内部
index = 0;
for (double size = 0.1, lightness = 1.5; size <= 20; size += 0.1) {
    double success_p = 1 / (1 + pow(e, 8 - size / 2));
    if (lightness > 1) {
        lightness -= 0.0025;
    }

    for (int i = 0; i < quantity; ++i) {
        if (success_p > (double)create_random(0, 100) / 100.0) {
            // 随机生成点的颜色、坐标并存储到Points数组中
            points[index].color = colors[create_random(0, 6)];
            points[index].x = size * origin_points[i].x + create_random(-4, 4);
            points[index++].y = size * origin_points[i].y + create_random(-4, 4);
        }
    }
}
  • 有两个嵌套循环。外部循环逐渐增加size的值,内部循环处理每个quantity个点。

  • 根据success_p的概率,决定是否生成一个点,并将点的颜色和坐标存储到points数组中。

✨ 查看解决方案并练习

动画生成与更新

// 在create_data()函数内部
int points_size = index;

for (int frame = 0; frame < frames; ++frame) {
    for (index = 0; index < points_size; ++index) {
        // 计算出的点的位置增加并更新坐标
        double x = points[index].x, y = points[index].y;
        double distance = sqrt(pow(x, 2) + pow(y, 2));
        double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
        double x_increase = distance_increase * x / distance / frames;
        double y_increase = distance_increase * y / distance / frames;
        points[index].x += x_increase;
        points[index].y += y_increase;
        // 使用XSetForeground和XFillArc绘制点
        XSetForeground(display, gc, points[index].color);
        XFillArc(display, win, gc, screen_x(points[index].x), screen_y(points[index].y), 2, 2, 0, 360 * 64);
    }

    for (double size = 17; size < 23; size += 0.3) {
        for (index = 0; index < quantity; ++index) {
            // 根据条件随机生成点的坐标和颜色,并使用XSetForeground和XFillArc绘制点
            if ((create_random(0, 100) / 100.0 > 0.6 && size >= 20) || (size < 20 && (double)create_random(0, 100) / 100.0 > 0.95)) {
                double x, y;
                if (size >= 20) {
                    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                } else {
                    x = origin_points[index].x * size + create_random(-5, 5);
                    y = origin_points[index].y * size + create_random(-5, 5);
                }
                XSetForeground(display, gc, colors[create_random(0, 6)]);
                XFillArc(display, win, gc, screen_x(x), screen_y(y), 2, 2, 0, 360 * 64);
            }
        }
    }
}
  1. points_size用于获取当前动画帧中的点数,它是根据上一段代码计算得出的。index是之前生成的点数。
  2. 外部循环for (int frame = 0; frame < frames; ++frame)用于遍历动画的每一帧,frames指定了总帧数。
  3. 内部循环for (index = 0; index < points_size; ++index)用于处理当前帧中的每个点。在每一帧中,它执行以下操作:
    • 首先,计算每个点的新位置。这通过以下公式完成:
double x = points[index].x, y = points[index].y;
double distance = sqrt(pow(x, 2) + pow(y, 2));
double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
double x_increase = distance_increase * x / distance / frames;
double y_increase = distance_increase * y / distance / frames;
points[index].x += x_increase;
points[index].y += y_increase;

这些计算用于更新点的x和y坐标,以实现动画中点的运动。distance_increase控制着点移动的速度,它随点到其原始位置的距离而变化。- 使用XSetForegroundXFillArc函数绘制点。这将点绘制到屏幕上,XSetForeground用于设置绘图颜色,XFillArc用于绘制填充的点,并且圆的中心坐标通过screen_xscreen_y函数进行转换。4. 内部循环的第二部分for (double size = 17; size < 23; size += 0.3)用于在当前帧中生成额外的点。在这个循环中,每个点被生成、着色并绘制到屏幕上。- 新点的坐标和颜色根据以下条件随机生成:

如果size >= 20且随机数大于0.6,或者size < 20且随机数大于0.95,则生成一个新点。

- 生成点的x和y坐标是根据原始点的位置和一些随机偏移量计算得出的。
double x, y;
if (size >= 20) {
    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
} else {
    x = origin_points[index].x * size + create_random(-5, 5);
    y = origin_points[index].y * size + create_random(-5, 5);
}
- 最后,使用`XSetForeground`和`XFillArc`函数将生成的点绘制到屏幕上,就像之前的点一样。
✨ 查看解决方案并练习

创建X窗口并进行初始化

main函数的主要目标是创建一个X窗口,初始化数据,然后在一个无限循环中生成并绘制心形动画的每一帧。

int main() {
    display = XOpenDisplay(NULL);

    int blackColor = BlackPixel(display, DefaultScreen(display));
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, xScreen, yScreen, 0, blackColor, blackColor);
    XSelectInput(display, win, StructureNotifyMask);
    XMapWindow(display, win);
    gc = XCreateGC(display, win, 0, NULL);
}
  • display:打开与X服务器的连接。
  • blackColor:获取显示的黑色像素值。
  • win:创建一个窗口,并指定其属性、位置、大小和边框颜色。
  • XSelectInput:指定窗口的输入事件掩码。
  • XMapWindow:将窗口映射到屏幕上。
  • gc:创建一个图形上下文。
✨ 查看解决方案并练习

初始化数据并创建点组

// 在main()函数内部
while (1) {
    XEvent e;
    XNextEvent(display, &e);
    if (e.type == MapNotify)
        break;
}

XFlush(display);
srand(time(NULL));
origin_points = (struct Point*)malloc(quantity * sizeof(struct Point));
points = (struct Point*)malloc(circles * quantity * sizeof(struct Point));

create_data();
  1. while (1)是一个无限循环,用于等待X窗口映射完成,这是窗口在屏幕上显示之前的初始化阶段。
    • 定义一个XEvent结构体来接收X事件。
    • XNextEvent:等待并获取下一个X事件,并将其存储在e变量中。
    • 使用if检查事件类型是否为MapNotify,表示窗口已成功映射到屏幕。如果窗口映射完成(即事件类型为MapNotify)。
  2. XFlush:清空X服务器的输出缓冲区,以确保之前的绘图命令生效,不会延迟到后续的动画绘制。
  3. srand:初始化随机数生成器。使用当前时间作为随机数生成器的种子,以便在动画中产生随机效果。
  4. origin_points:分配内存并创建一个包含quantityPoint结构体的数组,用于存储原始点坐标。
  5. points:再次分配内存并创建一个更大的数组来存储动画中的点。circles控制动画中的点数,是一个常量值。
  6. 最后,调用create_data()函数来初始化数据,生成并设置原始点的坐标,以及初始化动画点的颜色和初始坐标。
✨ 查看解决方案并练习

动画主循环

此主循环的目的是通过更改frame的值来控制心形图案的状态,从而实现动画效果的扩展和收缩。usleep函数用于控制帧率,以使动画以一定速度播放。

// 在main()函数内部
bool extend = true, shrink = false;
for (int frame = 0;;) {
    usleep(20000);
    XClearWindow(display, win);
    if (extend)
        frame == 19? (shrink = true, extend = false) : ++frame;
    else
        frame == 0? (shrink = false, extend = true) : --frame;
}

首先,定义两个布尔变量extendshrink,并分别初始化为truefalse,分别表示心形图案的扩展和收缩状态。

然后,开始一个无限循环,循环中的frame变量用于跟踪当前动画帧的计数。

  • usleep用于控制动画速度。
  • 通过调用X11函数XClearWindow清除窗口上的所有内容,以便在下一帧中重新绘制。
  • 根据extendshrink的值增加或减少frames,以使动画能够扩展和收缩。使用条件运算符? :来设置布尔值。

    如果extendtrue,检查frame是否等于19。如果是,则动画即将从扩展状态切换到收缩状态,将shrink设置为true,将extend设置为false。否则,增加frame
    如果extendfalse,检查frame是否等于0。如果是,则动画即将从收缩状态切换到扩展状态,将shrink设置为false,将extend设置为true。否则,减少frame

最后,释放资源并退出。

// 在main()函数内部
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
✨ 查看解决方案并练习

编译并运行项目

  1. 打开终端并导航到项目目录。
cd ~/project
  1. 使用以下命令编译代码:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. 运行应用程序:
./dynamic_heart
动态心形图案

(注:图片链接中的英文部分保留原文,因为它是一个特定的文件名或路径标识,直接翻译可能会导致链接失效或无法准确指向对应的资源。)

✨ 查看解决方案并练习

总结

在这个项目中,你已经学会了如何使用C编程语言创建一个引人入胜的动态心形动画。你已经设置了项目,定义了变量,实现了屏幕坐标函数、随机数生成器函数和数据生成函数。该程序利用X窗口系统来渲染动画心形。最后,你创建了一个动画循环并关闭了显示,释放了分配的内存。现在,你可以欣赏自己用C语言创建的动态心形动画了。

您可能感兴趣的其他 C 教程