CD Code Digest Hub /CS:GO

VGUI 与本地化

27 分钟阅读 8 个章节

CS:GO 的 HUD、主菜单与加载画面都用 Scaleform(见 HUD 与 Scaleform UI),但 Source 的 VGUI2 并没有退场:引擎用它搭建所有面板的根层级,开发者控制台、调试与性能面板、工具界面、专用服务器的 Windows 管理窗口都是 VGUI 面板,引擎的输入分发与绘制顺序也要在 VGUI 与 Scaleform 之间仲裁。本地化字符串则由独立的 localize 模块统一提供,VGUI 与 Scaleform 共用同一张表。本页说明这些模块如何装配、各自负责什么,以及本地化文件如何加载与查找。

相关页面:HUD 与 Scaleform UI · 输入与命令 · 专用服务器启动链 · 接口工厂与 AppSystem · 引擎总览

相关源码文件#

  • VGUI2 运行时:vgui2/src/vgui.cpp、vgui2/src/vgui_dll.vpc;控件库 vgui2/vgui_controls/(82 个跟踪文件),代表 vgui2/vgui_controls/consoledialog.cpp
  • 材质系统 surface:vguimatsurface/MatSystemSurface.cpp、vguimatsurface/MatSystemSurface.h、vguimatsurface/Input.cpp
  • 引擎集成:engine/vgui_baseui_interface.cpp、engine/console.cpp、engine/view.cpp
  • client.dll 的 GameUI 与控制台:game/client/cstrike15/gameui/gameui_interface.cpp、game/client/cstrike15/gameui/gameconsole.cpp、game/client/cstrike15/gameui/gameconsoledialog.h
  • 本地化:localize/localize.cpp、localize/localize.vpc、public/localize/ilocalize.h、public/vgui/ILocalize.h、interfaces/interfaces.cpp
  • Scaleform 翻译适配:scaleformui/scaleformuiimpl/scaleformuiintegration.cpp、scaleformui/scaleformuiimpl/scaleformuitranslationimpl.cpp
  • 专用服务器 GUI:dedicated/vgui/vguihelpers.cpp、dedicated/vgui/MainPanel.cpp、dedicated/sys_ded.cpp、dedicated/dedicated.vpc

1. 模块与装配#

模块(产物)职责装配证据
vguimatsurface(DLL)CMatSystemSurface:用材质系统实现 VGUI 的 ISurface(绘制、字体、光标、输入上下文)以 VGUI_SURFACE_INTERFACE_VERSION 与 SCHEME_SURFACE_INTERFACE_VERSION 暴露:vguimatsurface/MatSystemSurface.cpp:159-164;启动器把它排在 vgui2 之前,注释说明是为了替换 vgui2 自带的 surface:launcher/launcher.cpp:764-766
vgui2(vgui_dll 工程)面板树、消息、Scheme、输入状态;自带的 GDI Surface.cpp 只在 Windows 编译vgui2/src/vgui_dll.vpc:9-11、vgui2/src/vgui_dll.vpc:36-59;CVGui 声明依赖 localize 与 surface,并在 Connect 时要求拿到文件系统与本地化接口:vgui2/src/vgui.cpp:1103-1124
localize(DLL)CLocalize:令牌 → 宽字符串表以 LOCALIZE_INTERFACE_VERSION(Localize_001)暴露:localize/localize.cpp:57、localize/localize.cpp:187-190、public/interfaces/interfaces.h:131、localize/localize.vpc:8
vgui_controls(静态库)Panel、Frame、CConsoleDialog 等控件专用服务器工程直接链接:dedicated/dedicated.vpc:125
专用服务器Windows 模块表加载 vgui2.dlldedicated/sys_windows.cpp:278

这些工程都在 csgo_partner 构建组中:vpc_scripts/groups.vgc:86-95。localize.dll 不在启动器的模块表里(launcher/launcher.cpp:727-770),而是作为 vgui2 的 GetDependencies 被 AppSystem 组自动补齐(依赖补齐机制见 接口工厂与 AppSystem)。

