EasyX

看这个

开头加上 graphic.h 头文件

绘图窗口与设备

initgraph(int width,int height,int flag=NULL);
initgraph(800,600,EX_NOMINIMIZE | EX_NOCLOSE); //创建禁用最小化和关闭按钮的绘图窗口
initgraph(800,600,EX_SHOWCONSOLE); //带控制台的图形窗口

结尾加上system("pause")或_getch()/*conio.h*/以显式看到画布
最后再加上closegraph()关闭画布

坐标

默认原点在设别的左上角,X轴向右为正,Y轴向下为正,单位是像素

void setorigin(int x,int y) //用于设置坐标原点
setaspectratio(float xasp,float yasp)
//通过设置 x 和 y 方向上的缩放因子,从而修改绘图的缩放比例或坐标轴方向

setaspectratio(2, 1); // x轴方向的缩放因子为2,y轴方向的缩放因子为1(默认值)
circle(100, 100, 100); //x轴方向长度为200,y轴长度为100

setaspectratio(1, -1); // 缩放因子为负数,可以实现坐标轴的翻转,此行可使y轴向上为正

我真是服了,都不许用setaspectratio(1, -1),这是人类能设计出来的吗

initgraph(400, 400);
setaspectratio(1, -1);
outtextxy(200, 200, "hello");

如上代码执行后什么都不会显示

initgraph(400, 400);
setaspectratio(1, -1);
outtextxy(200, -200, "hello");

上述执行后如图:
image
我真气笑了


update:好像确实是我的问题……应该先设置原点的
范例:

#include <graphics.h>

int main()
{
	initgraph(600, 600);

	setorigin(0, 600);			// 将绘图窗口的左下角作为坐标原点
	setaspectratio(1, -1);		// 缩放因子为负数,可以实现坐标轴的翻转,此行可使y轴向上为正
	circle(100, 100, 100);

	system("pause");
	closegraph();
	return 0;
}

颜色

1.用预定义常量表示颜色(常量名要大写)
2.用16进制
3.RGB(0~255) eg:setlinecolor(RGB(2,2,2));

常量 值 颜色
BLACK 0 黑
BLUE 0xAA0000 蓝
GREEN 0x00AA00 绿
CYAN 0xAAAA00 青
RED 0x0000AA 红
MAGENTA 0xAA00AA 紫
BROWN 0x0055AA 棕
LIGHTGRAY 0xAAAAAA 浅灰
DARKGRAY 0x555555 深灰
LIGHTBLUE 0xFF5555 亮蓝
LIGHTGREEN 0x55FF55 亮绿
LIGHTCYAN 0xFFFF55 亮青
LIGHTRED 0x5555FF 亮红
LIGHTMAGENTA 0xFF55FF 亮紫
YELLOW 0x55FFFF 黄
WHITE 0xFFFFFF 白

EasyX图形绘制函数

函数用法 函数说明
void circle ( int x, int y, int radius ) 画无填充的圆
fillcircle 画有边框的填充圆
solidcircle 画无边框的填充圆
clearcircle 用当前背景色清空圆形区域
void ellipse ( int left, int top, int right, int bottom ) 画无填充的椭圆
fillellipse 画有边框的填充椭圆
solidellipse 画无边框的填充椭圆
clearellipse 用当前背景色清空椭圆区域
void pie ( int left, int top, int right, int bottom, double stangle, double endangle ); 画无填充的扇形
fillpie 画有边框的填充扇形
solidpie 画无边框的填充扇形
clearpie 用当前背景色清空扇形区域
void rectangle ( int left, int top, int right, int bottom ) 画无填充的矩形
fillrectangle 画有边框的填充矩形
solidrectangle 画无边框的填充矩形
clearrectangle 用当前背景色清空矩形区域
void roundrect ( int left, int top, int right, int bottom, int ellipsewidth, int ellipseheight ) 画无填充的圆角矩形
fillroundrect 画有边框的填充圆角矩形
solidroundrect 画无边框的填充圆角矩形
clearroundrect 用当前背景色清空圆角矩形区域
void polygon ( const POINT *points, int num ); 画无填充的多边形
fillpolygon 画有边框的填充多边形
solidpolygon 画无边框的填充多边形
clearpolygon 用当前背景色清空多边形区域
函数名称 函数说明
void putpixel ( int x, int y, COLORREF color ) 画点
void line ( int x1, int y1, int x2, int y2 ) 画直线
void arc ( int left, int top, int right, int bottom, double stangle, double endangle ) 画椭圆弧
void polyline ( const POINT *points, int num ) 画多条连续的直线
void polybezier ( const POINT *points, int num ) 画三次方贝塞尔曲线
void floodfill ( int x, int y, COLORREF color, int filltype = FLOODFILLBORDER ) 填充区域
COLORREF getpixel ( int x, int y ) 获取坐标点的颜色
int getwidth ( ) 获取绘图区的宽度
int getheight ( ) 获取绘图区的高度
setlinecolor( COLOR ) 设置线条颜色
setfillcolor( COLOR ) 设置填充颜色
函数:setlinestyle(PS_SOLID,10)设置线为实线(PS_SOLID),线宽为10

