最近和别人聊天,聊到了控制台绘图,曾经学了一些windows的API编程,却没想到直接在控制台窗口上进行绘图
今天有时间,便把代码写了出来
#include <Windows.h> #include <stdio.h> /************************************************************************/ /* 函数 DrawCircle /* 参数 HDC hdc 绘图的句柄 /* int x /* int y 圆心的x,y坐标 /* int r 圆的半径 /* COLORREF color 圆的填充颜色 /* 功能 在(x, y)处画一个半径为r的圆,用color的颜色填充 /************************************************************************/ void DrawCircle(HDC hdc, int x, int y, int r, COLORREF color) { HBRUSH brush = (HBRUSH)CreateSolidBrush (color); //新建一个画刷 SelectObject(hdc, (HGDIOBJ)brush); //选择画刷 Ellipse(hdc, x-r, y-r, x+r, y+r); //画一个圆 DeleteObject(brush); //删除画刷 } int main() { HWND console = GetConsoleWindow(); //获取控制台窗口句柄 HDC console_hdc = GetDC(console); //获取绘图dc RECT rect; //保存绘图区域大小的结构体 int x, y, //小球的xy坐标 dx = 3, //x的增量 dy = 3; //y的增量 int r = 0, g = 0, b = 0; //小球颜色 int speed; //小球速度 COORD pos={0,4}; //光标位置 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); GetClientRect(console, &rect); //首先获取绘图区域的大小 x = 10 + rand() % (rect.right); y = 10 + rand() % (rect.bottom); printf("当前窗口宽度 %d\n当前窗口高度 %d\n请输入小球颜色(r g b):", rect.right, rect.bottom); scanf("%d %d %d", &r, &g, &b); printf("请输入小球运动速度(1~10)"); scanf("%d", &speed); //rgb 的范围分别是0~255 //255 255 255为白色 if (speed <1 || speed > 10 || r >255 || r < 0 || g >255 || g < 0 || b >255 || g <0) { printf("无效的输入"); return 0; } while (1) { SetConsoleCursorPosition(hOut,pos); //设置光标位置 printf("当前小球位置 x= %d \ty=%d ", x, y); DrawCircle(console_hdc, x, y, 10, RGB(r, g, b)); //画一个白色的圆 Sleep(200/speed); DrawCircle(console_hdc, x, y, 10, RGB(0, 0, 0)); //画一个黑色的圆(擦除) if (x > rect.right - 10 || x < 10) dx = -dx; if (y > rect.bottom - 10 || y < 10) dy = -dy; x += dx; y += dy; } ReleaseDC(console, console_hdc); return 0; }
要实现绘图就需要先获取控制台窗口的句柄
句柄说的高大上,其实它就是一个数字而已,它标记了一种资源,根据这个数字,我们就可以找到这个窗口。不然在绘图函数绘图的时候,就可能写到别的窗口上了
获取了控制台句柄之后,我们需要获取HDC,HDC是设备上下文,名字变得更加玄乎了,其实它就是用来绘图的。
GetStdHandle这个API又是获取句柄的,不过这一次不是窗口了,而是标准输出,因为我们程序中需要直接定位光标的位置,因此需要使用到标准输出的句柄
GetClientRect能获取绘图的范围,知道了绘图的范围后,我们能让画出来的圆“碰到”边框后自动的“弹回”
因为printf会在每次画圆之后输出圆的坐标,因此我们需要在每次输出前将光标放到指定的位置
SetConsoleCursorPosition便发挥了这个作用,它将光标定位到第三行第一个字符的位置
DrawCircle是我们自己封装的一个函数,它的具体功能注释中已经明确的写了出来
DrawCircle中我们先新建了一个画刷,因为我们需要用指定的颜色去画圆
SelectObject选择画刷,这样下面的作图就会变成我们所指定的颜色
Ellipse是一个画椭圆的函数,它的后四个参数分别是椭圆的外界矩形的坐标,如果我们使矩形的边长相等,那么它画出来的自然就是圆形了
最后别忘了把画刷用DeleteObject删除,因为画刷也是需要占用资源的
下面就是实现小球的运动过程了,其实实现起来很简单。由于“视觉暂留”效应,我们只需要先画一个圆,再删除,接着立即在他的旁边画一个圆,人眼便会形成一种错觉,小球在运动!
那么怎么擦除呢,很简单啦,在我们画出的圆上再画一个圆,它的颜色和背景颜色相同,这样不就把刚才的圆给覆盖了吗?
小球的运动速度由每次画圆到擦除之前的延时时间决定,根据你的输入,每次延时的值将在200ms – 20ms之间变化
这样,我们便实现了控制台版本的运动的小球程序了