Easyx Learning 官方文档链接 EasyX 文档 - 基本说明
文本绘制 基础 需考虑的问题主要有:
项目字符集问题(解决方案见下文)
背景框透明
数字需转为字符串,sprintf_s函数是安全写法(c++可以直接用string容器替代)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void drawtext () { settextstyle (48 , 0 , "微软雅黑" ); setbkmode (OPAQUE); outtextxy (100 , 100 , "hello world!" ); system ("pause" ); outtextxy (100 , 100 , "你好,世界!" ); system ("pause" ); setbkmode (TRANSPARENT); int score = 66 ; char str[50 ] = "" ; sprintf_s (str, "Score:%d" , score); outtextxy (100 , 100 , str); system ("pause" ); }
文字中心显示 核心思路是计算提前绘制的图形中心点,然后加以计算方位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void centertext () { int rx = 30 ; int ry = 380 ; int rw = 300 ; int rh = 60 ; setfillcolor (WHITE); fillrectangle (rx, ry, rx + rw, ry + rh); system ("pause" ); setbkmode (TRANSPARENT); settextcolor (BROWN); settextstyle (48 , 0 , "微软雅黑" ); char str[] = "Center Text" ; int hSpace = (rw - textwidth (str)) / 2 ; int vSpace = (rh - textheight (str)) / 2 ; outtextxy (rx + hSpace, ry + vSpace, str); system ("pause" ); }
消息处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void handleKeyMessage (EXMSG& msg) { if (msg.message == WM_KEYDOWN) { g_isKeyDown[msg.vkcode] = true ; switch (msg.vkcode) { case VK_ESC: closegraph (); exit (0 ); break ; case VK_RETURN: outtextxy (100 , 50 , _T("按下了Enter键(确认)" )); break ; case VK_SPACE: outtextxy (100 , 80 , _T("按下了空格键(暂停/继续)" )); break ; case 'A' : case 'a' : outtextxy (100 , 110 , _T("按下了A键" )); break ; case VK_UP: outtextxy (100 , 140 , _T("按下了上方向键" )); break ; case VK_DOWN: outtextxy (100 , 170 , _T("按下了下方向键" )); break ; case VK_LEFT: outtextxy (100 , 200 , _T("按下了左方向键" )); break ; case VK_RIGHT: outtextxy (100 , 230 , _T("按下了右方向键" )); break ; } } if (msg.message == WM_KEYUP) { g_isKeyDown[msg.vkcode] = false ; } if (msg.message == WM_KEYDOWN) { if (msg.vkcode == 'C' && (GetKeyState (VK_CONTROL) & 0x8000 )) { outtextxy (100 , 260 , _T("按下了 Ctrl+C 组合键" )); } if (msg.vkcode == VK_UP && (GetKeyState (VK_SHIFT) & 0x8000 )) { outtextxy (100 , 290 , _T("按下了 Shift+上方向键" )); } } }
针对组合键处理进行解释:
GetKeyState() 返回值的含义
GetKeyState() 函数会返回一个 16 位的短整数(SHORT),这个值的 ** 最高位(第 15 位)** 是 “按键状态位”。
如果最高位是 1,表示按键当前是按下 的;如果是 0,表示是松开 的。
0x8000 的二进制意义
0x8000 是一个 16 进制数,它的二进制形式是 1000 0000 0000 0000。
这个数的特点是只有最高位是 1,其余位都是 0 。
按位与(&)的作用
当我们把 GetKeyState() 的返回值和 0x8000 做按位与时,只有最高位会被保留,其他所有位都会被清零。
如果结果非零(即等于 0x8000),就说明最高位是 1 → 按键是按下的。
如果结果为零,就说明最高位是 0 → 按键是松开的。
帧率控制 核心要点:
为什么不用sleep函数,因为普通sleep精度低
双缓冲机制防闪屏
FPS控制帧率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void buffer_FPS () { const clock_t FPS = 1000 / 60 ; int startTime = 0 ; int freamTime = 0 ; int score = 0 ; char str[50 ] = "" ; while (true ) { startTime = clock (); sprintf_s (str, "Score:%d" , score++); settextcolor (BLACK); BeginBatchDraw (); cleardevice (); outtextxy (20 , 20 , str); EndBatchDraw (); outtextxy (20 , 20 , "" ); freamTime = clock () - startTime; if (freamTime < FPS) { Sleep (FPS - freamTime); } } }
图片操作 基础及动画效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 void imgact () { IMAGE img; loadimage (&img, _T("../assets/cat.jpg" ), 470 , 470 ); putimage (0 , 0 , &img); system ("pause" ); } void animation () { IMAGE img[4 ]; char path[100 ] = { 0 }; for (int i = 0 ; i < 4 ; i++) { sprintf_s (path, "../assets/cat%d.jpg" , i + 1 ); loadimage (img + i, path, 100 , 100 ); } const int frameDelay = 100 ; int frameTime = 0 ; int frameStart = 0 ; int frames = 4 ; int index = 0 ; int speed = 500 ; while (true ) { frameStart = clock (); BeginBatchDraw (); cleardevice (); int w = (*(img + index)).getwidth () * index; putimage ((*(img + index)).getwidth () * index, 0 , img + index); EndBatchDraw (); index = (clock () / speed) % frames; frameTime = clock () - frameStart; if (frameDelay > frameTime) { Sleep (frameDelay - frameTime); } }
透明贴图实现 EasyX 用光栅技术处理透明背景
光栅操作(Raster Operations,简称 ROP) 本质是 Windows GDI 提供的像素级位运算指令,能直接在显存 / 内存中对两块像素区域做合并、遮罩、透明等操作。EasyX 依赖 GDI,所以优先用这种原生方案,核心原因有 3 点:
底层依赖决定:基于 Windows GDI 实现
EasyX 不是独立的图形引擎,而是对 Windows GDI 的轻量封装。GDI 本身没有 “一键透明” 的高级接口,而光栅操作(如 BitBlt 函数的 ROP 码) 是 GDI 中唯一能高效实现像素级透明合成的原生方式,EasyX 自然沿用这套机制。
效率优势:硬件 / 系统级加速
光栅操作由 Windows 系统内核或显卡驱动直接处理,属于 “底层硬件加速”,比纯软件遍历每个像素(如逐点判断 Alpha 值)快得多。比如 EasyX 中常用的 putimage 结合 SRCAND/SRCPAINT 等 ROP 码实现透明,本质是调用 GDI 的 BitBlt 光栅指令,避免了 CPU 逐像素计算的开销。
适配性:兼容旧版 Windows 和低性能设备
光栅技术是 Windows 从早期就支持的标准特性,无需依赖 DirectX/OpenGL 等高级图形接口,能保证 EasyX 代码在低配电脑、旧版 Windows(如 XP)上也能正常运行,符合 EasyX “轻量、易上手” 的设计目标。
除了光栅技术,还有 3 种常见方案,各有优缺点,适合不同场景:
方案
实现思路
优点
缺点
适用场景
1. 逐像素 Alpha 混合
遍历图片每个像素,判断透明度值(如 Alpha 通道),只绘制不透明 / 半透明像素
支持半透明 (光栅技术仅支持全透 / 全不透),逻辑直观
CPU 开销大,图片越大越卡顿
小图片、低帧率场景(如静态小图标)
2. 加载带 Alpha 通道的 PNG
用 EasyX 扩展函数(如 loadimage 加载 PNG)+ 原生透明支持
无需手动处理,支持半透明,效果好
EasyX 旧版本不支持,依赖特定图片格式
现代场景、使用新版 EasyX(2022+)
3. 颜色键(Color Key)透明
指定一个 “透明色”(如洋红 RGB (255,0,255)),绘制时跳过该颜色的像素
实现简单,比逐像素 Alpha 快
仅支持 “单一透明色”,边缘易有锯齿
简单图形、无渐变透明的场景
光栅技术(经典方案,全透明) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <graphics.h> #include <conio.h> int main () { initgraph (640 , 480 ); IMAGE img (100 , 100 ) , mask (100 , 100 ) ; loadimage (&img, _T("icon.bmp" )); loadimage (&mask, _T("mask.bmp" )); putimage (100 , 100 , &mask, SRCAND); putimage (100 , 100 , &img, SRCPAINT); _getch(); closegraph (); return 0 ; }
颜色键透明(简单替代,指定透明色) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <graphics.h> #include <conio.h> void putimageTransparent (int x, int y, IMAGE* img, COLORREF transColor) { DWORD* pBuffer = GetImageBuffer (img); int width = img->getwidth (); int height = img->getheight (); for (int i = 0 ; i < height; i++) { for (int j = 0 ; j < width; j++) { COLORREF color = pBuffer[i * width + j]; if (color != transColor) { putpixel (x + j, y + i, color); } } } } int main () { initgraph (640 , 480 ); IMAGE img; loadimage (&img, _T("icon.png" )); putimageTransparent (100 , 100 , &img, RGB (255 , 0 , 255 )); _getch(); closegraph (); return 0 ; }
新版 EasyX 加载带 Alpha 的 PNG(推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <graphics.h> #include <conio.h> int main () { initgraph (640 , 480 ); IMAGE img; loadimage (&img, _T("icon_alpha.png" )); putimage (100 , 100 , &img); _getch(); closegraph (); return 0 ; }
补充:常见图片格式的透明支持情况
图片格式
是否支持 Alpha 通道
透明效果
适用场景
PNG
✅ 原生支持
全透明 / 半透明 / 渐变透明
程序贴图、图标、UI 元素、需要透明的图形
JPG
❌ 完全不支持
无法原生透明,模拟效果极差
照片、无透明需求的实景图片
GIF
✅ 仅支持单通道透明(1 位)
仅全透明 / 全不透,无半透明
简单动图、低精度小图标
BMP
❌ 标准 BMP 不支持,扩展 BMP 可手动加 Alpha
支持但无压缩,文件极大
本地临时图片,不推荐工程使用
关键区别:Alpha 通道透明 vs 颜色键 / 光栅透明 很多人会混淆 PNG 的 Alpha 透明和颜色键 / 光栅透明,核心差异如下:
特性
PNG Alpha 通道透明
颜色键 / 光栅透明
透明度支持
0~255 级精细透明(半透明 / 渐变)
仅 0/255 两级(全透 / 全不透)
边缘效果
抗锯齿,边缘自然无锯齿
易出现锯齿、生硬边缘
绘制效率
新版 EasyX 直接putimage,无需额外处理
需手动生成遮罩 / 判断颜色,代码繁琐
图片体积
无损压缩,体积适中
需额外存储遮罩图(如 BMP),体积更大
音乐播放 常规 使用mciSendString函数进行操作,支持wav、mp3等多格式
函数原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 MCIERROR mciSendStringA ( LPCSTR lpstrCommand, LPSTR lpstrReturnString, UINT uReturnLength, HWND hwndCallback ) ;MCIERROR mciSendStringW ( LPCWSTR lpstrCommand, LPWSTR lpstrReturnString, UINT uReturnLength, HWND hwndCallback ) ;#define mciSendString mciSendStringW
函数示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <mmsystem.h> #pragma comment(lib,"winmm.lib" ) using namespace std;void playbkmusic (const char * music,bool isrepeat,int volume) { char cmd[100 ] = { 0 }; sprintf (cmd, "open %s alias bgm" , music); mciSendString (cmd, NULL , 0 , NULL ); mciSendString ("play bgm repeat" , NULL , 0 , NULL ); getchar (); MCIERROR ret = mciSendString ("setaudio bgm volume to 50" , NULL , 0 , NULL ); if (ret != 0 ) { char err[100 ] = { 0 }; mciGetErrorString (ret, err, sizeof (err)); puts (err); } mciSendString ("stop bgm" , NULL , 0 , NULL ); mciSendString ("close bgm" , NULL , 0 , NULL ); }
短音效 PlaySound 是 Windows 系统原生的多媒体 API 函数 ,专门用于播放 WAV 格式的音频文件或系统音效。
1 2 PlaySound ("../assets/bkmusic.wav" , NULL , SND_FILENAME | SND_ASYNC | SND_LOOP);