关于floodfill函数,第四个参数0表示 FLOODFILLBORDER,1表示FLOODFILLSURFACE,不写默认为0
对于 FLOODFILLBORDER 填充类型,填充动作以 (x, y) 为起点,向周围扩散,直到遇到 border 指定的颜色才会终止。指定的区域必须是封闭的。适用于填充具有固定颜色边界的区域。
对于 FLOODFILLSURFACE 填充类型,填充动作以 (x, y) 为起点,只要邻接的颜色为 color, 填充就会延伸。适用于填充具有多种颜色边界的区域。

在设置背景色之后,并不会改变现有背景色,而是只改变背景色的值
如果需要修改全部背景色,可以在设置背景色后执行 cleardevice() 函数。
所以,

setbkcolor(BLUE); //该语句执行后背景色不会发生实际变化
clearrectangle(350,400,550,500); //该语句执行用背景色清空矩形区域,矩形内为蓝色

EasyX文字输出

字符数组 TCHAR 当字符串用就可以
定义方式为: TCHAR = _T('A') TCHAR s[] = _T("Hello World")
赋值: TCHAR name[10]; _tcscpy_s(name,10,_T("张三"));
将int转化为字符串:TCHAR num[5]; _stprintf_s(num,_T("数字%d"),1024);

settextcolor(BLUE) 设置字体颜色
settextstyle(int height,int width,_T("宋体")) width可以直接填0,自适应,第三个参数是字体
void outtextxy(int x,int y,TCHAR) 在(x,y)位置输出字符串,(x,y)是这个文本框的左上角位置
textheight(text) 获取字符串的高度
textwidth(text) 获取字符串的宽度(不是一个字的宽度)
outtextxy(300 - w / 2, 300 - h / 2, text); // 在屏幕中心绘制文本
setbkmode(TRANSPARENT),透明背景;setbkmode(OPAQUE),背景用背景色填充(默认)

if (_kbhit()) break;
while (!_kbhit())
// 按任意键退出,_kbhit是非阻塞函数,可以终止类似while(1)的循环

添加音乐

把mp3文件放到项目文件夹里
1.引用Winmm.lib链接库
语法:#pragma comment(lib,"Winmm.lib")
2.播放音乐
语法:mciSendString(_T("open v2.mp3 alias bkmusic"),0,0,0); //alias指定别名,v2.mp3 == bkmusic
音乐名v2.mp3必须和程序文件中存储的音乐文件名称相同,否则调用失败
3.循环播放
语法:mciSendString(_T("play bkmusic repeat"),0,0,0); //重复播放bkmusic,即v2.mp3
4.停止播放
mciSendString(_T("close bkmusic"),0,0,0); 可以不写,直接叉掉

双缓冲绘图

双缓冲绘图通过在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,从而减少对屏幕的直接绘图操作,提高绘图效率、消除屏幕闪烁
BeginBatchDraw() 开始批量绘图
EndBatchDraw() EndBatchDraw(int left,int top,int right,int bottom)(指定区域) 结束批量绘制,并执行未完成的绘制任务
FlushBatchDraw() FlushBatchDraw(int left,int top,int right,int bottom)(指定区域) 执行未完成的绘制任务

