文章目录

  • 专栏导读
  • 文章导读
  • 绘制一根线条
  • 绘制一个简易的树干
  • 优化树干,使其更加细致
  • 绘制樱花树
  • 增加随机树形与渐变色效果
    • 如何设置随机数
  • 进阶——通过鼠标点击来控制生成樱花树
  • 进阶——生成樱花树并展示生长过程

专栏导读

作者简介:花想云,在读本科生一枚,致力于 C/C++、Linux 学习。

本文收录于 初学C语言必会的20个小游戏专栏,本专栏主要内容为利用C/C++与图形库EasyX实现各种有趣的小游戏。

相关专栏推荐:C语言初阶系列C语言进阶系列数据结构与算法

文章导读

本文主要内容为,利用图形库与简单的C语言知识实现樱花树。文章涉及的C语言语法并不多,但要求了解简单的递归运用。每一小节都会附有完整代码与实现效果图,有需求的小伙伴也可以直接去复制使用~

特别声明——本文内容与代码全部参考书籍《C和C++趣味游戏编程》,当然我也非常推荐这本书。

绘制一根线条

  1. 初始化画板窗口;
  2. 设置画板背景颜色(白色);
  3. 绘制一个线条;
  4. 设置线条颜色(黑色);

2-1

#include#include#include#include#define WIDTH 800 // 画面宽度#define HEIGHT 600 // 画面高度int main(){initgraph(WIDTH, HEIGHT); // 初始化窗口setbkcolor(RGB(225, 225, 225)); //白色背景setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色setlinestyle(PS_SOLID, 3);// 设定线宽cleardevice(); // 清屏BeginBatchDraw(); // 开始批量绘制line(WIDTH / 2, HEIGHT, WIDTH / 2,150); //绘制线条FlushBatchDraw(); // 刷新画板_getch(); // 等待输入closegraph(); // 关闭画板return 0;}

效果图

绘制一个简易的树干

  1. 使用函数递归来完成树干的绘制;
  2. 利用三角函数来改变每根线条的倾斜度;

2-2

#include#include#include#include#define PI 3.1415926 // 圆周率#define WIDTH 800 // 画面宽度#define HEIGHT 600 // 画面高度// 绘制树干的函数void branch(float x_start, float y_start, float angle, int generation){// 利用三角函数求出当前树枝的终点坐标float x_end, y_end;x_end = x_start + 150 * cos(angle);y_end = y_start + 150 * sin(angle);line(x_start, y_start, x_end, y_end); // 画出当前枝干// 求出子枝干的代数int childGeneration = generation + 1;// 当子枝干的代数<=4,画出当前枝干,并递归调用产生子枝干if (childGeneration <= 4){// 产生左右的子枝干branch(x_end, y_end,angle+PI/6,childGeneration);branch(x_end, y_end, angle - PI / 6, childGeneration);}}int main(){initgraph(WIDTH, HEIGHT); // 初始化窗口setbkcolor(RGB(225, 225, 225)); // 白色背景setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色setlinestyle(PS_SOLID, 3);// 设定线宽cleardevice(); // 清屏BeginBatchDraw(); // 开始批量绘制branch(WIDTH/2,HEIGHT,-PI/2,1); // 递归绘图FlushBatchDraw();_getch();closegraph();return 0;}

效果图

优化树干,使其更加细致

  1. 使用比例来控制父枝干与子枝干的长度;
  2. 控制父枝干与子枝干的夹角变化;

2-3

#define PI 3.1415926 // 圆周率#define WIDTH 800 // 画面宽度#define HEIGHT 600 // 画面高度float offsetAngle = PI / 6; // 左右枝干与父枝干偏离的角度float shortenRate = 0.65; // 左右枝干长度与父枝干长度的比例void branch(float x_start, float y_start, float length,float angle,float thickness, int generation){// 利用三角函数求出当前树枝的终点坐标float x_end, y_end;x_end = x_start + length * cos(angle);y_end = y_start + length * sin(angle);setlinestyle(PS_SOLID, thickness); //设置当前枝干的宽setlinecolor(RGB(0, 0, 0)); // 设置当前枝干的颜色line(x_start, y_start, x_end, y_end); // 画出当前枝干// 求出子枝干的代数int childGeneration = generation + 1;float childLength = shortenRate * length; // 生成的枝干的长度逐渐变短// 当子枝干的长度>=2,且当子枝干的代数<10,画出当前枝干,并递归调用产生子枝干if (childLength >= 2 && childGeneration <10){// 生成子枝干的宽度逐渐变细float childThickness = thickness * 0.8;if (childThickness < 2){childThickness = 2;}// 产生左右的子枝干branch(x_end, y_end, childLength, angle + offsetAngle, childThickness, childGeneration);branch(x_end, y_end, childLength, angle - offsetAngle, childThickness, childGeneration);}}int main(){initgraph(WIDTH, HEIGHT); // 初始化窗口setbkcolor(RGB(225, 225, 225)); //白色背景setlinecolor(RGB(0, 0, 0)); //设定线条颜色为黑色setlinestyle(PS_SOLID, 3);//设定线宽cleardevice(); // 清屏BeginBatchDraw(); // 开始批量绘制branch(WIDTH/2,HEIGHT,0.45*HEIGHT*shortenRate,-PI/2, 15 * shortenRate, 1); // 递归绘图FlushBatchDraw();_getch();closegraph();return 0;}

