需求分析
最近这个爱心桥段火了!一个帅气高冷的学霸小哥,写了一个爱心代码,直接碾压了所有同学,并获得班花的迷之爱恋!这样的故事,不论真假,对于我们程序员而言,想想就觉得美滋滋...
是不是满满的青春感?荷尔蒙和胶原蛋白在代码中肆意飞溅!我们现在来复刻一下这个爱心撩妹代码吧!
代码效果
多说无益,直接上效果:
原理分析
看到不停奔涌而出的爱心,有经验的小伙伴,可能马上就会想到“粒子喷射器”,这个和粒子喷射器,确实有相似之处。不过游戏开发中的粒子效果,一般都使用已有的粒子模型,然后去修改参数和粒子素材。这里的话,我们就只有手动实现整个过程了,但是基础原理是相同的,产生新粒子(爱心),修改粒子的属性,粒子的渲染,粒子的消亡。
爱心轨迹分析
不要被小心心干扰我们的视线哦,很多人说,开发中架构大于代码细节。不论这话对不对,直觉告诉我,先分析出爱心的整体轨迹,然后再去实现其中的万点繁星。
怎样实现这个爱心轨迹呢?374年前,也就是公元1648年,52岁的大数学家笛卡尔,遇见了自己的白月光-瑞典公主克里斯汀,因为阶级差距不敢表白。其实也就是没有钱。没有钱,真的是万万不能的啊。通过各种机缘和努力,笛卡尔成为了公主的家庭数学教师,和白月光朝夕相处。数学家的智慧让公主无比崇拜,很快他们就相爱了。说实话,整日面对这样的公主,也许只有我们的韦神能够控制自己吧。
幸福和甜蜜总是那么短暂,瑞典国王很快发现他们的恋情。国王很生气,他只是请笛卡尔做家庭教师,万万没想到...国王处死了笛卡尔。笛卡尔在临刑前,写了最后一封情书。公主在情书中,发现了一个公式。公主毕竟没有白学,通过公式,绘制了一个图案,这就是传说中的“笛卡尔心形曲线”:
好了,故事说完了,该写代码了。问题是我们很多人数学并不好,很多程序员老鸟的数学也不好,如果因为这个东西,就要去重学数学,那这个撩妹成本也太了。所以直接“借鉴”方便的公式使用。笛卡尔曲线公式演变出很多公式,我选择了这个(注意感觉这个公式绘制的心形比较苗条,有些公式绘制的效果像个大馒头,影响B格和效果)
这是一个极坐标公式。我们来复习一下极坐标的基本概念:
在极坐标中,任意一点的位置,由r和θ决定,r表示这个点到“极点”O之间的距离,θ表示如上图所示的夹角。根据这个公式,我们可以直接写出如下代码:.
// angle表示角度θ,r表示ρ
double r = sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142) - 2 * sin(angle) + 2;
但是我们在使用代码进行绘制的时候,使用的都是直角坐标系,所以还需要把这个极坐标,转换成直角坐标系。直接查百度百科,得知:
现在我们就的到了完整的曲线公式:
r = sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142) - 2 * sin(angle) + 2;
x = r * cos(angle);
y = r * sin(angle);
轨迹测试
#include <stdio.h>
#include <graphics.h>
#include <math.h>
int main(void) {
initgraph(1920, 900);
int R = 100;
double angle = 0;
for (angle = 0; angle <= 3.14 * 2; angle += 0.1) {
double r = (sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142)
- 2 * sin(angle) + 2);
int x = R * r * cos(angle);
int y = R * r * sin(angle);
char str[8];
setbkmode(TRANSPARENT);
sprintf_s(str, sizeof(str), "%.1f", angle);
outtextxy(x, y, str);
}
system("pause");
return 0;
}
执行效果:
心形不完整,加偏移量:
#include <stdio.h>
#include <graphics.h>
#include <math.h>
int main(void) {
initgraph(1920, 900);
int R = 100;
double angle = 0;
for (angle = 0; angle <= 3.14 * 2; angle += 0.1) {
double r = (sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142)
- 2 * sin(angle) + 2);
int x = R * r * cos(angle) + 400;
int y = R * r * sin(angle) + 400;
char str[8];
setbkmode(TRANSPARENT);
sprintf_s(str, sizeof(str), "%.1f", angle);
outtextxy(x, y, str);
}
system("pause");
return 0;
}
效果发现这个心形式反着的,我们在来一个“垂直镜像”(把y坐标加一个负号反向即可):
#include <stdio.h>
#include <graphics.h>
#include <math.h>
int main(void) {
initgraph(1920, 900);
int R = 100;
double angle = 0;
for (angle = 0; angle <= 3.14 * 2; angle += 0.1) {
double r = (sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142)
- 2 * sin(angle) + 2);
int x = R * r * cos(angle) + 400;
int y = -R * r * sin(angle) + 400;
char str[8];
setbkmode(TRANSPARENT);
sprintf_s(str, sizeof(str), "%.1f", angle);
outtextxy(x, y, str);
}
system("pause");
return 0;
}
最后调整一下偏移量,完美!
心形实现
到现在,我们已经把心形轨迹模拟出来了,但是每个轨迹点的“爱心”还没有实现。其实最简单的办法就是直接使用一个心形小图片,但是这样子不方便表白活动的展开,总不能给学妹分享你的B格代码时,还要额外附送一个心形图片,这样子就没有神秘感,B格维度直接降低到零点。
其实也很简单,只要把字体调整一下就可以了,把字体设置为“Webdings”,再直接输出字符Y即可得到一个心形,但是代码中可以不要直接输出Y, 可以在代码中输出Y的ASCII码值89, 这样就更神秘了:-)
再做一个测试:
#include <stdio.h>
#include <graphics.h>
#include <math.h>
int main(void) {
initgraph(600, 500);
int R = 100;
double angle = 0;
for (angle = 0; angle <= 3.14 * 2; angle += 0.1) {
double r = (sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142)
- 2 * sin(angle) + 2);
int x = R * r * cos(angle) + 260;
int y = -R * r * sin(angle) + 80; //垂直翻转
char str[8];
setbkmode(TRANSPARENT);
settextcolor(RED);
settextstyle(40, 0, "Webdings");
setbkmode(TRANSPARENT);
outtextxy(x, y, 89); //89 就是 'Y'
}
system("pause");
return 0;
}
动态心形分析
让心形不断的奔涌而出,怎么实现呢?直接使用粒子喷射器的原理来做,假设每次喷射20个小心心,然后每隔一定时间(比如30毫秒),再次喷射20个小心心。同时在每次更新的时候,把每个小信息的size调大一个像素,每个小心心经历20轮之后,就消亡(消失)。
代码实现:
#include <stdio.h>
#include<graphics.h>
#include<time.h>
#include<math.h>
struct love {
int height;
double angle;
double r; //极坐标的r(0..1)
double curR;
int x;
int y;
};
struct love mylove[400];
int offX = 260;
int offY = 180;
double R = 60;
void init() {
initgraph(600, 480);
memset(mylove, 0, sizeof(mylove));
srand(time(NULL));
}
void getRandAngles(int* buf, int count) {
int rangle = 314 * 2 * 2;
for (int i = 0; i < count; i++) {
int randTmp = 0;
while (1) {
randTmp = rand() % rangle;
int j;
for (j = 0; j < i && buf[j] != randTmp; j++);
if (j >= i) break;
}
buf[i] = randTmp;
}
}
void xiugaiData() {
for (int i = 0; i < 400; i++) {
if (mylove[i].curR == 0) continue;
mylove[i].curR++;
if (mylove[i].curR > 80) {
memset(&mylove[i], 0, sizeof(struct love));
}
mylove[i].height++;
mylove[i].x = (int)(-mylove[i].curR * mylove[i].r * cos(mylove[i].angle) + offX);
mylove[i].y = -mylove[i].curR * mylove[i].r * sin(mylove[i].angle) + offY - mylove[i].curR;
}
}
void addNewHeart() {
int buf[20];
getRandAngles(buf, 20);
int k;
for (k = 0; k < 400 && mylove[k].curR > 0; k++);
for (int i = k; i < k+20; i++) {
double angle = mylove[i].angle = buf[i-k] * 0.01;
mylove[i].r = sin(angle) * sqrt(fabs(cos(angle))) / (sin(angle) + 1.4142) - 2 * sin(angle) + 2;
mylove[i].curR = R;
mylove[i].height = 0;
mylove[i].x = mylove[i].curR * mylove[i].r * cos(mylove[i].angle) + offX;
mylove[i].y = -mylove[i].curR * mylove[i].r * sin(mylove[i].angle) + offY - mylove[i].curR;
}
}
void updateWindow()
{
BeginBatchDraw();
cleardevice();
settextcolor(RED);
for (int i = 0; i < 400; i++) {
if (mylove[i].curR == 0)continue;
settextstyle(mylove[i].height + 10, 0, "Webdings");
setbkmode(TRANSPARENT);
outtextxy(mylove[i].x + 20, mylove[i].y + 20, 'Y'); //89
}
EndBatchDraw();
}
void mySleep(int delay) {
unsigned long long start = GetTickCount();
if (GetTickCount() < start + delay) Sleep(1);
}
int main() {
init();
while (1) {
addNewHeart();
updateWindow();
xiugaiData();
mySleep(30);
}
return 0;
}
【公众号】:奇牛编程
【C语言】五小时快速入门C语言:【C语言】五小时快速入门C语言