范例:自动移动的圆,可以把三条语句未注释和注释掉的对比一下,不使用缓冲会屏幕闪烁

#include <graphics.h>

int main()
{
	initgraph(640, 480);
	 //BeginBatchDraw();			// 开启批量绘图

	setlinecolor(WHITE);			// 设置画线颜色
	setfillcolor(RED);				// 设置填充颜色
	for (int i = 50; i < 600; i++)
	{
		cleardevice();				// 清屏
		circle(i, 100, 40);			// 绘制空心圆
		floodfill(i, 100, WHITE);	// 用红色填充,直到遇到边界为白色为止的区域
		//FlushBatchDraw();		// 刷新批量绘图
		Sleep(10);					// 延时10毫秒
	}

	 //EndBatchDraw();				// 关闭批量绘图
	closegraph();
	return 0;
}

关于时间的一些函数

控制帧数:

//include <windows.h>
#include <graphics.h>

int main()
{
	initgraph(640, 480);
	BeginBatchDraw();

	setlinecolor(WHITE);
	setfillcolor(RED);
	for (int i = 50; i < 600; i++)
	{
		DWORD beginTime = GetTickCount();			// 记录循环开始时间

		cleardevice();
		circle(i, 100, 40);
		floodfill(i, 100, WHITE);
		FlushBatchDraw();

		DWORD endTime = GetTickCount();				// 记录循环结束时间
		DWORD elapsedTime = endTime - beginTime;	// 计算循环耗时
		if (elapsedTime < 1000 / 60)				// 按每秒60帧进行补时
			Sleep(1000 / 60 - elapsedTime);
	}

	EndBatchDraw();
	closegraph();
	return 0;
}

GetTickCount 是一个 Windows 系统函数,用于获取从操作系统启动以来所经过的毫秒数,通过在代码中的不同位置调用该函数,并计算两次调用之间的差值,可以得知某段代码或某个操作的执行时间。单位:毫秒
image

图像处理

把图片放到项目文件夹内
image

IMAGE p; //定义图片变量
p.getwidth(); p.getheight(); //IMAGE类具有内置函数获取宽高
loadimage(&图片变量名,"XX.jpg"/*图片名称*/,int width=0,int height=0); //载入图片
loadimage(NULL, _T("image\\background.jpg")); loadimage(NULL, _T("testt.jpg"), 700, 300,false); 
// 第一个参数为NULL时,直接读取图片至绘图窗口
// 第五个参数若为 true,则会调整窗口以适应图片的大小
putimage(int x,int y,&图片变量名); //显示图片,xy是图片左上角的坐标
putimage(0, 0, 900, 600, &img, 115, 0); // 从图像的(115,0)坐标处截取宽900、高600的部分内容显示在窗口(0,0)处
Resize(&img, 600, 400); // 调整IMAGE的尺寸,注意不是缩放

putimage(0, 0, &img, NOTSRCCOPY);,第四个参数是三元光栅操作码
image

GetImageBuffer 函数,可以设置渐变背景色
#include <graphics.h>

int main()
{
	initgraph(600, 400);

	DWORD* pMem = GetImageBuffer();							// 获取当前窗口所指图像缓冲区的指针	
	for (int i = 0; i < 600 * 400; i++)
		pMem[i] = BGR(RGB(0, 0, i * 256 / (600 * 400)));	// 直接对图像缓冲区每个坐标像素赋值(颜色)

	system("pause");
	closegraph();
	return 0;
}

透明贴图

通过PS制作原图的掩码图和前景图,再进行三元光栅操作叠加而成
#include <graphics.h>

