一句话总结:收尾篇把渲染层换成 DX11 并接入 ImGui,加上菜单交互、配置保存和自动跟窗,再聊聊检测面的科普——四篇合起来,你就有了一套结构完整、可以继续扩展的绘制框架。
前三篇跑通了「窗口 → 数据 → 绘制」的主链路,但程序还没法交互,功能全靠改代码里的常量。收尾篇解决三件事:接入 ImGUI 做菜单、把配置存到文件、让 oveRLay 自动跟着游戏窗口走。末了聊聊检测面,把「为什么只能在靶场玩」讲透。
为什么是 ImGui
ImGui(Dear ImGui)是即时模式 GUI:每帧用代码描述一遍界面,没有控件树、没有消息映射,跟渲染循环天然合拍。游戏行业内的调试工具、引擎编辑器大量用它,周边成熟。对我们更实际的一点是:它自带 DX11 后端,接进 overlay 只要几分钟。
代价是渲染层要换。ImGui 官方没有 D2D 后端,所以上一篇的 D2D 画笔要换成 DX11。绘制逻辑不变——DrawRectangle 变成自己封装的 DrawBox(用顶点缓冲画四个三角形),篇幅原因不贴全套,社区搜 imgui dx11 external overlay 有大量可参考的最小实现。核心骨架:
// 初始化(在 CreateWindowExW 之后)
// 1. D3D11CreateDeviceAndSwapChain 拿 device/swapchain
// 2. swapchain->GetBuffer(0, ...) → CreateRenderTargetView
// 3. ImGui::CreateContext(); ImGui_ImplWin32_Init(hwnd);
// ImGui_ImplDX11_Init(device, ctx);
// 渲染循环
const float clear[4] = { 0, 0, 0, 0 }; // 透明清屏
ctx->OMSetRenderTargets(1, &rtv, nullptr);
ctx->ClearRenderTargetView(rtv, clear);
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
DrawESP(); // 上一篇的方框/血条逻辑原样搬来
if (showMenu) DrawMenu();
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
swapchain->Present(1, 0); // 垂直同步限帧
注意 Present(1, 0) 的第一个参数:垂直同步会把帧率锁到显示器刷新率,CPU 占用立刻从「风扇起飞」变成正常水平,比前三篇的 Sleep(1) 优雅得多。
菜单:几十行代码的事
ImGui 的菜单就是普通函数调用,一个开关联动一个变量:
bool g_boxESP = true, g_hpBar = true, g_snapLines = false;
float g_boxWidth = 0.33f;
float g_boxColor[4] = { 1, 0.2f, 0.2f, 1 };
void DrawMenu() {
ImGui::SetNextWindowsize(ImVec2(360, 300), ImGuiCond_FirstUseEver);
ImGui::Begin("StarFall Assist", nullptr);
ImGui::Checkbox("方框 ESP", &g_boxESP);
ImGui::Checkbox("血条", &g_hpBar);
ImGui::Checkbox("落地线", &g_snapLines);
ImGui::SliderFloat("框宽比例", &g_boxWidth, 0.2f, 0.6f);
ImGui::ColorEdit4("方框颜色", g_boxColor);
if (ImGui::Button("保存配置")) SaveConfig();
ImGui::End();
}
菜单本身想不被点击穿透,DrawMenu 显示期间把窗口的 WS_EX_TRANSPARENT 摘掉、隐藏时加回来,一行 SetWindowLongPtrW 的事。这也是很多人实际用起来「菜单点不动」的原因——穿透样式开着呢。
配置保存不引第三方库,就一个极简写法:
void SaveConfig() {
FILE* f = nullptr;
_wfopen_s(&f, L"config.ini", L"w");
if (!f) return;
fprintf(f, "boxESP=%d\nhpBar=%d\nwidth=%f\n",
g_boxESP, g_hpBar, g_boxWidth);
fclose(f);
}
启动时反着读一遍即可。要上规模可以换 JSON,但四个配置项真用不上。
自动跟窗
前三篇的窗口位置是写死的 100,100,游戏一动就露馅。解法是渲染循环里每帧同步目标窗口位置:
HWND game = FindWindowW(nullptr, L"StarFall");
if (game) {
RECT rc;
GetClientRect(game, &rc);
POINT pt = {};
ClientToScreen(game, &pt);
SetWindowPos(hwnd, HWND_TOPMOST,
pt.x, pt.y,
rc.right - rc.left, rc.bottom - rc.top,
SWP_NOACTIVATE);
}
配合 FindWindowW 失败时的休眠重试,游戏重开也不怕。至此四篇的代码拼起来,就是完整可用的框架。
图 1:四篇拼出来的完整数据流,虚线框是第一篇的 Overlay 窗口边界
聊聊检测面:为什么必须留在靶场
技术分享不聊清这条边界,等于教人踩坑。外部绘制的检测面大致三层:一是进程与句柄层,反作弊会枚举谁打开了自己进程的句柄,OpenProcess(PROCESS_VM_READ) 在有反作弊的游戏里基本等于自首;二是行为层,方框恰好套人的绘制特征、菜单窗口类名,都是指纹;三是内核层,现代反作弊(EAC、BattlEye 这类)驱动级运行,用户态的这套把戏在它面前是透明的。
所以这个系列的价值定位是:理解图形渲染、Windows 窗口体系和跨进程内存访问这三样通用的底层技术。靶场(Pwn Adventure、自建服务器、自己的练习程序)练完,这套能力拿去做调试工具、做数据可视化 overlay、做游戏内嵌 UI 的分析,路都是通的。拿去在线游戏上试,那不是技术问题,是自毁账号外加可能的法律责任,系列开头说过,这里再说一遍。
系列总结
四篇的路线回顾:(一)透明窗口搭玻璃 → (二)读内存拿数据 → (三)WorldToScreen做投影 → 本篇 ImGui 收尾。全系列的偏移都是虚构的,全部代码可以在虚构靶场或 Pwn Adventure 上直接跑。
要继续深入,三个方向值得看:骨骼绘制(读 boneMatrix 做多点连线,数学是本篇 W2S 的重复应用)、内部注入与外部读取的取舍(能力更强但检测面完全不同)、以及 Cheat Engine 的入门(自己找偏移,不再依赖别人的表)。有问题评论区见。

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。