EGE专栏:EGE专栏
更详细的EGE相关介绍请查看 EGE专栏 中的 EGE基础教程
这个答疑帖回答了新手常问的一些问题
http://tieba.baidu.com/p/5219031936
#include
int main()
{
//初始化为640*480大小
initgraph(640, 480);
//等待用户按键
getch();
//关闭图形界面
closegraph();
return 0;
}
#include
int main()
{
//设置为普通窗口(去除动画,有边框),设置窗口的位置
setinitmode(0, 100, 100);
//设置为手动模式,只有在调用延时功能的函数时才更新绘制,减少闪烁
setrendermode(RENDER_MANUAL); //设置为手动模式
int screenWidth = 640, screenHeight = 480;
initgraph(screenWidth, screenHeight); //初始化窗口
setcaption("EGE窗口标题"); //设置窗口标题
//颜色设置
setbkcolor(WHITE); //设置背景颜色
setcolor(RED); //设置画笔颜色
setfillcolor(YELLOW); //设置填充颜色
//设置文字背景颜色为透明(如果不设置文字会自带色块,当然,如果不清屏,直接覆盖,就不用设置)
//setbkmode(TRANSPARENT);
//基本的循环,控制帧率为每秒60帧,这是大多显示器默认的刷新频率
//is_run() 判断窗口是否被关闭,窗口关闭则退出循环
for ( ; is_run(); delay_fps(60)) {
//绘画, 交互
}
//绘图结束,关闭窗口
closegraph();
return 0;
}
initgraph(640, 480, 0);
closegraph();
调用 closegraph(), 关闭窗口之后,窗口也仅仅是不显示,程序仍在运行,不是真正的关闭 ,再次 initgraph()打开也不会重新初始化窗口。
initgraph(screenWidth, screenHeight, 0);
initgraph(screenWidth, screenHeight, INIT_WITHLOGO);
setinitmode(INIT_WITHLOGO, 100, 100);
initgraph(640, 480);
在 initgraph() 之后调用
setcaption("EGE窗口标题");
initgraph() 中设置初始化模式为 INIT_NOBORDER
initgraph(640, 480, INIT_NOBORDER);
那什么时候 is_run() 会返回 false 呢? 就是窗口初始化模式先设置INIT_NOFORCEEXIT,然后点击EGE窗口右上角的关闭按钮,或者使用快捷键 Alt + F4 ,这是EGE窗口并不会关闭,但是EGE窗口环境标记 会变成 false, is_run() 就会返回 false 。
如果不设置的话,点击EGE窗口右上角的关闭按钮将窗口关闭后,程序就会被强制退出,不会再运行下去。
所以这时下面的两个程序是一样的,循环外面的那句都不会被运行。
for ( ; is_run(); delay_fps(60)) {
}
//下面不会执行
printf("窗口环境已不存在\n");
closegraph();
for ( ; true; delay_fps(60))
delay_fps(60)) {
}
//下面不会执行
printf("窗口环境已不存在\n");
closegraph();
当 窗口初始化模式 添加 INIT_NOFORCEEXIT 后,此时点击EGE窗口右上方关闭按钮并不会关闭EGE窗口,但是此is_run() 会返回 false,从而退出循环,往下执行程序。
如下,需要先调出控制台,
#define SHOW_CONSOLE
#include
#include
#include
int main()
{
initgraph(640, 480, INIT_NOFORCEEXIT);
setbkcolor(WHITE);
setcolor(BLACK);
setfont(30, 0, "黑体");
xyprintf(40, 40, "请关闭EGE窗口,然后看控制台");
for (; is_run(); delay_fps(60)) {
}
printf("已经退出循环\n");
system("pause");
closegraph();
return 0;
}
需要注意,当点击EGE窗口关闭按钮后,getch(), getkey() 和 getmouse() 将不会阻塞程序
#define SHOW_CONSOLE
#include
需要注意的是,EGE中的 getch() 是不能用在控制台上的,也就是说,如果你设置了显示控制台,那么焦点在控制台上时,按任意键是没有效果的。
如果你在程序中包含了 graphics.h 或 ege.h 头文件,但是又没有调用创建图形窗口,只使用控制台。这时调用EGE 中的 getch() 是会出问题的, 因为EGE中的getch()对控制台不起作用。
那如果我想使用控制台,想要 getch(),怎么办呢?
方法一:
使用 stdlib.h 中的 system() 替代
#include
system("pause");
效果是起到暂停作用,按下任意键继续,并且显示 “ 按下任意键继续… ”
方法二:
在不同的源文件中包含,并且自己另外定义一个命名不冲突的函数。
graphics.h 与 conio.h 分开包含在不同的.cpp文件中
可以另创建一个.cpp文件, 包含
如果你是使用VS的话,编译时还可能说使用了被遗弃的 getch() 。这时把 getch() 换成用 _getch() 就好了
包含了 ege 的头文件后,_getch()是不可以用的
#inlcude
int getch_console() {
return getch();
}
然后就在使用到的源文件中,加个函数声明,然后就可以用了
#define SHOW_CONSOLE
#include
#include
int getch_console();
int main()
{
int a;
printf("输入数字:");
scanf("%d", &a);
printf("输入的数字为:%d\n按任意键继续", a);
getch_console();
initgraph(640, 480, 0);
xyprintf(100, 220, "按任意键退出");
getch();
closegraph();
return 0;
}
比如下面这个有什么问题?
for ( ; is_run(); delay_fps(60)) {
PIMAGE pimg = newimage();
getimage(pimg, "image.jpg");
//绘图
putimage(0, 0, pimg);
}
1. 创建图像后没有使用 delimage() 销毁图像,出现了 内存泄漏 。由于每秒创建几十个图像对象,如果你图片很大的话,运行久了,你会发现内存占用极高。
2. 从文件中读取图像是很慢的,很耗时的过程,如果你在里面每次都读取数目极多的图像,会卡到你怀疑人生。
3. 图片文件中的图像都是不变的内容,没必要重复获取。
4. 除非你要获取的图像是不断变化的,否则不要在循环中重复地读取。重复地读取相同的图像,除了能让你的程序卡以外没啥用
要把从PIMAGE 放在帧循环外读取,退出后,如果不用,记得使用 delimage()
PIMAGE pimg = newimage();
getimage(pimg, "image.jpg");
for ( ; is_run(); delay_fps(60)) {
//操作
//绘图
putimage(0, 0, pimg);
}
//记得销毁
delimage(pimg);
这就需要使用文件, 将程序中的一些数据写入文件来保存。在每次程序开始运行时,就从文件中读取数据。这样就可以做到读取上次的进度的。
//设置为INIT_RENDERMANUAL模式,以及窗口位置
setinitmode(INIT_RENDERMANUAL, 100, 100);
initgraph(640, 480);
或者
initgraph(640, 480, INIT_RENDERMANUAL);
initgraph(640, 480);
setrendermode(RENDER_MANUAL);
EGE中画点是用 putpixel() 函数
putpixel 做了两个工作:
由此,有了更高效的 putpixel_f(), 它并不会检测点的位置是否在有效范围内,少了一些运算,能更快,但缺点是需要调用者确保位置的合法性。不然将会越界访问。
频繁的调用函数依然是有些费时,所以可以获取EGE窗口帧缓存的首地址,然后由我们来给对应像素改变颜色。
pimg 若为 NULL 则表示窗口帧缓存
)color_t* buff = getbuffer(pimg);
pimg 若为 NULL 则表示窗口帧缓存
)int width = getwidth(pimg), height = getheight(pimg);
buff[x + y * width] = color;
坐标 (x, y) 的位置对应缓存区中的 x + y * width
这省去了调用函数的开销,但是自己要保证不要越界访问
即要确保0 <= x < width, 0 <= y < height;
cleardevice(PIMAGE pimg = NULL);
参数是一个PIMAGE, 默认是NULL, 即将窗口清屏
cleardevice();
如果是传入一个图像,则是对图像清屏
cleardevice(pimg);
for ( ; is_run(); delay_fps(60)) {
cleardevice();
//绘图
draw();
}
PIMAGE 是 指向IMAGE对象的指针类型,定义如下
typedef IMAGE* PIMAGE;
IMAGE 是EGE中的图像类,它的创建必须在窗口初始化之后。
使用 newimage() 可以创建一个IMAGE对象,并返回指向该对象的指针。因为PIMAGE仅仅是指针类型,所以可以先定义之后再用 newimage() 赋值。
PIMAGE pimg;
pimg = newimage();
PIMAGE newimage(); // 创建PIMAGE
PIMAGE newimage(int width, int height); // 创建PIMAGE
函数名(参数, ..., PIMAGE pimg = NULL)
其中 PIMAGE pimg = NULL 的表示接受一个 PIMAGE参数,但是这个参数可以省略,省略时相当于参数为 NULL。而对于EGE中的PIMAGE 类型, 很大一部分如果是NULL, 则指的是窗口, 如果是一个指向图像的指针,则操作的是图像。
PIMAGE pimg = newimage();
getimage(pimg, "文件名");
详细可以参考EGE专栏中的EGE基础教程中篇
PIMAGE pimgs[20];
char fileName[30];
for (int i = 0; i < 20; i++) {
sprintf(fileName, "image%d.png", i + 1);
pimgs[i] = newimage();
getimage(pimgs[i], fileName);
}
for (int i = 0; i < 20; i++) {
delimage(pimgs[i]);
pimgs[i] = NULL;
}
getimage(PIMAGE pimg, int x, int y, int width, int height, PIMAGE src = NULL);
getimage(pimg, 0, 0, getwidth(), getheight());
PIMAGE pimg = newimage();
getimage(pimg, 0, 0, getwidth(), getheight());
PIMAGE pimg = newimage();
getimage(pimg, 0, 0, width, heigth, anotherPimg);
有些人还会在一个本子每页上手绘一些漫画,当快速翻页的时候,也会看到动画的效果。
动画是由一张张图片组成的,在计算机中,我们称每一张图片为 一帧画面 。
如果我们想实现这么一个动画:一个水杯放在桌子的左边,移动到右边,那么我们实际操作的,只是水杯。
所以动画的实现,只是对运动变化了的部分的处理。
类似于上面提到的手绘翻页方式,我们可以将这个水杯在每帧画面中的位置一一找出来,这样实现动画的方式就叫作 逐帧动画,我们需要处理动画中的每一帧。
我们一般在计算机上用 FPS ( Frames Per Second) ,即 每秒的帧数 来表示动画的刷新速度,基于屏幕的刷新率等其他原因,在计算机上一般采用 60 FPS。
如果运动变化幅度较缓,减半到 30 FPS 时,我们肉眼也是可接受的。
较低的 FPS 会让我们有“卡顿”的感觉。
逐帧动画是最直接的,但要处理的帧数太多,所以实现过程是会麻烦。
计算机的工作就是来完成重复单调的工作的,所以,有些工作是可以考虑让计算机来完成的。
上面的例子,可以变成一个涉及数学和物理的问题:一个杯子初始位置在左边,n秒后匀速运动到右边,那么在每 1/60 秒的时候,这个杯子的位置显然是可以计算出来的了。
所以,我们其实只需要指定一些 关键 信息就能让计算机自己计算出每一帧杯子的位置了:
起始位置,比如一个坐标 (0,0)
结束位置,再比如一个坐标 (100,0)
动画总时间,比如 0.25 秒
匀速运动
这种方式就称之为 关键帧动画。即我们只需要给定几个关键帧的画面信息,关键帧与关键帧之间的过渡帧都将由计算机自动生成
//基础动画二:简单平移动画
#include
void mainloop()
{
// 动画控制变量,控制横坐标,初始值为0
int x = 0;
setcolor(EGERGB(0, 0xFF, 0));
setbkcolor(WHITE);
setfillcolor(EGERGB(0, 0, 0xFF));
for (; is_run(); delay_fps(60))
{
// todo: 逻辑更新
//计算新坐标,右移一个像素,如果等于440则重新移回x=0,达到动画循环
x = (x + 2) % 440;
// todo: 图形更新
//清屏,重新在新的位置绘图图像
cleardevice();
//以x为圆的左边界绘画,为什么是左边界?因为圆心坐标是 (x + 半径) 了
fillellipse(x + 100, 200, 100, 100);
}
}
int main(void)
{
//INIT_ANIMATION相当于INIT_NOFORCEEXIT|INIT_DEFAULT|INIT_RENDERMANUAL
//下面就不需要再多一步setrendermode
setinitmode(INIT_ANIMATION);
// 图形初始化,窗口尺寸640x480
initgraph(640, 480);
// 随机数初始化,如果需要使用随机数的话
randomize();
// 程序主循环
mainloop();
// 关闭绘图设备
closegraph();
return 0;
}
如果一些动画是有一些连续的图组成,可以使用PIMAGE数组 保存,然后根据帧数循环绘图。
如果按帧循环帧率是A FPS, 帧动画的帧率是每秒B帧, 那计算公式应该是
((FramCount * B) / A) % B
PIMAGE pimgs[8] = {NULL};
获取图像
unsigned int FrameCount = 0;
for ( ; is_run(); delay_fps(60)) {
//绘图
putimage(0, 0, pimgs[FrameCount * 8 / 60 % 8 ]);
FrameCount++;
}
销毁图像
EGE没有视频播放的类,但EGE是基于windowsAPI的,可以使用windowsAPI的函数来实现视频播放。
主要是使用mciSendString() 函数发送命令,来播放视频。
由于需要把视频嵌入到EGE窗口,所以需要获取EGE窗口句柄
HWND egeHWnd = getHWnd();
void PlayVideoInWindow(char *pszFileName, HWND hWnd, int x, int y, int width, int height)
{
char openCommand[MAX_PATH] = { 0 };
char sizeCommand[MAX_PATH] = { 0 };
/*构建命令字符串*/
// 构造mci打开视频命令, 设置视频播放的窗口
wsprintf(openCommand, "open \"%s\" type mpegvideo alias myvideo parent %u style %u", pszFileName, hWnd, WS_CHILD);
// 构造mci视频播放位置大小命令, 设置视频播放的窗口
wsprintf(sizeCommand, "put myvideo window at %d %d %d %d", x, y, width, height);
// 打开视频, 指定窗口
mciSendString(openCommand, NULL, 0, NULL);
// 设置视频播放位置及画面大小
mciSendString(sizeCommand, NULL, 0, NULL);
// 播放视频
mciSendString("play myvideo", NULL, 0, NULL);
}
所以只是做个播放视频的说明,其它问题解决方法请找相关资料(我不会)。
#include
#include
void PlayVideoInWindow(char *pszFileName, HWND hWnd, int x, int y, int iWidth, int iHeight);
int main(void)
{
setinitmode(0, 100, 100);
int screenWidth = 640, screenHeight = 480;
initgraph(screenWidth, screenHeight);
setbkcolor(WHITE);
char fileName[] = "秦时明月之君临天下.avi";
HWND hWnd = getHWnd();
PlayVideoInWindow(fileName, hWnd, 0, 0, screenWidth, screenHeight);
getch();
mciSendString("close myvideo", NULL, 0, NULL);
closegraph();
return 0;
}
void PlayVideoInWindow(char *pszFileName, HWND hWnd, int x, int y, int width, int height)
{
char openCommand[MAX_PATH] = { 0 };
char sizeCommand[MAX_PATH] = { 0 };
/*构建命令字符串*/
// 构造mci打开视频命令, 设置视频播放的窗口
wsprintf(openCommand, "open \"%s\" type mpegvideo alias myvideo parent %u style %u", pszFileName, hWnd, WS_CHILD);
// 构造mci视频播放位置大小命令, 设置视频播放的窗口
wsprintf(sizeCommand, "put myvideo window at %d %d %d %d", x, y, width, height);
// 打开视频, 指定窗口
mciSendString(openCommand, NULL, 0, NULL);
// 设置视频播放位置及画面大小
mciSendString(sizeCommand, NULL, 0, NULL);
// 播放视频
mciSendString("play myvideo", NULL, 0, NULL);
}
#include
int main() {
initgraph(640, 480, 0);
closegraph();
getch();
return 0;
}
关闭了窗口之后,有一个暂停接受键盘输入的 getch(), 因为窗口已经关闭,没有了窗口,已经无法对程序进行键盘输入了,程序仍在运行,这时需要在任务管理器结束程序。
原因是图像的创建需要在窗口初始化之后才能进行
图像的创建使用 newimage(), 窗口初始化在第一次调用 initgraph() 是进行,所以调用 newimage() 必须是在 第一次调用 initgraph() 之后。
如果你定义的类的构造函数里面有创建图像操作,那么就不能在窗口初始化之前创建该类的对象。
需要注意的是全局变量,全局变量是在main()之前初始化完成的。所以如果一个类有全局对象,那么就不能在对应的构造函数中创建图像。
PIMAGE pimg;
getimage(pimg, "image.jpg");
原因是使用的 pimg 并没有初始化,需要先创建图像才能调用 getimage()。
PIMAGE pimg = newimage();
getimage(pimg, "image.jpg");
EGE专栏:EGE专栏