int main()
{
	IMAGE imgGuoqi, imgGuohui, imgGuohuiMask, imgGuohuiFg;
	loadimage(&imgGuoqi, _T("guoqi.jpg"), 1000, 600);			// 加载国旗(背景图)
	loadimage(&imgGuohui, _T("guohui.jpg"), 200, 200);			// 加载国徽原图(白色周边)
	loadimage(&imgGuohuiMask, _T("guohui_mask.jpg"), 200, 200);	// 加载国徽掩码图(白色周边+黑色内容)
	loadimage(&imgGuohuiFg, _T("guohui_fg.jpg"), 200, 200);		// 加载国徽前景图(黑色周边+待显示内容)

	initgraph(1000, 600);

	putimage(0, 0, &imgGuoqi);							// 显示国旗
	putimage(0, 0, &imgGuohui);							// 显示国徽原图
	putimage(0, 200, &imgGuohuiMask);					// 显示国徽掩码图
	putimage(0, 400, &imgGuohuiFg);						// 显示国徽前景图

	// 透明贴图
	putimage(200, 0, &imgGuohuiMask, SRCAND);			// 显示掩码图(SRCAND:按位与)
	putimage(200, 0, &imgGuohuiFg, SRCPAINT);			// 显示前景图(SRCPAINT:按位或)

	system("pause");
	closegraph();
	return 0;
}
![image](https://img2024.cnblogs.com/blog/2930138/202507/2930138-20250709101757678-1964597891.png)
已经有透明底图片了,AlphaBlend 函数实现(推荐)
#include <graphics.h>
#pragma comment(lib, "MSIMG32.LIB")						// 链接器在链接过程中包含指定的库文件

void putimage_alpha(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA });
}

int main()
{
	initgraph(1000, 600);

	IMAGE imgGuoqi, imgBaidu;
	loadimage(&imgGuoqi, _T("guoqi.jpg"), 1000, 600);		// 加载国旗(背景图)
	loadimage(&imgBaidu, _T("baidu.png"));					// 加载百度LOGO(PNG格式),baidu.png已经是透明底了!!!
	putimage(0, 0, &imgGuoqi);										// 显示国旗
	putimage(0, 0, &imgBaidu);										// 显示百度LOGO
	putimage_alpha(0, 300, &imgBaidu);								// 显示百度LOGO(透明贴图),这个函数要自己写

	system("pause");
	closegraph();
	return 0;
}

image

图片动画

是一系列静态的动画帧

#include <graphics.h>
#include <cstdio>
#pragma comment(lib, "MSIMG32.LIB")

const int WINDOW_WIDTH = 1000;			//窗口宽度
const int WINDOW_HEIGHT = 600;			//窗口高度
const int FRAME = 60;					//帧数
const int INTERVAL_MS = 15;				//动画帧间隔
const int IMAGE_NUM = 13;				//动画图片数

/*
帧率是每秒屏幕刷新的速度,决定了动画的流畅度,确保显示稳定
动画帧间隔是决定图片切换的速度 时间间隔越短,角色动作越快;时间间隔越长,动作越慢。
INTERVAL_MS是代码层面的图片切换速度,实际显示出来的时间间隔还和帧率有关
因为屏幕刷新后新操作才会被呈现,如果在屏幕刷新前切换是不会被显示的
*/

//显示透明图片
void putimage_alpha(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();
	AlphaBlend(GetImageHDC(NULL), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA });
}
int cnt = 0;

int main()
{
	bool running = true;			//主循环控制
	IMAGE imgBackground;			//背景图片对象
	IMAGE imgPEA[13];				//玩家动画图片
	TCHAR imgPath[256];				//动画图片文件路径
	int imgIndex = 0;				//动画帧索引
	static int timer = 0;			//动画计时器

	loadimage(&imgBackground, _T("image\\background.jpg"));		//加载背景图片
	for (int i = 0; i < IMAGE_NUM; i++)							//加载动画图片
	{
		_stprintf_s(imgPath, _T("image\\pea\\%d.png"), i + 1);	//动画图片路径(格式转换)
		loadimage(&imgPEA[i], imgPath);							//加载动画图片
	}

	initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
	BeginBatchDraw();

	//主循环
	while (running)
	{
		DWORD beginTime = GetTickCount();

		//数据处理
		timer += 5;
		if (timer > INTERVAL_MS)					//定时器超过预定的时间间隔时切换下一张图片
		{
			imgIndex = (imgIndex + 1) % IMAGE_NUM;			//循环切换图片:索引值0-12
			timer = 0;										//重置计时器
		}

		//这是我自己写的,我认为这个程序每三帧切换一次图片,于是采用了cnt计数,运行后视觉效果没啥变化
		//cnt++;
		//if (cnt > 3) {
		//	imgIndex = (imgIndex + 1) % IMAGE_NUM;
		//	cnt = 0;
		//}

		//绘图
		cleardevice();
		putimage(0, 0, &imgBackground);						//绘制背景图片
		putimage_alpha(500, 300, &imgPEA[imgIndex]);		//绘制豌豆图片
		FlushBatchDraw();

		//帧延时处理
		DWORD endTime = GetTickCount();
		DWORD elapsedTime = endTime - beginTime;
		if (elapsedTime < 1000 / FRAME)
			Sleep(1000 / FRAME - elapsedTime);
	}

	EndBatchDraw();
	closegraph();
	return 0;
}

