jjzjj

樱花树盛开的季节,我用简单的C代码绘制了一棵樱花树向她表白~『C/C++&图形库EasyX』

花想云(西安第一深情) 2023-08-24 原文

文章目录

💐专栏导读

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

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

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

💐文章导读

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

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

绘制一根线条

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

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;
}

效果图

绘制一个简易的树干

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

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;
}

效果图

优化树干,使其更加细致

  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) // 枝干绘图最细的线宽为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;
}

效果图

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

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

  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)) // 防止除以零的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,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

有关樱花树盛开的季节,我用简单的C代码绘制了一棵樱花树向她表白~『C/C++&图形库EasyX』的更多相关文章

  1. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  4. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  5. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  6. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  7. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  9. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  10. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

随机推荐