PS1 : setfont ()函数是一个用于设置字体显示效果的方法,可以指定字体、风格和字号。
PS2 : BeginBatchDraw()函数 执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出,以实现批绘图功能,可以消除闪烁。
PS3 : outtextxy 这个函数用于在指定位置输出字符串。 void outtextxy( int x, int y, LPCTSTR str ); void outtextxy( int x, int y, TCHAR c ); 参数 x 字符串输出时头字母的 x 轴的坐标值。 y 字字符串输出时头字母的 y 轴的坐标值, str待输出的字符串的指针。
PS4: clearrectangle 这个函数用于清空矩形区域。 void clearrectangle( int left, int top, int right, int bottom ); 参数 left 矩形左部 x 坐标。 top 矩形顶部 y 坐标。 right 矩形右部 x 坐标。 bottom矩形底部 y 坐标。
PS5:srand函数是随机数发生器的初始化函数。原型:void srand(unsigned seed);
用法:它初始化随机种子,会提供一个种子,这个种子会对应一个随机数,如果使用相同的种子后面的rand()函数会出现一样的随机数,如: srand(1); 直接使用1来初始化种子。不过为了防止随机数每次重复,常常使用系统时间来初始化,即使用 time函数来获得系统时间,它的返回值为从 00:00:00 GMT, January 1, 1970 到现在所持续的秒数,然后将time_t型数据转化为(unsigned)型再传给srand函数,即: srand((unsigned) time(&t)); 还有一个经常用法,不需要定义time_t型t变量,即: srand((unsigned) time(NULL)); 直接传入一个空指针,因为你的程序中往往并不需要经过参数获得的数据。
进一步说明下:计算机并不能产生真正的随机数,而是已经编写好的一些无规则排列的数字存储在电脑里,把这些数字划分为若干相等的N份,并为每份加上一个编号用srand()函数获取这个编号,然后rand()就按顺序获取这些数字,当srand()的参数值固定的时候,rand()获得的数也是固定的,所以一般srand的参数用time(NULL),因为系统的时间一直在变,所以rand()获得的数,也就一直在变,相当于是随机数了。只要用户或第三方不设置随机种子,那么在默认情况下随机种子来自系统时钟。如果想在一个程序中生成随机数序列,需要至多在生成随机数之前设置一次随机种子。 即:只需在主程序开始处调用srand((unsigned)time(NULL)); 后面直接用rand就可以了。不要在for等循环放置srand((unsigned)time(NULL));
#include<graphics.h>
#include<math.h>
#include<time.h>
#include <cmath>
#define Width 450
#define High 450
#define NUM_MOVERS 800
#define FRICTION 0.96f //摩擦力,阻尼系数
struct Mover
{
COLORREF color; //颜色
float x, y;
float vx, vy;
float radius;
};
//定义全局变量
Mover movers[NUM_MOVERS];
int mouseX, mouseY; //当前鼠标坐标
int isMouseDown; //鼠标左键是否按下
int preMouseX, preMouseY; //上次鼠标坐标
int mouseVX, mouseVY; //鼠标的速度
//绝对延时,是为在不同的计算机上实现同样的速度运行效果
void delay(DWORD ms)
{
static DWORD oldtime = GetTickCount();
while (GetTickCount() - oldtime < ms)
Sleep(1);
oldtime = GetTickCount();
}
void startup()
{
//设置随机种子
srand((unsigned int)time(NULL));
for (int i = 0; i < NUM_MOVERS; i++)
{
movers[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
movers[i].x = rand() % Width;
movers[i].y = rand() % High;
movers[i].vx = float(cos(float(i))) * (rand() % 34);
movers[i].vy = float(sin(float(i))) * (rand() % 34);
movers[i].radius = (rand() % 34) / 15.0;
}
//初始化 当前鼠标坐标 和 上次鼠标 都在画布中心
mouseX =preMouseX=Width / 2;
mouseY =preMouseY= High / 2;
isMouseDown = 0; //初始鼠标未按下
initgraph(Width,High);
BeginBatchDraw();
}
void show()
{
clearrectangle(0, 0, Width - 1, High - 1); //清空画面中全部矩形区域
//画小球
for (int i = 0; i < NUM_MOVERS; i++)
{
setcolor(movers[i].color);
setfillstyle(movers[i].color);
fillcircle(movers[i].x+0.5, movers[i].y+0.5, movers[i].radius+0.5); //四舍五入
}
isMouseDown = 0;
FlushBatchDraw();
delay(20); //类似于Sleep(5)的作用
}
void updateWithoutInput()
{
//吸引距离,若小球和鼠标的距离在此范围内,则受到向内的吸引力
float toDist = Width * 0.86;
//击打距离,若小球与鼠标的距离在此范围内,则会受到向外的斥力。
float blowDist =Width * 0.5;
//扰动距离,若小球与鼠标的距离在此范围内,则会受到向外的扰动。
float stirDist = Width * 0.125;
//前后两次运行间,鼠标移动的距离,及鼠标的速度
mouseVX = mouseX - preMouseX;
mouseVY = mouseY - preMouseY;
//为记录这次鼠标坐标,更新上次鼠标坐标变量
preMouseX = mouseX;
preMouseY = mouseY;
//对所有的小球遍历
for (int i = 0; i < NUM_MOVERS; i++)
{
//计算当前鼠标和小球的位置差
float dX = movers[i].x - mouseX;
float dY = movers[i].y - mouseY;
//当前小球和鼠标的位置差
float d = sqrt(dX * dX + dY * dY);
//将dX和dY归一化,仅反映方向,和距离无关
if (d != 0)
{
dX /= d;
dY /= d;
}
else
{
dX = 0;
dY = 0;
}
//距离小于toDist 则小球受到鼠标吸引
if (d < toDist)
{
//吸引力引起的加速度幅度,越近,加速度越大, 但吸引力的值任仍比上面斥力的值明显小很多。
float toAcc = (1 - (d /toDist)) * Width * 0.0014f;
//由于归一化,加速度幅度值为toAcc,得到小球新速度
movers[i].vx = movers[i].vx - dX * toAcc;
movers[i].vy = movers[i].vy - dY * toAcc;
}
if (isMouseDown && d < blowDist)
{
//击打力引起的加速度幅度,此公式表明小球距鼠标越近,由击打斥力引起的加速度越大。
float blowAcc = (1 - (d / blowDist)) * 10;
//由上面的归一化, 加速度赋值为blowACC, 得到新的小球速度。
movers[i].vx = movers[i].vx + dX * blowAcc + 0.5f - float(rand()) / RAND_MAX;
movers[i].vy = movers[i].vy + dY * blowAcc + 0.5f - float(rand()) / RAND_MAX;
//float (rand())/RAND_MAX //随机产生0-1的数
//0.5f-float (rand())/RAND_MAX产生-0.5到0.5的随机数,加入一些扰动
}
//若小球与鼠标的距离小于stirDist,则受到扰动
if (d < stirDist)
{
//扰动力引起的加速度幅度,小球距鼠标越近,引起的加速度越大,扰动力越小
float mAcc = (1 - (d / stirDist)) * Width * 0.00026f;
//鼠标移动速度越快,扰动越大
movers[i].vx = movers[i].vx + mouseVX * mAcc;
movers[i].vy = movers[i].vy + mouseVY * mAcc;
}
//小球运动有一个阻尼(摩擦力),速度逐渐减小
movers[i].vx *= FRICTION;
movers[i].vy *= FRICTION;
//速度的绝对值
float avgvx = abs(movers[i].x);
float avgvy = abs(movers[i].y);
//两个方向的平均速度
float avgv = (avgvx + avgvy) * 0.5f;
//由于阻尼作用,若速度过小,则乘以一个0到3的随机数,会较大概率变大速度
if (avgvx < 0.1)
movers[i].vx = movers[i].vx * float((rand())) / RAND_MAX * 3;
if (avgvy < 0.1)
movers[i].vy = movers[i].vy * float(rand()) / RAND_MAX * 3;
//小球的半径在0.4到3.5之间,速度越大,半径越大
float sc = avgv * 0.45f;
sc = max(min(sc, 3.5f), 0.4f);
movers[i].radius = sc;
//更新小球的坐标
float nextx = movers[i].x + movers[i].vx;
float nexty = movers[i].y + movers[i].vy;
//小球超过上下左右的边界,小球反弹
if (nextx > Width)
{
nextx = Width;
movers[i].vx = -movers[i].vx;
}
else if (nextx < 0)
{
nextx = 0;
movers[i].vx = -movers[i].vx;
}
if (nexty > High)
{
nexty = High;
movers[i].vy = -movers[i].vy;
}
else if (nexty < 0)
{
nexty = 0;
movers[i].vy = -movers[i].vy;
}
//更新小球的位置速度的结构体数组
movers[i].x = nextx;
movers[i].y = nexty;
}
}
void updateWithInput()
{
//定义鼠标信息
MOUSEMSG m;
while (MouseHit())
{
m = GetMouseMsg();
if (m.uMsg = WM_MOUSEMOVE)
{
mouseX = m.x;
mouseY = m.y;
}
else if (m.uMsg = WM_LBUTTONDOWN) //鼠标左键按下
{
isMouseDown = 1;
}
else if (m.uMsg = WM_LBUTTONUP) //鼠标左键松开
{
isMouseDown = 0;
}
}
}
void gameover()
{
EndBatchDraw();
closegraph();
}
int main()
{
startup();
while (1)
{
show();
updateWithoutInput();
updateWithInput();
}
gameover();
return 0;
}