消息处理

ExMessage 是一个结构体

函数:
ExMessage getmessage(BYTE filter=-1) void getmessage(ExMessage *msg,BYTE filter=-1)从消息缓冲区获取一个消息
bool peekmessage(ExMessage *msg,BYTE filter = -1, bool removemsg = true) 从消息缓冲区获取一个消息并立即返回
void flushmessage(BYTE filter = -1) 清空消息缓冲区

参数说明
msg:指向消息结构体 ExMessage 的指针,用来保存获取到的消息。
filter:指定要获取的消息范围,默认 -1 获取所有类别的消息。可以用以下值或值的组合获取指定类别的消息 具体的,EX_MOUSE鼠标消息,EX_KEY按键消息,EX_CHAR字符消息,EX_WINDOW窗口消息
removemsg:在 peekmessage 处理完消息后,是否将其从消息队列中移除。

image

鼠标消息

ExMessage m;
while (true) {
	m = getmessage(EX_MOUSE);
	if (m.message == WM_MOUSEMOVE) {
		//...
	}else if(m.message == WM_LBUTTONDOWN){
        //...    
    }
}
////////////////////////////////////
ExMessage msg;
while (running) {
	// 消息处理		
	while (peekmessage(&msg)) {
		if (msg.message == WM_MOUSEMOVE) {				// 圆的位置随鼠标位置变化
			x = msg.x;
			y = msg.y;
		}
		else if (msg.message == WM_LBUTTONDOWN) {		// 左键按下圆变红色
			setfillcolor(RED);
		}
		else if (msg.message == WM_LBUTTONUP) {			// 左键松开圆变白色
			setfillcolor(WHITE);
		}
		else if (msg.message == WM_RBUTTONDOWN) {		// 右键按下结束主循环
			running = false;
		}
	}
	// 绘图
	cleardevice();					// 清除屏幕
	solidcircle(x, y, r);			// 绘制当前帧内容
	FlushBatchDraw();				// 刷新批量绘图
}

键盘消息

用键盘控制小球
typedef struct Ball
{
	int x;				// 小球圆心坐标x
	int y;				// 小球圆心坐标y
	int r;				// 小球半径
	int dx;				// 小球在x轴方向移动的增量
	int dy;				// 小球在y轴方向移动的增量
	COLORREF color;		// 小球颜色
} Ball;

int main()
{
	bool running = true;
	ExMessage msg;
	Ball ball = { 300, 300, 20, 5, 5, YELLOW };	// 创建小球并初始化
	initgraph(600, 600);
	BeginBatchDraw();

	while (running)
	{
		while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN) //按键按下状态
			{
				switch (msg.vkcode)					// 判断虚拟键代码
				{
				case 'w'://小写w					// 上键:小球Y坐标减少
				case 'W'://大写W
				case VK_UP://箭头上
					ball.y -= ball.dy;
					break;

				case 's':
				case 'S':
				case VK_DOWN:						// 下键:小球Y坐标增加
					ball.y += ball.dy;
					break;

				case 'a':
				case 'A':
				case VK_LEFT:						// 右键:小球X坐标减少
					ball.x -= ball.dx;
					break;

				case 'd':
				case 'D':
				case VK_RIGHT:						// 右键:小球X坐标增加
					ball.x += ball.dx;
					break;

				case VK_ESCAPE:						// ESC键:结束主循环
					running = false;
					break;
				}
			}			
		}

		cleardevice();							// 清除屏幕
		setfillcolor(ball.color);				// 设置填充颜色
		solidcircle(ball.x, ball.y, ball.r);	// 绘制无边框填充圆;
		FlushBatchDraw();
	}

	EndBatchDraw();
	closegraph();
	return 0;
}
缺陷:小球不能斜向移动,小球越界