两个本地化指针指向同一实现:interfaces 库把 g_pLocalize(::ILocalize)与 g_pVGuiLocalize(vgui::ILocalize)都用 LOCALIZE_INTERFACE_VERSION 查询:interfaces/interfaces.cpp:147-148。vgui::ILocalize 只是为兼容保留的子接口,唯一声明的 FindAsUTF8 在基类中已有同签名纯虚函数,因此两者虚表布局一致(推断):public/vgui/ILocalize.h:15-28、public/localize/ilocalize.h:126。vgui2/src/LocalizedStringTable.cpp 是旧实现,没有被任何 .vpc 引用,且它使用的 VGUI_LOCALIZE_INTERFACE_VERSION 在公共头文件中没有定义:vgui2/src/LocalizedStringTable.cpp:53、vgui2/src/LocalizedStringTable.cpp:180。

2. 引擎内置 VGUI:CEngineVGui#

Host_Init 在材质系统、Scaleform 全屏影片与匹配框架之后、ClientDLL_Init 之前初始化引擎 VGUI:engine/host.cpp:5641-5661。CEngineVGui::Init 的主要步骤:

  1. 从 client.dll 的工厂取得 IGameUI 与(PC 上)IGameConsole;命令行 -gameuidll 时改从独立的 gameui.dll 取:engine/vgui_baseui_interface.cpp:708-747。
  2. 在输入栈上建立 GameUI 与 VGUI 两个输入上下文:engine/vgui_baseui_interface.cpp:749-755。
  3. 加载 Resource/SourceScheme.res(失败即 Sys_Error),启动 ivgui():engine/vgui_baseui_interface.cpp:776-800。
  4. 建立面板层级、引擎面板与本地化文件(下表与第 4 节),再初始化并启动 GameUI、初始化游戏控制台、激活 GameUI;带 -console、-toconsole、-rpt 或 -allowdebug 时直接打开控制台:engine/vgui_baseui_interface.cpp:1024-1059。
面板(自下而上)用途证据
staticPanel根面板,挂在 surface 的嵌入面板上engine/vgui_baseui_interface.cpp:824-827
staticClientDLLPanel 等client.dll 的面板根;默认不接收键盘输入engine/vgui_baseui_interface.cpp:833-847
staticGameUIPanelGameUI 面板根engine/vgui_baseui_interface.cpp:852-857
staticGameDLLPanel / staticEngineToolsPanel工具框架与引擎工具面板根(-tools 时工具面板挂到 GameDLL 面板下)engine/vgui_baseui_interface.cpp:860-878
staticDebugSystemPanel 等PC 上的调试系统、demo UI、雾效 UI、纹理查看、性能工具、调色工具engine/vgui_baseui_interface.cpp:880-914
过渡效果 / 焦点叠加层最上层,焦点叠加层 Z 序 150engine/vgui_baseui_interface.cpp:916-937

面板的 Z 序随 TOOLFRAMEWORK_VGUI_REFACTOR 取不同值:engine/vgui_baseui_interface.cpp:807-822、engine/vgui_baseui_interface.cpp:853-866。PC 上还会在引擎工具面板下创建控制台叠加面板、实体报告、绘制树、纹理列表与 VProf 面板,然后按 scripts/EngineVGuiLayout.res 布局并隐藏 VProf 面板:engine/vgui_baseui_interface.cpp:939-980。其中 CConPanel 负责屏幕左上角的通知文本与 Con_NPrintf 调试区,创建后默认隐藏:engine/console.cpp:118-158、engine/console.cpp:1631-1638。

3. 开发者控制台#

  • toggleconsole 在 con_enable、developer、-console 或 -rpt 之一成立时,调用 EngineVGui()->ShowConsole();有应用级模态面板时不打开:engine/console.cpp:224-242、engine/console.cpp:257-270、engine/console.cpp:1645。
  • ShowConsole/HideConsole 只是转给 IGameConsole 的 Activate/Hide:engine/vgui_baseui_interface.cpp:1415-1438。
  • CS:GO 的 IGameConsole 实现在 client.dll 的 cstrike15/gameui 中:CGameConsole::Initialize 创建 CGameConsoleDialog(派生自 vgui::CConsoleDialog)并放在屏幕右半部:game/client/cstrike15/gameui/gameconsole.cpp:32-71、game/client/cstrike15/gameui/gameconsoledialog.h:24-26。在 CSTRIKE15 下它不挂到 staticGameUIPanel:engine/vgui_baseui_interface.cpp:1031-1040。
  • 控制台对话框打开/关闭时维护 console_window_open,引擎按键分发据此让 ESC 优先关闭控制台:vgui2/vgui_controls/consoledialog.cpp:1298-1318、engine/keys.cpp:1344-1353。

