[CEGUI]. 4、CEGUI 初始化

Par @Martin dans le
Tags :

环境搭建好, Device 也取得后就开始初始化 CEGUI 了.

取 Device 时要注意下, 有的游戏不止创建了一个 Device, 有可能是多个, 所以 HOOK 的时候要先试试用哪个 Device 初始化 CEGUI 可以成功, 例如下面的代码:

HRESULT APIENTRY hkIDirect3D9::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow
    , DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters
    , IDirect3DDevice9 **ppReturnedDeviceInterface) {
    HRESULT hRet = m_pD3Dint->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags
                        , pPresentationParameters, ppReturnedDeviceInterface);
    static int nCount = 0;
    if (SUCCEEDED(hRet)) {
        theApp.m_pDevice = new hkIDirect3DDevice9(ppReturnedDeviceInterface, pPresentationParameters, this);
        martin->add_log("Hooked Direct3D9 device: 0x%x -> 0x%x"
            , ((hkIDirect3DDevice9*)theApp.m_pDevice)->m_pD3Ddev, theApp.m_pDevice);
        if (++nCount == 3) {
            theApp.initGui();
            if (theApp.m_OrgWndProc == 0) {
                theApp.m_hGWnd = hFocusWindow;
                theApp.m_OrgWndProc = ::GetWindowLong(theApp.m_hGWnd, GWL_WNDPROC);
                if (theApp.m_OrgWndProc) {
                    ::SetWindowLong(theApp.m_hGWnd, GWL_WNDPROC, (LONG)&FilterWndProc);
                } else {
                    martin->add_log("GetWindowLong() Failed.");
                    ::ExitProcess(-1);
                }
            }
        }
    }

    return hRet;
}

可以看到, 这里加了个 nCount 计数, 当游戏第三次 CreateDevice() 时才初始化 CEGUI; 后面的代码等下再说, 先看看如何初始化 CEGUI, 也就是 initGui() 函数.

CEGUI::Direct3D9Renderer::bootstrapSystem(theApp.m_pDevice);
//-----------------------------------------------------------
//    此行等同于下面两行, 即初始化 Direct3D9Renderer
//    Direct3D9Renderer *myRenderer = &CEGUI::Direct3D9Renderer::create(pDevice);
//    System::create(*myRenderer);
//-----------------------------------------------------------

/////////////////////////////////////////////////////////////////////////
//    设置默认资源路径
/////////////////////////////////////////////////////////////////////////
CEGUI::DefaultResourceProvider* rp = static_cast<CEGUI::DefaultResourceProvider*>
                    (CEGUI::System::getSingleton().getResourceProvider());
rp->setResourceGroupDirectory("schemes", "E:\\cegui-0.8.4\\datafiles\\schemes");
rp->setResourceGroupDirectory("imagesets", "E:\\cegui-0.8.4\\datafiles\\imagesets");
rp->setResourceGroupDirectory("fonts", "E:\\cegui-0.8.4\\datafiles\\fonts");
rp->setResourceGroupDirectory("layouts", "E:\\cegui-0.8.4\\datafiles\\layouts");
rp->setResourceGroupDirectory("looknfeels", "E:\\cegui-0.8.4\\datafiles\\looknfeel");
rp->setResourceGroupDirectory("lua_scripts", "E:\\cegui-0.8.4\\datafiles\\lua_scripts");
rp->setResourceGroupDirectory("schemas", "E:\\cegui-0.8.4\\datafiles\\xml_schemas");
rp->setResourceGroupDirectory("animations", "E:\\cegui-0.8.4\\datafiles\\animations");
martin->add_log("设置默认资源路径");

///////////////////////////////////////////////////////////////////////////
////    设置使用的缺省资源
///////////////////////////////////////////////////////////////////////////
CEGUI::ImageManager::setImagesetDefaultResourceGroup("imagesets");
CEGUI::Font::setDefaultResourceGroup("fonts");
CEGUI::Scheme::setDefaultResourceGroup("schemes");
CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels");
CEGUI::WindowManager::setDefaultResourceGroup("layouts");
CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts");
CEGUI::AnimationManager::setDefaultResourceGroup("animations");
CEGUI::XMLParser* parser = CEGUI::System::getSingleton().getXMLParser();
if (parser->isPropertyPresent("SchemaDefaultResourceGroup")) {
parser->setProperty("SchemaDefaultResourceGroup", "schemas");
}
martin->add_log("设置使用的缺省资源");