虚拟键代码 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes

用键盘控制小球(支持斜向运动)
#include <graphics.h>

// 用结构体封装小球属性
typedef struct Ball
{
	int x;						// 小球圆心坐标x
	int y;						// 小球圆心坐标y
	int r;						// 小球半径
	int dx;						// 小球在x轴方向移动的增量
	int dy;						// 小球在y轴方向移动的增量
	COLORREF color;				// 小球颜色
	bool isMoveUp = false;		// 小球是否向四个方向移动
	bool isMoveDown = false;
	bool isMoveLeft = false;
	bool isMoveRight = false;
} Ball;

int main()
{
	bool running = true;
	ExMessage msg;
	Ball ball = { 300, 300, 20, 5, 5, YELLOW };		// 创建小球并初始化
	initgraph(600, 600);
	BeginBatchDraw();

	while (running)
	{
		DWORD beginTime = GetTickCount();			// 记录循环开始时间

		// 消息处理
		while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN)			// 按下按键处理
			{
				switch (msg.vkcode)
				{
				case 'w':
				case 'W':
				case VK_UP:
					ball.isMoveUp = true;
					break;

				case 's':
				case 'S':
				case VK_DOWN:
					ball.isMoveDown = true;
					break;

				case 'a':
				case 'A':
				case VK_LEFT:
					ball.isMoveLeft = true;
					break;

				case 'd':
				case 'D':
				case VK_RIGHT:
					ball.isMoveRight = true;
					break;

				case VK_ESCAPE:
					running = false;
					break;
				}
			}
			if (msg.message == WM_KEYUP)			// 松开按键处理
			{
				switch (msg.vkcode)
				{
				case 'w':
				case 'W':
				case VK_UP:
					ball.isMoveUp = false;
					break;

				case 's':
				case 'S':
				case VK_DOWN:
					ball.isMoveDown = false;
					break;

				case 'a':
				case 'A':
				case VK_LEFT:
					ball.isMoveLeft = false;
					break;

				case 'd':
				case 'D':
				case VK_RIGHT:
					ball.isMoveRight = false;
					break;
				}
			}
		}

		// 数据处理:根据小球的移动状态设置其坐标
		if (ball.isMoveUp)
			ball.y -= ball.dy;
		if (ball.isMoveDown)
			ball.y += ball.dy;
		if (ball.isMoveLeft)
			ball.x -= ball.dx;
		if (ball.isMoveRight)
			ball.x += ball.dx;
		
		// 绘图
		cleardevice();								// 清除屏幕
		setfillcolor(ball.color);					// 设置填充颜色
		solidcircle(ball.x, ball.y, ball.r);		// 绘制无边框填充圆;
		FlushBatchDraw();

		// 帧延时
		DWORD endTime = GetTickCount();				// 记录循环结束时间
		DWORD elapsedTime = endTime - beginTime;	// 计算循环耗时
		if (elapsedTime < 1000 / 60)				// 按每秒60帧进行补时
			Sleep(1000 / 60 - elapsedTime);
	}

	EndBatchDraw();
	closegraph();
	return 0;
}

该代码中需要帧延时,第一个程序中,有按键操作才会有移动,速度取决于按键速度;
(解释一下长按操作也会一直移动的原因:这是因为 Windows 系统的键盘自动重复机制在起作用,系统默认的重复率通常是每秒 30 次左右,这就限制了小球的最大移动速度)
而第二个程序中如果长按一个键,每次循环都为true都会移动,速度取决于刷新频率。