4. 与 Scaleform 的分工#

方面VGUIScaleform证据
内容控制台、调试/性能/工具面板、cstrike15/gameui 下的 VGUI 对话框、专用服务器 GUIHUD、主菜单、开始画面、加载画面等engine/vgui_baseui_interface.cpp:880-956、game/client/cstrike15/Scaleform/loadingscreen_scaleform.cpp:183、game/client/cstrike15/Scaleform/createmainmenuscreen_scaleform.cpp:113
鼠标轴事件先给 VGUI surface其后;vgui_panel_active 为真时跳过engine/sys_mainwind.cpp:424-437、engine/vgui_baseui_interface.cpp:145
手柄轴事件其后先给 Scaleformengine/sys_mainwind.cpp:416-423
按键事件按 Key_Event 优先级链(VGUI 先于 Scaleform,ESC/反引号例外),详见 输入与命令同左engine/keys.cpp:1306-1363
菜单/加载时的绘制V_RenderVGuiOnly_NoSwap 中先画 Scaleform 全屏槽,再画 VGUI PAINT_UIPANELS,最后画 Scaleform 光标同左engine/view.cpp:124-147
游戏中的绘制client.dll 先 VGui_Paint( PAINT_INGAMEPANELS )再按分屏槽设置视口并绘制 Scaleform HUD 槽game/client/viewrender.cpp:3561-3578
绘制开关r_drawvgui 为假、计时 demo 时或 -noshaderapi 时不画 VGUI—engine/vgui_baseui_interface.cpp:2225-2236

VGUI 的输入走 CMatSystemSurface::HandleInputEvent:输入未启用时不处理,否则更新按键状态并交给当前输入上下文:vguimatsurface/MatSystemSurface.cpp:688-699、vguimatsurface/Input.cpp:59。

5. 本地化:ILocalize#

5.1 加载#

CLocalize::AddFile 处理带 %language% 占位符的文件名:localize/localize.cpp:492-581

  1. 拒绝绝对路径:localize/localize.cpp:504-508。
  2. 总是先加载英文文件,保证不缺令牌。
  3. PC 上语言取自 -language 参数,否则读注册表 HKEY_CURRENT_USER\Software\Valve\Steam\Language;值为 unknown 时退回英文。主机平台取系统语言。
  4. 语言不是英文时再加载对应语言文件;后加载的同名令牌覆盖先前的值,并触发变更回调:localize/localize.cpp:881-943。

-all_languages 时改为加载全部语言并保留最宽的字符串(用 surface 作为文字宽度查询器):localize/localize.cpp:226、localize/localize.cpp:521-524、localize/localize.cpp:918-934、vguimatsurface/MatSystemSurface.cpp:398。在 OSX 上,引擎会把 Steam 的当前游戏语言写入上述注册表键:engine/vgui_baseui_interface.cpp:759-766。

解析时,非英文文件中以 [english] 开头的键被跳过,值后面的 [$X360] 一类条件标签会被求值,条件不满足的条目不加入:localize/localize.cpp:371-376、localize/localize.cpp:446-475。

加载者文件证据
引擎 CEngineVGui::Initvalve_%language%、当前游戏的 <game>_%language%(注释说明 CS:GO 把字符串从 valve 文件移到了 csgo_%language%)、当前 mod 的 <mod>_%language%、低暴力版本的 _lv 覆盖文件engine/vgui_baseui_interface.cpp:994-1022
client.dll GameUIgameui_%language%、valve_%language%(GAME 路径,含回退路径)、platform_%language%、vgui_%language%game/client/cstrike15/gameui/gameui_interface.cpp:287、game/client/cstrike15/gameui/gameui_interface.cpp:294、game/client/cstrike15/gameui/gameui_interface.cpp:532-533
通用客户端组件聊天 chat_%language%、隐藏字幕 closecaption_%language%game/client/hud_basechat.cpp:765、game/client/hud_closecaption.cpp:890
专用服务器 GUIplatform_%language%、vgui_%language%、Admin/server_%language%dedicated/vgui/vguihelpers.cpp:77-80

