文章目录
- 💐专栏导读
- 💐文章导读
- 绘制一根线条
- 绘制一个简易的树干
- 优化树干,使其更加细致
- 绘制樱花树
- 增加随机树形与渐变色效果
- 如何设置随机数
- 进阶——通过鼠标点击来控制生成樱花树
- 进阶——生成樱花树并展示生长过程
💐专栏导读
🌸作者简介:花想云,在读本科生一枚,致力于 C/C++、Linux 学习。
🌸本文收录于 初学C语言必会的20个小游戏专栏,本专栏主要内容为利用C/C++与图形库EasyX实现各种有趣的小游戏。
🌸相关专栏推荐:C语言初阶系列 、C语言进阶系列 、数据结构与算法
💐文章导读
本文主要内容为,利用图形库
与简单的C语言
知识实现樱花树。文章涉及的C语言语法并不多,但要求了解简单的递归运用
。每一小节都会附有完整代码与实现效果图,有需求的小伙伴也可以直接去复制使用~
特别声明
——本文内容与代码全部参考书籍《C和C++趣味游戏编程》
,当然我也非常推荐这本书。
绘制一根线条
- 初始化画板窗口;
- 设置画板背景颜色(白色);
- 绘制一个线条;
- 设置线条颜色(黑色);
2-1
#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include<math.h>
#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;
}
效果图
绘制一个简易的树干
- 使用函数递归来完成树干的绘制;
- 利用三角函数来改变每根线条的倾斜度;
2-2
#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#include<math.h>
#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;
}
效果图
优化树干,使其更加细致
- 使用比例来控制父枝干与子枝干的长度;
- 控制父枝干与子枝干的夹角变化;
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;
}
绘制樱花树
- 修改树干颜色为褐色;
- 为末端的树干添上樱花(实际为粉色的小圆);
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) // 枝干绘图最细的线宽为2
childThickness = 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) // 如果子枝干长度小于等于4
fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)
else
fillcircle(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;
}
效果图
增加随机树形与渐变色效果
将樱花树的各个参数修改为随机数,生成各种形态不同的樱花树;
- 控制树干的颜色渐变(越往上枝干越亮);
- 设置花瓣的随机颜色;
- 控制树干的长度与比例具有随机性;
如何设置随机数
- 使用rand函数来生成随机数;
- 定义mapvalue函数来将生成的随机数映射到某一区间;
float mapValue(float input, float inputMin, float inputMax, float outputMin, float outputMax)
{
float output;
if (abs((float)(input - inputMin) < 0.000001)) // 防止除以零的bug
output = outputMin;
else
output = (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<stdio.h>
#include<graphics.h>
#include<conio.h>
#include<math.h>
#include <time.h>
#define PI 3.1415926
#define WIDTH 800 // 画面宽度
#define HEIGHT 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)) // 防止除以零的bug
output = outputMin;
else
output = (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); // 设定当前枝干线宽
// 设置枝干为灰褐色,主树干最黑,子枝干逐渐变亮
COLORREF color = 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) // 枝干绘图最细的线宽为2
childThickness = 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),有一定随机性
COLORREF color = HSVtoRGB(randBetween(300, 350), randBetween(0.2, 0.3), 1);
setlinecolor(color); // 设定线条颜色
setfillcolor(color); // 设定填充颜色
if (childLength <= 4) // 如果子枝干长度小于等于4
fillcircle(x_end, y_end, 2); // 圆的半径为2(再小就看不清了)
else
fillcircle(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,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