C语言如何 实现 下雪效果
在 2015 年底那会刚好下雪时候, 就想起大学一位对我非常好老师 C 语言老师. 想用她教的知识写一个下雪代码送给她.
当现在2024年底, 重新翻修下用于感谢老师的照顾帮助.
之前老版本很粗糙, 主要 window 和 linux 都能跑, 动画 采用 1s 40 帧, 雪花具有 x 轴速度和 y 轴速度.
如果在网上搜 C语言下雪相关的 很多抄袭我写的老版本, 这里不再提了, 这次写一个简单新玩具, 具备简单物理引擎, 例如重力, 风对雪花影响.
准备环境, 最好 Linux Ubuntu C2X
如果是 Window 推荐使用最新 Window 版本, 开启 WSL 安装 Ubuntu 子系统, 配置下 VSCode 远端开发环境也完全可以.
好的那我们开始吧.
安装 libncurses-dev
包
sudo apt update sudo apt install libncurses-dev
ncurses (new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。
这里下雪页面绘制就是基于这个 GNU GUI 库二次开发
#include <time.h> #include <stdlib.h> #include <unistd.h> #include <ncurses.h> // 每行最大雪花密度(自适应屏幕宽度的比例) #define MAX_SNOWFLAKE_DENSITY_PER_ROW 0.06 // 微秒延迟,控制帧率 #define FRAME_DELAY 160000 // 重力加速度 #define GRAVITY 0.04 // 风力强度 #define WIND 0.4 struct snowflake { // 雪花位置(浮点数支持平滑移动) float x, y; // 垂直速度 float velocity_y; // 水平速度 float velocity_x; // 雪花字符 char shape; // 是否激活(是否在屏幕中出现) bool active; }; struct snowfall { // 雪花数组 struct snowflake * snowflakes; // 最大雪花数量 int max_snowflake_count; // 第一行 top 行最大雪花数量 int max_snowflakes_top_row; // 屏幕宽度 int screen_width; // 屏幕高度 int screen_height; }; // 找到空闲没有激活的雪花对象, 生成一个新的雪花, 并放置在屏幕第一行处 void snowflake_spawn(struct snowfall * sl) { for (int i = 0; i < sl->max_snowflake_count; i++) { if (!sl->snowflakes[i].active) { // 随机水平位置 sl->snowflakes[i].x = rand() % sl->screen_width; // 从顶部出现 sl->snowflakes[i].y = 0; // 随机垂直速度 & 拍脑门微小随机量 sl->snowflakes[i].velocity_y = GRAVITY + (rand() % 50) / 100.0; // 随机水平风速 & 拍脑门微小随机量 sl->snowflakes[i].velocity_x = WIND * ((rand() % 200) / 100.0 - 1); // 随机选择雪花形状 static const char shapes[] = {'*', 'o', '*', '.', '*', '+', '*'}; sl->snowflakes[i].shape = shapes[rand() % (sizeof (shapes) / sizeof (*shapes))]; // 激活雪花 sl->snowflakes[i].active = true; // 一次只生成一个雪花 break; } } } // 更新雪花位置 void snowflakes_update(struct snowfall * sl) { int top_row_count = 0; for (int i = 0; i < sl->max_snowflake_count; i++) { if (sl->snowflakes[i].active) { // 更新垂直位置 sl->snowflakes[i].y += sl->snowflakes[i].velocity_y; // 更新水平位置 sl->snowflakes[i].x += sl->snowflakes[i].velocity_x; if ((int)sl->snowflakes[i].y == 0) { top_row_count++; } // 如果雪花超出屏幕范围,取消激活 if (sl->snowflakes[i].y >= sl->screen_height || sl->snowflakes[i].x < 0 || sl->snowflakes[i].x >= sl->screen_width) { sl->snowflakes[i].active = false; } } } // 确保第一行生成的雪花数量不超过限制, 但也不会太少 for (int i = top_row_count; i < sl->max_snowflakes_top_row; i++) { snowflake_spawn(sl); i += rand() % 2; } } // 绘制雪花 void snowflakes_draw(struct snowfall * sl) { // 清屏 clear(); for (int i = 0; i < sl->max_snowflake_count; i++) { if (sl->snowflakes[i].active) { // 绘制雪花 mvaddch((int)sl->snowflakes[i].y, (int)sl->snowflakes[i].x, sl->snowflakes[i].shape); } } // 刷新屏幕 refresh(); }
/*
* gcc -g -Wall -O2 -Wextra -o snow snow.c -lncurses & ./snow
*/
int main() { // 初始化随机数种子 srand((unsigned)time(NULL)); // 初始化 ncurses initscr(); // 隐藏光标 curs_set(0); // 非阻塞模式 nodelay(stdscr, true); // 禁用回显 noecho(); // 初始化屏幕尺寸和雪花数量 struct snowfall sl; getmaxyx(stdscr, sl.screen_height, sl.screen_width); // 计算每行最大雪花数量 sl.max_snowflakes_top_row = sl.screen_width * MAX_SNOWFLAKE_DENSITY_PER_ROW; // 计算总雪花数量 sl.max_snowflake_count = sl.max_snowflakes_top_row * sl.screen_height; // 分配雪花数组的内存 & 初始化内存 sl.snowflakes = calloc( sl.max_snowflake_count, sizeof(struct snowflake)); if (sl.snowflakes == NULL) { endwin(); fprintf(stderr, "Memory allocation failed\n"); return -1; } // 按 'q' 退出 while (getch() != 'q') { // 更新雪花位置 snowflakes_update(&sl); // 绘制雪花 snowflakes_draw(&sl); // 延迟模拟帧率 usleep(FRAME_DELAY); } // 释放雪花数组的内存 free(sl.snowflakes); // 恢复终端设置 endwin(); return 0; }
Build & Run
gcc -g -Wall -O2 -Wextra -o snow snow.c -lncurses ./snow
这个冬天,雪花很美,(。⌒∇⌒)