当前位置:首页 > 编程开发 > 正文

C++ 游戏辅助绘制系列(一):透明窗口与点击穿透,把画布架在游戏上面

约 4707 字 · 预计阅读 12 分钟
一句话总结:本系列用 C++ 从零实现一个外部绘制的最小可用框架,第一篇先解决地基问题——用一个置顶的透明分层窗口盖在游戏画面上,直接穿透鼠标操作,再用 Direct2D 在上面画东西,全文代码可以直接编译运行。

这是系列的开工篇。整个系列四篇的路线是:透明窗口(本篇)→ 读内存拿数据 → 世界坐标投影到屏幕(WorldToScreen)→ ImGui 菜单。目标读者是会 C++ 基础语法、想搞明白「游戏里的方框和线条到底是怎么画出来的」的人。

先把丑话说在前面:本系列的演示对象是虚构的《星陨 Online》(进程名 StarFall.exe),所有内存偏移都是编的。练习请用自己的单机程序或专门的教学靶场,比如 Pwn Adventure 这种官方欢迎你倒腾的 open 靶场游戏。在线游戏上玩这套,轻则封号,重则碰法律红线,这不在本系列的讨论范围里。

OveRLay 的原理:其实是三个窗口技巧

游戏辅助绘制本质上是「往游戏画面上叠一层玻璃」。Windows 给了三样工具,拼起来就是全部原理:

  1. 置顶窗口(WS_EX_TOPMOST):让自己的窗口永远压在游戏窗口上面
  2. 分层窗口(WS_EX_LAYERED):支持逐像素透明,画布背景全透明,只显示画出来的线条
  3. 点击穿透(WS_EX_TRANSPARENT):鼠标事件直接穿过去落到游戏窗口,不然你的透明窗口会挡住操作

再加一个无边框样式(WS_POPUP),去掉标题栏,就是一块「透明的玻璃」。

图 1:三层窗口叠出绘制层,点击穿透和数据供给是两条独立通道

环境搭建

Visual Studio 2022 Community,新建「空项目(C++)」,平台工具集默认。需要在链接器里附加:d2d1.lib、dwrite.lib、user32.lib(一般默认已链)。字符集建议直接用 Unicode,省得宽窄字符来回转。

窗口尺寸先写死 1280×720,跟靶场窗口对齐。系列收尾篇会加自动跟窗。

窗口创建:参数一个一个抠

#include <windows.h>
#include <d2d1.h>
#pRAGma comment(lib, "d2d1.lib")

ID2D1Factory*   g_factory   = nullptr;
ID2D1HwndRenderTarget* g_rt  = nullptr;
ID2D1SolidColorBrush*  g_br  = nullptr;

LRESULT CALLBACK WndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
    switch (m) {
    case WM_DESTROY: PostQuitMessage(0); return 0;
    }
    return DefWindowProcW(h, m, w, l);
}

int APIENTRY wWinMain(HINSTANCE hi, HINSTANCE, PWSTR, int show) {
    WNDCLASSW wc = {};
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hi;
    wc.lpszClassName = L"OverlayClass";
    RegisterClassW(&wc);

    HWND hwnd = CreateWindowExW(
        WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT, // 三个扩展样式是核心
        L"OverlayClass", L"overlay",
        WS_POPUP,                                        // 无边框
        100, 100, 1280, 720,
        nullptr, nullptr, hi, nullptr);
    ShowWindow(hwnd, show);

三个扩展样式各司其职:TOPMOST 保置顶,LAYERED 允许透明,TRANSPARENT 让鼠标点击穿透。少任何一个都会出问题——少 TRANSPARENT,你会得到一块点不动的玻璃;少 LAYERED,背景是黑的。

注意 CreateWindowExW 第一个参数才是扩展样式,容易跟第三个参数 dwStyle 搞混,这是新手最常犯的错。

D2D 初始化与渲染循环

Direct2D 是这套方案里最省心的 2D 渲染选择:API 简单、抗锯齿默认开启、不需要懂交换链。

    D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_factory);
    g_factory->CreateHwndRenderTarget(
        D2D1::RenderTargetProperties(),
        D2D1::HwndRenderTargetProperties(hwnd,
            D2D1::SizeU(1280, 720)),
        &g_rt);
    g_rt->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::Red), &g_br);

    MSG msg = {};
    while (msg.message != WM_QUIT) {
        if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg); DispatchMessageW(&msg);
            continue;
        }
        g_rt->BeginDraw();
        g_rt->Clear(D2D1::ColorF(0, 0.0f));   // 全透明清屏
        // ---- 每帧绘制内容,下一篇开始在这里画 ----
        g_rt->DrawRectangle(D2D1::RectF(100, 100, 300, 260), g_br, 2.0f);
        // ----------------------------------------------
        g_rt->EndDraw();
        Sleep(1);
    }
    return 0;
}

编译运行,你会看到屏幕上有个红色方框悬在桌面上,鼠标点它会直接点到下面的窗口——地基就成了。这个红色方框就是将来「透视方框」的雏形,区别只是它画在哪由下一篇的内存数据决定。

三个必踩的坑

DPI 缩放。Windows 缩放不是 100% 时,你的窗口坐标和物理像素对不上,绘制会偏移。最省事的解法是在程序入口加一句 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2),让程序自己管 DPI。

全屏独占模式。这套方案只在窗口化和无边框窗口下有效。游戏切全屏独占时,置顶窗口会被直接压到画面下面。所以实际用的时候,游戏一律开「无边框窗口」模式。这也是为什么市面上所有外部绘制工具都要求无边框。

CPU 占用。渲染循环里 Sleep(1) 只是个占位,实际会让一帧的间隔抖动很大。想省 CPU 可以把渲染帧率限到 60,或者干脆只在数据变化时重绘。系列后面统一改成基于 WaitableTimer 的稳定节拍。

下一篇预告

窗口有了,画笔有了,但画在哪还不知道。下一篇读内存:打开进程、拿模块基址、定义玩家结构体,把「敌人在哪」这个问题变成几个 ReadProcessMemory 调用。到时候这套偏移依然全部是编的,方法是真的。

目录导航
  • Overlay 的原理:其实是三个窗口技巧
  • 环境搭建
  • 窗口创建:参数一个一个抠
  • D2D 初始化与渲染循环
  • 三个必踩的坑
  • 下一篇预告
  • 相关文章:

  • C++ 游戏辅助进阶系列(四):速度预判与边界,给自瞄系列收个尾2026-10-20 00:43:45
  • C++ 游戏辅助进阶系列(三):写视角与骨骼瞄准,把自瞄真正打通2026-10-20 00:43:45
  • C++ 游戏辅助进阶系列(二):目标怎么选,FOV 筛选、权重排序与平滑防抖2026-10-20 00:43:45
  • C++ 游戏辅助进阶系列(一):自瞄的数学地基,从两个坐标算出该转多少度2026-10-20 00:43:45
  • C++ 游戏辅助绘制系列(四):ImGui 菜单与工程收尾,从 demo 到像样的工具2026-10-20 00:43:45
  • C++ 游戏辅助绘制系列(三):WorldToScreen 投影,把三维坐标钉到屏幕上2026-10-20 00:43:45
  • C++ 游戏辅助绘制系列(二):读内存,把「敌人在哪」变成几个函数调用2026-10-20 00:43:45
  • 渗透测试报告 — Z-BlogPHP 1.7.5 本地部署实例2026-10-20 00:43:45
  • 发表评论

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