序——写在文章之前
原本,我设定的题目是《手把手叫你创作一个C++格斗游戏》,但最后废弃了。
一是因为作者第一次做这种游戏,没什么经验(之前都是直接在控制台终端用ASCII码来做游戏的),怕教错;
二是因为这样太标题党了,我也只是个小学生,算半个文盲,文笔也不怎么好,“手把手”也是实在叫不上。
所以,我改成了《与你一起创作一个C++格斗游戏》,旨在我们一起去研究,去发现,去进步。
这不是一篇讲座,而是一场读者与作者之间的一场交流会。
请你以这样的心态,去拥抱这一篇文章吧!
前言
我之前的文章里基本讲的都是在终端利用字符和文字来构造一个游戏。但是,那些游戏和真正的游游戏相比,实在差了许多。能不能用C++代码来编一个可以在窗口里加载图片,动画的游戏呢?
是可以的:
第一步:自己组织一个团队(或者说继承别人的公司)
第二步:让别人照你的做出一个游戏,然后躺平
第三步:开玩
听起来很简单!
(什么?你做不到?)
emmm....如果你没颜值没身份没朋友没资产没地位没胆抢银行没好的工作并且所有剩余的时间都在刷抖音,做到上面的要求是有一点困难......
看来只能自己做游戏了!
目录
1:编译配置
正文
说了这么多废话,也该开启主题了。
本文讲述的是对于Easyx库的安装以及简单的页面创造。
工具准备
我们要想创作一个游戏,必须准备一些工具,就像画画必须准备笔一样。
1:编译配置
2:Easyx库
如果你已经安装好了,请跳过
这个神奇的工具想必大家都听说过,但是孤陋寡闻的me之前并不知道,要不是问了Deepseek,我还在如何注册以及创建窗口和WinMain函数的迷宫里晕头转向。
这是一段实现了一个基础的Windows桌面应用程序框架,用于创建一个具有标准界面元素的可视化窗口的代码,自己看吧......
不用认真看完,就是想告诉大家这样有多难。
#include <windows.h>
/* This is where all the input to the window goes to */
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message) {
/* Upon destruction, tell the main thread to stop */
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
/* All other messages (a lot of them) are processed using default procedures */
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
/* The 'main' function of Win32 GUI programs: this is where execution starts */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX wc; /* A properties struct of our window */
HWND hwnd; /* A 'HANDLE', hence the H, or a pointer to our window */
MSG msg; /* A temporary location for all messages */
/* zero out the struct and set the stuff we want to modify */
memset(&wc,0,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc; /* This is where we will send messages to */
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
/* White, COLOR_WINDOW is just a #define for a system color, try Ctrl+Clicking it */
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "WindowClass";
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* Load a standard icon */
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); /* use the name "A" to use the project icon */
if(!RegisterClassEx(&wc)) {
MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","Caption",WS_VISIBLE|WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, /* x */
CW_USEDEFAULT, /* y */
640, /* width */
480, /* height */
NULL,NULL,hInstance,NULL);
if(hwnd == NULL) {
MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
/*
This is the heart of our program where all input is processed and
sent to WndProc. Note that GetMessage blocks code flow until it receives something, so
this loop will not produce unreasonably high CPU usage
*/
while(GetMessage(&msg, NULL, 0, 0) > 0) { /* If no error is received... */
TranslateMessage(&msg); /* Translate key codes to chars if present */
DispatchMessage(&msg); /* Send it to WndProc */
}
return msg.wParam;
}
实际上,要想完成我们的任务,并不需要这么复杂。
这就要提到我们大名鼎鼎的Easyx库了!
什么是EasyX?
EasyX 是一款专为 C/C++ 初学者 设计的轻量级图形库,主要面向 Windows 平台开发。它封装了底层复杂的图形 API(如 GDI),提供直观的绘图函数,让开发者无需深入掌握图形学或 Win32 编程,即可快速实现图形界面、小游戏和可视化应用。
为何选择EasyX?
-
零学习成本:语法简单,与C++标准库无缝衔接,适合小项目。
-
轻量高效:无需安装复杂环境,仅需几MB的头文件和库。
-
即时反馈:所见即所得的绘图效果,美观又便捷。
-
Windows友好:完美兼容VC和Dev-C++等IDE,编译即用。
Easyx的安装:
官方教程(支持 Visual C++ 6.0 / 2008 ~ 2022,但好像不支持Devc++)
1.访问 EasyX 官网 下载适用于 MinGW 的版本(如 easyx4mingw_20240601
)。
选择文件下载位置,等待下载完成。
2.右键点击压缩包,点全部解压缩。
然后找到easyx4mingw_20240601文件夹,点进去。
找到里面的include文件夹,将里面的头文件全部复制。
(就是这俩)
3.找到MinGW 的头文件和库文件文件夹。
如果你用的是Dev-C++,那么默认的文件路径是C:\Program Files (x86)\Dev-Cpp\MinGW64\include
如果是VS,那应该是C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\<version>\include (<version>是你下载的VS版本)
注意:
检查是否安装了 C++ 工作负载
-
打开 Visual Studio Installer。
-
点击你的 VS 版本右侧的 修改。
-
确保勾选了 使用 C++ 的桌面开发(必须安装此组件才会有 MSVC 编译器目录)。
将Easyx的两个头文件放入include文件夹。
然后返回Easyx文件夹,将lib32/lib64中的一个文件夹(取决于编译器的配置,如果是Devc++5.4.0就是lib-for-devcpp_5.4.0)里的libeasyx.a文件复制到你所使用的编译器的lib文件夹内。
接着,如果你用的是Devc++:
-
新建项目
打开 Dev-C++ → 菜单栏选择 文件 → 新建 → 项目 → 空项目,设置项目名称(如MyGame
)。 -
设置链接器参数
右键项目 → 项目属性 → 参数 → 链接器 → 在输入框中添加-leasyx
。
如果是VS:
(1) 创建/打开项目
-
新建一个 空项目 或打开现有项目,确保项目类型为 控制台应用程序(避免
WinMain
入口错误)。
(2) 配置包含目录和库目录
-
右键项目 → 属性 → VC++ 目录。
-
添加以下路径:
-
包含目录:确保包含
graphics.h
的路径(如果已复制到默认目录可跳过)。 -
库目录:指向
easyx.lib
所在目录(如...\lib\x86\
)。
-
(3) 链接器配置
-
进入 属性 → 链接器 → 输入 → 附加依赖项。
-
添加
easyx.lib
。
做到这一步后,你的Easyx库就安装好了。
可以复制以下代码运行一下,能运行说明安好了。
#include <graphics.h>
int main() {
initgraph(640, 480);
setbkcolor(WHITE);
cleardevice();
outtextxy(200, 200, "EasyX 加载完成!");
getchar();
closegraph();
return 0;
}
知识储备
Easyx有自己的绘图,文字和图像函数,需要自己查(我就是这么过来的)。这里简单分享一下几个函数:
-
基础绘图
支持绘制点、线、矩形、圆等几何图形,轻松构建2D图形界面。circle(300, 200, 100); // 画一个圆
-
文字显示
内置字体设置功能,支持自定义字号、字体和颜色。settextcolor(RED); outtextxy(100, 100, "Hello EasyX!");
-
图像处理
加载常见格式(BMP/PNG/JPG)的图片,支持透明通道和缩放操作。IMAGE img; loadimage(&img, "image.png"); putimage(0, 0, &img);
-
交互支持
简化鼠标和键盘事件处理,轻松实现点击、移动等交互逻辑。
一个简单的Easyx程序是这样的格式:
#include <graphics.h> // 引入EasyX头文件
int main() {
initgraph(640, 480); // 创建窗口
setbkcolor(WHITE); // 设置背景色
cleardevice(); // 清屏
settextcolor(BLUE);
outtextxy(200, 200, "欢迎使用EasyX!"); // 显示文字
setfillcolor(RED);
fillcircle(320, 240, 50); // 绘制红色实心圆
getch(); // 按任意键退出
closegraph(); // 关闭窗口
return 0;
}
能把这些记记,就差不多可以了。
一些详细的介绍可以去看那些官方文档。
资源推荐
-
官网文档:https://easyx.cn
-
社区教程:EasyX实战案例合集
实战:做一个游戏开场首页
第一步:创建页面
有了Easyx,我们就不用费劲和windows.h打交道了。
删啦!
好吧,还是要用Sleep函数。
当然代码量就减少了很多。
不需要复杂的WinMain,只需要一个initgraph,就可以创建一个Easyx窗口。
关闭也很简单,一个closegragh解决。
#include <graphics.h>
#include <windows.h>
#include <conio.h>
#include <string>
#include <fstream>
#include <cstring>
#include <iostream>
#include <vector>
#include <iomanip>
#include <cstdio>
#include <algorithm>
#include <stdexcept>
#include <exception>
using namespace std;
int main() {
cout<<"创建页面中......"<<endl;
initgraph(800, 600);//创建一个长800像素,宽600像素的Easyx窗口
cout<<"成功创建页面."<<endl;
getch();//阻塞式输入,防止窗口一闪而过
closegraph();//关闭窗口
return 0;
}
第二步:制作背景和标题
1. 加载背景图
// 加载背景图(建议把图片放在项目目录下,否则你会体验满世界找文件的快乐)
IMAGE img_bg; //新建一个图片变量,用来存图片
if (loadimage(&img_bg, "bg_home.png") != 0) { //把图片加载到img_bg里,如果返回非0,说明加载失败; bg_home.png是你已经准备好的图片名称
MessageBox(GetHWnd(), "背景图离家出走了!", "错误", MB_ICONERROR);
closegraph();
return -1;
}
putimage(0, 0, &img_bg); // 把图片贴到窗口左上角(前两个变量表示图片左上角坐标)
避坑指南
-
图片格式推荐
.png
(要想支持支持透明背景或其他图片格式,需要装Easyx插件,这里暂且不说) -
如果显示黑屏,请检查:
-
文件名是否拼对(包括大小写)
-
图片路径是否正确(建议直接丢项目文件夹)
-
2. 绘制标题
// 设置字体
settextstyle(100, 0, "华文行楷");
/*当然,这字体也可以填别的,
只要你的C:\Windows\Fonts文件夹里有就行,
也可以下载字体放到C:\Windows\Fonts里,再调用*/
settextcolor(WHITE);//文字颜色,这里调成白色
setbkmode(TRANSPARENT); // 设置文字背景透明(不然会有丑丑的色块)
outtextxy(170, 60, "小时格斗"); //绘制文字 坐标自己调,歪了别怪我
效果升级
想给文字加阴影?复制粘贴大法!
settextcolor(BLACK);
outtextxy(173, 63, "小时格斗"); // 稍微偏移一点
settextcolor(WHITE);
outtextxy(170, 60, "小时格斗"); // 原位置再画一次
第三步:制作按钮
1. 绘制按钮:从方块到灵魂
Easyx里有没有现成的按钮函数呢?很抱歉,没有。
没有咱可以自己造嘛!
先造一个Button结构体。
// 按钮结构体(建议全局定义方便调戏)
struct Button {
int x, y, width, height;//按钮的左上角坐标,宽度,高度
bool isHover; // 鼠标悬停状态
bool isClicked; // 是否被点击
};
为什么要有isHover呢?因为在绘制按钮时,我们要实现在悬浮时变色的功能。
// 画按钮函数(RGB颜色自己替换成喜欢的)
void drawButton(const Button& btn, const char* text) {
// 根据状态变色
if (btn.isHover) {
setfillcolor(RGB(221, 221, 221)); // 悬停时灰色
} else {
setfillcolor(WHITE); // 默认白色
}
// 画按钮本体
fillrectangle(btn.x, btn.y, btn.x + btn.width, btn.y + btn.height);
// 画边框和文字
setlinecolor(BLACK);
rectangle(btn.x, btn.y, btn.x + btn.width, btn.y + btn.height);
settextcolor(BLACK);
outtextxy(btn.x + 20, btn.y + 10, text);
}
那怎么判断鼠标位置呢?
2. 让按钮活过来:鼠标交互检测
Button startBtn = {300, 400, 200, 60, false, false}; // 按钮位置尺寸
// 在游戏主循环中添加消息循环:
ExMessage msg;//消息变量
while (true) {
// 处理鼠标事件
if (peekmessage(&msg, EM_MOUSE)) {
// 检测悬停
startBtn.isHover =
(msg.x >= startBtn.x && msg.x <= startBtn.x + startBtn.width) &&
(msg.y >= startBtn.y && msg.y <= startBtn.y + startBtn.height);
// 检测点击
if (msg.message == WM_LBUTTONDOWN && startBtn.isHover) {
startBtn.isClicked = true;
// 这里可以跳转到游戏场景!(我会在下一章和大家分享)
}
}
// 绘制三连(顺序不能错!)
cleardevice();
putimage(0, 0, &img_bg); // 先画背景
outtextxy(170, 60, "小时格斗"); // 再画标题
drawButton(startBtn, "开始游戏"); // 最后画按钮(避免被覆盖)
Sleep(10); // 防止CPU飙车
}
但是,当我们打好代码运行后,发现画面闪到不能行,特难看!
这时候,就需要动用新的手断了 —— 双缓冲防闪烁:
在 initgraph
后立即添加:
BeginBatchDraw(); // 开启双缓冲,接下来的所有绘制都暂时不显示,直到出现FlushBatchDraw()或EndBatchDraw()
在循环末尾添加:
FlushBatchDraw(); // 将之前未显示的绘制同时显现
这样就行了。
这是我的项目代码,可以参考一下
#include <graphics.h>
#include <windows.h>
#include <conio.h>
#include <string>
#include <fstream>
#include <cstring>
#include <iostream>
#include <vector>
#include <iomanip>
#include <cstdio>
#include <algorithm>
#include <stdexcept>
#include <exception>
using namespace std;
#define version "0.0.0"
IMAGE homeimg;
struct Button {
int x, y;
int width;
int height;
bool isHover;
bool willdraw;
bool canClicked;
}btn = {170, 400, 300, 60, false, true,true};
vector<Button> btns;
void drawButton(const Button& btn, const char* text) {
if(!btn.willdraw){
return ;
}
if (btn.isHover) {
setfillcolor(0xDDDDDD);
} else {
setfillcolor(WHITE);
}
fillrectangle(btn.x, btn.y, btn.x + btn.width, btn.y + btn.height);
setlinecolor(BLACK);
rectangle(btn.x, btn.y, btn.x + btn.width, btn.y + btn.height);
settextcolor(BLACK);
outtextxy(btn.x + 20, btn.y + 10, text);
}
bool isMouseInButton(int mouseX, int mouseY, const Button& btn) {
return (mouseX >= btn.x && mouseX <= btn.x + btn.width &&
mouseY >= btn.y && mouseY <= btn.y + btn.height);
}
void HOME(){
cout<<"目前页面:首页"<<endl;
ExMessage msg;
while (true) {
cleardevice();
// 绘制背景图
putimage(0, 0, &homeimg);
// 绘制标题
settextstyle(100, 0, "华文行楷");
settextcolor(WHITE);
outtextxy(170, 60, "小时格斗");
// 处理鼠标事件
if (peekmessage(&msg, EM_MOUSE)) {
if (msg.message == WM_MOUSEMOVE) {
btn.isHover = isMouseInButton(msg.x, msg.y, btn);
} else if (msg.message == WM_LBUTTONDOWN) {
if (isMouseInButton(msg.x, msg.y, btn)) {
//游戏开始!这就是下一章讨论的内容
}
}
}
// 绘制按钮
settextstyle(16, 0, "宋体"); // 按钮文字样式
drawButton(btn, "开始游戏");
FlushBatchDraw();
Sleep(5);
}
}
int main() {
cout<<"创建页面中......"<<endl;
initgraph(800, 600);
cout<<"成功创建页面."<<endl;
// ==== 新增:启用双缓冲 ====
cout<<"启用双缓冲中......"<<endl;
BeginBatchDraw();
cout<<"成功启用双缓冲."<<endl;
cout<<"设置字体透明度中......"<<endl;
setbkmode(TRANSPARENT);
cout<<"成功设置字体."<<endl;
cout<<"加载图片中......"<<endl;
loadimage(&homeimg, "Shi+home.png");
cout<<"成功加载图片."<<endl;
HOME();//首页
EndBatchDraw();
closegraph();
return 0;
}
后记
通过这场"艰苦卓绝"的战斗(其实也就写了百来行代码),我们:
-
成功摆脱了控制台的黑框诅咒
-
用EasyX召唤出了游戏窗口
-
学会了加载图片和装X字体
-
做出了会变色的按钮
虽然目前的游戏开场页比北极还空旷,但——
这就是我们征服图形编程的第一步!
接下来可以:
-
给按钮添加点击音效(建议选用"咕呱"这种魔性音效)
-
加入中二度爆棚的过场动画
-
其他优化方案
最后温馨提示:
"你的游戏完成度,与你的掉发量成正比。
但至少现在——我们已经有了一扇通向新世界的大门!"
(至于门后是BUG的海洋还是创意的乐园,就看诸君的造化了✧(≖ ◡ ≖✿))
(求点赞,关注)