5.2 查找与使用#

  • Find 找不到令牌时返回 NULL;FindSafe 在非 _CERT 构建中返回 #FIXME_LOCALIZATION_FAIL_MISSING_STRING,_CERT 构建中返回空串:localize/localize.cpp:812-838。ConstructString 支持用变参或 KeyValues 变量填充格式串:public/localize/ilocalize.h:113-115、public/localize/ilocalize.h:131-148。
  • Scaleform:Flash 中以 # 开头的文本键经 ScaleformTranslatorAdapter::Translate 转成 UTF-8,交给 ScaleformUIImpl::Translate,后者调用 g_pVGuiLocalize->Find 并把按键字形关键字替换成 HTML:scaleformui/scaleformuiimpl/scaleformuiintegration.cpp:151-169、scaleformui/scaleformuiimpl/scaleformuitranslationimpl.cpp:930、scaleformui/scaleformuiimpl/scaleformuitranslationimpl.cpp:1016-1028。
  • 引擎:资源下载进度等文本直接用 FindSafe/ConstructString 取本地化串:engine/client.cpp:1802、engine/client.cpp:1822。
  • 服务器发往客户端的文本常以 # 令牌形式出现(例如拒绝连接与断开原因 #Valve_Reject_*、#GameUI_Disconnect_TooManyCommands),由客户端本地化后显示(推断,依据是这些字符串的格式):engine/baseserver.cpp:609、engine/sv_client.cpp:1547。

6. 专用服务器的 VGUI 管理界面#

  • 何时启用:Windows 上默认使用 VGUI 界面,带 -console 时改用文本控制台;Linux 只有文本控制台:dedicated/sys_ded.cpp:371-389。
  • 启动:ConsoleStartup 调用 StartVGUI,并在配置界面完成前循环 RunVGUIFrame;用户在配置界面退出时返回失败:dedicated/sys_ded.cpp:284-301。之后服务器循环的每次迭代都会 RunVGUIFrame:dedicated/sys_ded.cpp:187-191。
  • StartVGUI:加入 MAIN/PLATFORM 搜索路径,配置目录优先放在 Steam 安装目录下;创建 CMainPanel 作为 surface 的嵌入面板;加载 Scheme 与三个本地化文件;启动 ivgui();然后加载 AdminServer 模块并查询 IAdminServer 与 VGuiModuleAdminServer001:dedicated/vgui/vguihelpers.cpp:40-100。CMainPanel 构造时创建“Config”页(CCreateMultiplayerGameServerPage):dedicated/vgui/MainPanel.cpp:90。
  • AdminServer 不在仓库中:构建脚本把它指向 Tracker/AdminServer/AdminServer.vpc,而该目录不存在,仓库只保留接口头文件 common/IAdminServer.h、common/IManageServer.h:vpc_scripts/projects.vgc:31-33、dedicated/vgui/MainPanel.cpp:28-31。StartVGUI 对加载失败只打印错误并继续(dedicated/vgui/vguihelpers.cpp:86-94),因此用本仓库构建时管理界面推断只剩配置页。
  • 专用服务器工程编译 dedicated/vgui/ 下的三个源文件并链接 vgui_controls:dedicated/dedicated.vpc:53-56、dedicated/dedicated.vpc:125。

约束与边界#

  • 平台差异:vgui2 自带的 GDI surface 只在 Windows 编译,游戏进程中被 vguimatsurface 取代;Linux/OSX 专用服务器没有 VGUI 界面:vgui2/src/vgui_dll.vpc:50、dedicated/sys_ded.cpp:380-383。
  • 主机平台:控制台的显示/隐藏在主机平台上为空操作,Xbox 360 上 toggleconsole 直接返回:engine/vgui_baseui_interface.cpp:1415-1418、engine/console.cpp:257-260。
  • 失败路径:Scheme 文件缺失、取不到 IGameUI 或(PC 上)IGameConsole 都是致命错误:engine/vgui_baseui_interface.cpp:733-747、engine/vgui_baseui_interface.cpp:776-782。
  • 失效代码:LocalizedStringTable.cpp 未参与构建(见第 1 节)。
  • 本页未展开:vgui_controls 各控件实现、Scheme 与字体光栅化、vgui2/dme_controls、matsys_controls、game_controls 等工具侧控件库、HTML 窗口、serverbrowser 模块、cstrike15/gameui 中各 VGUI 对话框的具体内容,以及 vlocalize 工具。