绘制樱花树

  1. 修改树干颜色为褐色;
  2. 为末端的树干添上樱花(实际为粉色的小圆);

2-4

#define PI 3.1415926 // 圆周率#define WIDTH 800 // 画面宽度#define HEIGHT 600 // 画面高度float offsetAngle = PI / 6; // 左右枝干与父枝干偏离的角度float shortenRate = 0.65; // 左右枝干长度与父枝干长度的比例// 枝干生成和绘制递归函数// 输入参数:枝干起始x y坐标,枝干长度,枝干角度,枝干绘图线条宽度,第几代void branch(float x_start, float y_start, float length, float angle, float thickness, int generation){// 利用三角函数求出当前枝干的终点x,y坐标float x_end, y_end;x_end = x_start + length * cos(angle);y_end = y_start + length * sin(angle);setlinestyle(PS_SOLID, thickness); // 设定当前枝干线宽setlinecolor(HSVtoRGB(15, 0.75, 0.5)); // 设定当前枝干颜色为褐色line(x_start, y_start, x_end, y_end); // 画出当前枝干// 求出子枝干的代数int childGeneration = generation + 1;// 生成子枝干的长度,逐渐变短float childLength = shortenRate * length;// 当子枝干长度大于等于2,并且代数小于等于10,递归调用产生子枝干if (childLength >= 2 && childGeneration <= 9){// 生成子枝干的粗细,逐渐变细float childThickness = thickness * 0.8;if (childThickness < 2) // 枝干绘图最细的线宽为2childThickness = 2;// 产生左右的子枝干branch(x_end, y_end, childLength, angle + offsetAngle, childThickness, childGeneration);branch(x_end, y_end, childLength, angle - offsetAngle, childThickness, childGeneration);// 产生中间的子枝干branch(x_end, y_end, childLength, angle, childThickness, childGeneration);}else// 画出最末端的樱花{setlinestyle(PS_SOLID, 1); // 线宽setlinecolor(HSVtoRGB(325, 0.3, 1)); // 设定线条颜色 粉色setfillcolor(HSVtoRGB(325, 0.3, 1)); // 设定填充颜色 粉色if (childLength <= 4) // 如果子枝干长度小于等于4fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)elsefillcircle(x_end, y_end, childLength / 2); // 画一个圆,半径为子枝干长度的一半}}int main(){initgraph(WIDTH, HEIGHT); // 初始化窗口setbkcolor(RGB(225, 225, 225)); //白色背景setlinecolor(RGB(0, 0, 0)); // 设定线条颜色为黑色setlinestyle(PS_SOLID, 3);// 设定线宽cleardevice(); // 清屏BeginBatchDraw(); // 开始批量绘制branch(WIDTH/2,HEIGHT,0.45*HEIGHT*shortenRate,-PI/2, 15 * shortenRate, 1); // 递归绘图FlushBatchDraw();_getch();closegraph();return 0;}

效果图

增加随机树形与渐变色效果

将樱花树的各个参数修改为随机数,生成各种形态不同的樱花树;

  1. 控制树干的颜色渐变(越往上枝干越亮);
  2. 设置花瓣的随机颜色;
  3. 控制树干的长度与比例具有随机性;

如何设置随机数

  • 使用rand函数来生成随机数;
  • 定义mapvalue函数来将生成的随机数映射到某一区间;
