Easyx Learning

官方文档链接

EasyX 文档 - 基本说明

文本绘制

基础

需考虑的问题主要有:

  1. 项目字符集问题(解决方案见下文)
  2. 背景框透明
  3. 数字需转为字符串,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()
{
//EasyX与字符串相关的函数,都有字符集问题
//三种解决方法
//1.在字符串前加L进行强制转换,如 L"hello world"
//2.用带参宏 _T(),_TEXT() 把字符串包裹起来进行自适应转换,(推荐使用)
//3.菜单栏->项目->属性->高级->字符集->把unicode改为多字节(大力推荐,但仅针对当前项目有效)
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) {
// 1. 按键按下:记录状态
if (msg.message == WM_KEYDOWN) {
g_isKeyDown[msg.vkcode] = true;

// ---- 一次性按键响应(按下即触发,如确认、退出)----
switch (msg.vkcode) {
case VK_ESC: // ESC键:退出程序
closegraph();
exit(0);
break;
case VK_RETURN: // Enter键:确认/触发事件
outtextxy(100, 50, _T("按下了Enter键(确认)"));
break;
case VK_SPACE: // 空格键:暂停/继续
outtextxy(100, 80, _T("按下了空格键(暂停/继续)"));
break;
case 'A': case 'a': // 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;
// 可扩展:添加其他按键(如 VK_SHIFT/VK_CONTROL 组合键)
}
}

// 2. 按键松开:重置状态
if (msg.message == WM_KEYUP) {
g_isKeyDown[msg.vkcode] = false;
}

// ---- 组合键处理(如 Ctrl+C)----
if (msg.message == WM_KEYDOWN) {
// 判断 Ctrl + C
if (msg.vkcode == 'C' && (GetKeyState(VK_CONTROL) & 0x8000)) {
outtextxy(100, 260, _T("按下了 Ctrl+C 组合键"));
}
// 判断 Shift + 上方向键
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 → 按键是松开的。

帧率控制

核心要点:

  1. 为什么不用sleep函数,因为普通sleep精度低
  2. 双缓冲机制防闪屏
  3. 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;//1秒/60帧=16.6 每一帧应该花费的时间

int startTime = 0;
int freamTime = 0;

int score = 0;
char str[50] = "";

while (true)
{
startTime = clock();//50ms

sprintf_s(str, "Score:%d", score++);
settextcolor(BLACK);

//双缓冲防止闪屏
BeginBatchDraw();
cleardevice();
outtextxy(20, 20, str);
EndBatchDraw();

outtextxy(20, 20, "");

freamTime = clock() - startTime;//56-50=6ms
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");


//cleardevice();
//system("pause");

//loadimage(&img, _T("..\\assets\\cat.jpg"), 400, 500);
//putimage(0, 0, &img);

//想要实现透明贴图,需要另备掩码图
}
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;//每隔500毫秒切换一次图片

while (true)
{
frameStart = clock();
//双缓冲绘图
BeginBatchDraw();
cleardevice();
int w = (*(img + index)).getwidth() * index;
putimage((*(img + index)).getwidth() * index, 0, img + index);
EndBatchDraw();

//index=(index+1)%4;

//计算当前应该显示第几帧
index = (clock() / speed) % frames;

//控制帧率
frameTime = clock() - frameStart;

if (frameDelay > frameTime)
{
Sleep(frameDelay - frameTime);
}
}

透明贴图实现

​ EasyX 用光栅技术处理透明背景

光栅操作(Raster Operations,简称 ROP) 本质是 Windows GDI 提供的像素级位运算指令,能直接在显存 / 内存中对两块像素区域做合并、遮罩、透明等操作。EasyX 依赖 GDI,所以优先用这种原生方案,核心原因有 3 点:

  1. 底层依赖决定:基于 Windows GDI 实现

    EasyX 不是独立的图形引擎,而是对 Windows GDI 的轻量封装。GDI 本身没有 “一键透明” 的高级接口,而光栅操作(如 BitBlt 函数的 ROP 码) 是 GDI 中唯一能高效实现像素级透明合成的原生方式,EasyX 自然沿用这套机制。

  2. 效率优势:硬件 / 系统级加速

    光栅操作由 Windows 系统内核或显卡驱动直接处理,属于 “底层硬件加速”,比纯软件遍历每个像素(如逐点判断 Alpha 值)快得多。比如 EasyX 中常用的 putimage 结合 SRCAND/SRCPAINT 等 ROP 码实现透明,本质是调用 GDI 的 BitBlt 光栅指令,避免了 CPU 逐像素计算的开销。

  3. 适配性:兼容旧版 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")); // 遮罩图

// 用光栅操作实现透明绘制(SRCAND 遮罩 + SRCPAINT 原图)
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")); // 图片中洋红(RGB(255,0,255))为透明色

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;
// 新版 EasyX 直接加载带 Alpha 通道的 PNG,自动处理透明
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
// ANSI 版本(处理 char 字符串)
MCIERROR mciSendStringA(
LPCSTR lpstrCommand, // MCI 指令字符串
LPSTR lpstrReturnString, // 接收返回结果的缓冲区
UINT uReturnLength, // 缓冲区长度
HWND hwndCallback // 回调窗口句柄(一般设为 NULL)
);

// Unicode 版本(处理 wchar_t 字符串)
MCIERROR mciSendStringW(
LPCWSTR lpstrCommand,
LPWSTR lpstrReturnString,
UINT uReturnLength,
HWND hwndCallback
);

// 宏定义(根据项目字符集自动切换 A/W 版本)
#define mciSendString mciSendStringW // 默认为 Unicode

函数示例:

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;

//播放背景音乐函数,支持wav、mp3等多格式
void playbkmusic(const char* music,bool isrepeat,int volume)
{
char cmd[100] = { 0 };
sprintf(cmd, "open %s alias bgm", music);//此处bgm是自定义的音乐别名,可以随意取名,但不能和系统关键字冲突
//多音乐播放时,可以用不同别名区分不同音乐文件


//播放音乐 m-media媒体/c-control控制/i-interface接口
mciSendString(cmd, NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);//repeat表示循环播放,否则播放一次后停止,可以不加
getchar();

//如果失败返回非0值,成功返回0,可以不用返回值直接用函数,其他的函数返回值操作也是类似
MCIERROR ret = mciSendString("setaudio bgm volume to 50", NULL, 0, NULL);//设置音量 0-1000,默认500
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);

//音乐播放完毕后,系统会自动关闭文件
//如果不关闭文件,反复播放会报错

//无法播放音乐可能原因:
//1.路径错误
//2.系统驱动不支持
//3.音乐格式错误,网易云下载的音乐一般是加密的,无法播放

}

短音效

PlaySoundWindows 系统原生的多媒体 API 函数,专门用于播放 WAV 格式的音频文件或系统音效。

1
2
//只能播放wave格式的音乐文件
PlaySound("../assets/bkmusic.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);