///////////////////////////////////////////////////////////////////////
//    加载方案
///////////////////////////////////////////////////////////////////////
// 加载主题
CEGUI::SchemeManager::getSingleton().createFromFile("TaharezLook.scheme");

// 设置字体
CEGUI::Font& defaultFont = CEGUI::FontManager::getSingleton().createFromFile("DejaVuSans-12.font");
CEGUI::System::getSingleton().getDefaultGUIContext().setDefaultFont(&defaultFont);

// 设置鼠标, hook 游戏 d3d 时不用设置, 直接使用游戏的鼠标模型
//System::getSingleton().getDefaultGUIContext().getMouseCursor().setDefaultImage("TaharezLook/MouseArrow");

// 得到窗口管理器
CEGUI::WindowManager& winMgr = CEGUI::WindowManager::getSingleton();
martin->add_log("加载方案");

/////////////////////////////////////////////////////////////////////////
//    开始绘图
/////////////////////////////////////////////////////////////////////////
// 得到根窗口, 即画布
m_root = static_cast<CEGUI::DefaultWindow*>(winMgr.loadLayoutFromFile("monster.layout"));
CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(m_root);

// 得到主窗口
m_mainWnd = static_cast<CEGUI::FrameWindow*>(m_root->getChild("Demo"));
m_mainWnd->subscribeEvent(CEGUI::FrameWindow::EventCloseClicked,
CEGUI::Event::Subscriber(&CGame::onEventHome, this));


martin->add_log("\n---------------------\n全部加载完毕...\n---------------------\n");

m_root->setVisible(false);
martin->ModuleHide(GetModuleHandle("D3D9_CEGUI.dll"));
m_bInit = true;

代码也没什么好说的, 标准的初始化 CEGUI 代码, 适用版本 8.0+ .

接下来就是要游戏的画面上绘画我们刚初始化好的界面了, 我们只需要 HOOK IDevice 的 Present 虚函数即可:

HRESULT APIENTRY hkIDirect3DDevice9::Present(CONST RECT *pSourceRect, CONST RECT *pDestRect
                , HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion) {
    if (theApp.m_bInit) {
        CEGUI::System::getSingleton().renderAllGUIContexts();
    }

    return m_pD3Ddev->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
}

此时, CEGUI 已经初始化完毕了, 当然前提是你还需要准备 .layout 文件, 启动游戏并注入我们的 DLL 后, 暂时还是看不到我们的画面的, 因为上面的代码中倒数第三行 m_root->setVisible(false); 将窗体隐藏了, 为此, 我们可以 HOOK 游戏的窗口过程(HOOK 的代码就在上面 CreateDevice() 中), 然后给一个快捷键, 例如 HOME, 按下后, 就显示窗体.

case WM_KEYDOWN:
switch (wParam) {
case VK_HOME:
    if (m_bInit) {
    if (m_root->isVisible()) {
        m_root->setVisible(false);
    } else {
        m_root->setVisible(true);
    }
    }
    break;
}
break;
}

但是我们 HOOK 窗口过程可并不只是因为这个需求;

现在显示出来 CEGUI 窗体后, 你会发现, 窗体是静态的, 点击它, 拖动它, 完全不响应, 这是因为现在并没有把鼠标, 键盘等消息传递给 CEGUI, 也就是说, CEGUI 现在并不知道你点击了什么或者你拖动什么等等消息. 我们 HOOK 游戏的窗口过程就是为了把这些消息传递给 CEGUI, 好让 CEGUI 去响应.

if (m_bInit) {
    switch (message) {
    case WM_MOUSEMOVE:
        //ShowCursor(true);
        if (CEGUI::System::getSingleton().getDefaultGUIContext().injectMousePosition((float)(LOWORD(lparam))
    , (float)(HIWORD(lparam)))) {
        return 0;
        }
        break;

    case WM_LBUTTONDOWN:
        if (CEGUI::System::getSingleton().getDefaultGUIContext().injectMouseButtonDown(CEGUI::LeftButton)) {
        return 0;
        }
        break;
    }
}

上面的代码传递了 鼠标移动 + 左键按下 这两个消息, 现在你的窗体上 拖动窗口, 点击按钮 这两个动作是可以被支持的了.