float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax){float output;if (abs((float)(input - inputMin) < 0.000001)) // 防止除以零的bugoutput = outputMin;elseoutput = (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;return output;}// 生成[min,max]之间的随机小数float randBetween(float min, float max){float t = rand() / double(RAND_MAX); // 生成[0,1]的随机小数// 调用mapValue函数,把值范围从[0,1]映射到[min,max]float r = mapValue(t, 0, 1, min, max);return r;}

2-5

#include#include#include#include#include #definePI 3.1415926#defineWIDTH 800 // 画面宽度#defineHEIGHT 600// 画面高度度float offsetAngle = PI / 6; // 左右枝干和父枝干偏离的角度float shortenRate = 0.65;// 子枝干比父枝干变短的倍数// 把[inputMin,inputMax]范围的input变量,映射为[outputMin,outputMax]范围的output变量float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax){float output;if (abs((float)(input - inputMin) < 0.000001)) // 防止除以零的bugoutput = outputMin;elseoutput = (input - inputMin) * (outputMax - outputMin) / (inputMax - inputMin) + outputMin;return output;}// 生成[min,max]之间的随机小数float randBetween(float min, float max){float t = rand() / double(RAND_MAX); // 生成[0,1]的随机小数// 调用mapValue函数,把值范围从[0,1]映射到[min,max]float r = mapValue(t, 0, 1, min, max);return r;}// 枝干生成和绘制递归函数// 输入参数:枝干起始x y坐标,枝干长度,枝干角度,枝干绘图线条宽度,第几代void branch(float x_start, float y_start, float length, float angle, float thickness, int generation){// 利用三角函数求出当前枝干的终点x,y坐标float x_end, y_end;x_end = x_start + length * cos(angle);y_end = y_start + length * sin(angle);// 画线条枝干setlinestyle(PS_SOLID, thickness); // 设定当前枝干线宽// 设置枝干为灰褐色,主树干最黑,子枝干逐渐变亮COLORREFcolor = HSVtoRGB(15, 0.75, 0.4 + generation * 0.05);setlinecolor(color); // 设定当前枝干颜色line(x_start, y_start, x_end, y_end); // 画出当前枝干(画线)// 求出子枝干的代数int childGeneration = generation + 1;// 生成左、右、中间三个子枝干的长度,逐渐变短,并有一定随机性float childLength = shortenRate * length;float leftChildLength = childLength * randBetween(0.9, 1.1);float rightChildLength = childLength * randBetween(0.9, 1.1);float centerChildLength = childLength * randBetween(0.8, 1.1);// 当子枝干长度大于2,并且代数小于等于10,递归调用产生子枝干if (childLength >= 2 && childGeneration <= 9){// 生成子枝干的粗细,逐渐变细float childThickness = thickness * 0.8;if (childThickness < 2) // 枝干绘图最细的线宽为2childThickness = 2;// 一定概率产生左、右、中子枝干if (randBetween(0, 1) < 0.95)branch(x_end, y_end, leftChildLength, angle + offsetAngle * randBetween(0.5, 1), childThickness, childGeneration);if (randBetween(0, 1) < 0.95)branch(x_end, y_end, rightChildLength, angle - offsetAngle * randBetween(0.5, 1), childThickness, childGeneration);if (randBetween(0, 1) < 0.85)branch(x_end, y_end, centerChildLength, angle + offsetAngle / 5 * randBetween(-1, 1), childThickness, childGeneration);}else // 最末端绘制樱花,画一个粉色填充圆{setlinestyle(PS_SOLID, 1); // 线宽// 樱花粉色HSVtoRGB(325,0.3,1),有一定随机性COLORREFcolor = HSVtoRGB(randBetween(300, 350), randBetween(0.2, 0.3), 1);setlinecolor(color); // 设定线条颜色setfillcolor(color); // 设定填充颜色if (childLength <= 4) // 如果子枝干长度小于等于4fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)elsefillcircle(x_end, y_end, childLength / 2); // 画一个圆,半径为子枝干长度的一半}}void startup()// 初始化{srand(time(0)); // 随机初始化initgraph(WIDTH, HEIGHT); // 新开一个画面setbkcolor(RGB(255, 255, 255)); // 白色背景cleardevice(); // 清屏BeginBatchDraw(); // 开始批量绘制brunch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归函数调用FlushBatchDraw(); // 批量绘制}void update()// 每帧更新{offsetAngle = randBetween(PI/10,PI/6);shortenRate = randBetween(0.7, 0.3);cleardevice(); // 清屏branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归调用}int main() // 主函数{startup();// 初始化 while (1)// 重复循环update();// 每帧更新return 0;}

效果图


进阶——通过鼠标点击来控制生成樱花树

2-5版本缺点在于每次运行程序只能生成一棵樱花树。我们还可以引进鼠标点击的功能来实现每次鼠标点击生成不同的樱花树。

  • 检测鼠标是否发生移动,从而更新递归函数的参数;

2-6

if (m.uMsg == WM_MOUSEMOVE) // 当鼠标移动时,设定递归函数的参数{// 鼠标从左到右,左右子枝干偏离父枝干的角度逐渐变大offsetAngle = mapValue(m.x, 0, WIDTH, PI / 10, PI / 4);// 鼠标从上到下,子枝干比父枝干的长度缩短的更快shortenRate = mapValue(m.y, 0, HEIGHT, 0.7, 0.3);}
  • 检测鼠标是否发生点击动作,若点击则开始绘制;
f (m.uMsg == WM_LBUTTONDOWN) // 当鼠标左键点击时,以当前参数开始绘制一棵新数{cleardevice(); // 清屏branch(WIDTH / 2, HEIGHT, 0.45 * HEIGHT * shortenRate, -PI / 2, 15 * shortenRate, 1); // 递归调用FlushBatchDraw(); // 批量绘制}

效果图


进阶——生成樱花树并展示生长过程

  • 使用sleep函数与FlushBatchDraw将每次绘制的结果间隔一秒刷新,形成樱花树生长的动画。

2-7

void branch(float x_start, float y_start, float length, float angle, float thickness, int generation){//....if (isShowAnimation) // 如果为1,绘制樱花树生成的过程动画{FlushBatchDraw(); // 批量绘制Sleep(1); // 暂停}}

效果图


快去向你喜欢的人展示樱花树叭~

点击下方个人名片,可添加博主的个人QQ,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