窗口碰撞检测
typedef struct Ball
{
	int x;						// 小球圆心坐标x
	int y;						// 小球圆心坐标y
	int r;						// 小球半径
	int dx;						// 小球在x轴方向移动的增量
	int dy;						// 小球在y轴方向移动的增量
	COLORREF color;				// 小球颜色
	bool isMoveUp = false;		// 小球是否向四个方向移动
	bool isMoveDown = false;
	bool isMoveLeft = false;
	bool isMoveRight = false;
} Ball;

int main()
{
	bool running = true;
	ExMessage msg;
	Ball ball = { 300, 300, 20, 5, 5, YELLOW };		// 创建小球并初始化
	initgraph(WIN_WIDTH, WIN_HEIGHT);
	BeginBatchDraw();

	while (running)
	{
		DWORD beginTime = GetTickCount();			// 记录循环开始时间

		// 消息处理
		while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN)			// 按下按键处理
			{
				switch (msg.vkcode)
				{
				case 'w':
				case 'W':
				case VK_UP:
					ball.isMoveUp = true;
					break;

				case 's':
				case 'S':
				case VK_DOWN:
					ball.isMoveDown = true;
					break;

				case 'a':
				case 'A':
				case VK_LEFT:
					ball.isMoveLeft = true;
					break;

				case 'd':
				case 'D':
				case VK_RIGHT:
					ball.isMoveRight = true;
					break;

				case VK_ESCAPE:
					running = false;
					break;
				}
			}
			if (msg.message == WM_KEYUP)			// 松开按键处理
			{
				switch (msg.vkcode)
				{
				case 'w':
				case 'W':
				case VK_UP:
					ball.isMoveUp = false;
					break;

				case 's':
				case 'S':
				case VK_DOWN:
					ball.isMoveDown = false;
					break;

				case 'a':
				case 'A':
				case VK_LEFT:
					ball.isMoveLeft = false;
					break;

				case 'd':
				case 'D':
				case VK_RIGHT:
					ball.isMoveRight = false;
					break;
				}
			}
		}

		// 斜向移动:计算不同方向(包括同时)按下时的速度增量

        //if (ball.isMoveUp)
		//	ball.y -= ball.dy;
		//if (ball.isMoveDown)
		//	ball.y += ball.dy;
		//if (ball.isMoveLeft)
		//	ball.x -= ball.dx;
		//if (ball.isMoveRight)
		//	ball.x += ball.dx;
        // 斜向移动优化
		int directX = ball.isMoveRight - ball.isMoveLeft;
		int directY = ball.isMoveDown - ball.isMoveUp;
		double directXY = sqrt(directX * directX + directY * directY);
		if (directXY != 0)
		{
			double factorX = directX / directXY;	//计算X、Y方向的标准化分量
			double factorY = directY / directXY;
			ball.x += (int)ball.dx * factorX;		//小球坐标 = 方向增速 * 方向的标准化分量
			ball.y += (int)ball.dy * factorY;
		}

		// 边缘检测
		if (ball.y - ball.r <= 0)					// 上
			ball.y = ball.r;
		if (ball.y + ball.r >= WIN_HEIGHT)			// 下
			ball.y = WIN_HEIGHT - ball.r - 1;
		if (ball.x - ball.r <= 0)					// 左
			ball.x = ball.r;
		if (ball.x + ball.r >= WIN_WIDTH)			// 右
			ball.x = WIN_WIDTH - ball.r - 1;

		// 绘图
		cleardevice();								// 清除屏幕
		setfillcolor(ball.color);					// 设置填充颜色
		solidcircle(ball.x, ball.y, ball.r);		// 绘制无边框填充圆;
		FlushBatchDraw();

		// 帧延时
		DWORD endTime = GetTickCount();				// 记录循环结束时间
		DWORD elapsedTime = endTime - beginTime;	// 计算循环耗时
		if (elapsedTime < 1000 / 60)				// 按每秒60帧进行补时
			Sleep(1000 / 60 - elapsedTime);
	}

	EndBatchDraw();
	closegraph();
	return 0;
}

设置窗口标题

HWND hWnd = GetHWnd();						// 获得窗口句柄	
SetWindowText(hWnd, _T("植物大战僵尸"));		// 使用 Windows API 修改窗口名称
posted @ 2025-07-07 09:47  弗阴  阅读(169)  评论(0)    收藏  举报