|
|
(13 intermediate revisions by 6 users not shown) |
Line 1: |
Line 1: |
| {{otherlang2 | | {{Machine Translation}} |
| |es=VGUI_Documentation:es | | {{LanguageBar|title=VGUI文档}} |
| |ru=VGUI_Documentation:ru
| | |
| }} | |
| {{toc-right}} | | {{toc-right}} |
|
| |
|
| '''[[:Category:VGUI|VGUI]]''' 是VALVE专有的 [[Wikipedia:Graphical User Interface|图形用户界面GUI]]. 所有起源游戏和steam都使用vgui绘制窗口,提示以及菜单。 它同样处理 [[#Localization|本地化]]: 根据用户的语言而显示相应的语言内容。 | | '''[[:Category:VGUI|VGUI]]'''是Valve专有的{{L|Wikipedia:Graphical User Interface|图形用户界面}}系统。所有或大多数{{src|2}}游戏以及早期的{{steamicon|2}}都使用VGUI来绘制窗口、对话框和菜单。它还处理{{↓|Localization|本地化}}功能:以用户首选语言显示文本。 |
|
| |
|
| * The object architecture is hierarchical and all implemented elements derive from VGUI base classes. | | * 对象架构采用层级结构,所有实现的元素均派生自VGUI基类。 |
| * The keyboard/mouse input system is event-driven and quite similar to other GUI systems. | | * 键盘/鼠标输入系统是事件驱动的,与其他GUI系统非常相似。 |
| * Implementations for the most common GUI elements like buttons, text fields or images are provided by the VGUI controls library (<code>vgui_controls.lib</code>). | | * 最常见GUI元素(如按钮、文本字段或图像)的实现由VGUI控件库提供(<code>vgui_controls.lib</code>)。 |
| * Base interface headers are located in <code>\public\vgui\</code> | | * 基础接口头文件位于<code>\public\vgui\</code> |
| * Control elements are defined in <code>\public\vgui_controls\</code>. | | * 控件元素定义在<code>\public\vgui_controls\</code>。 |
|
| |
|
| As a mod author you will most likely use VGUI in the client.dll project to show menus, HUD elements or for in-game displays (on weapons or computer terminals etc).
| | 作为模组作者,您最可能通过client.dll项目使用VGUI来显示菜单、HUD元素或游戏内显示(如武器或计算机终端等)。 |
| 作为一个mod作者,你将会经常使用client.dll项目中的vgui方案来显示菜单,HUD元素或者in-game显示物品(如武器或者电脑终端等)。
| |
| == Panels and hierarchy ==
| |
|
| |
|
| The base class all VGUI elements derive from is <code>vgui::Panel</code>, which defines an area on your screen that has a specific size and position, can draw itself and process input events. Dialog windows, text fields and buttons are all VGUI panels in a hierarchical parent-child relationship. The very first panel is the root panel and is provided by the Source engine. The client root panel covers the whole screen, but doesn't show anything. Even though you could use the client root panel, most client panels use the shared <code>BaseViewport</code> panel as parent ( <code>g_pClientMode->GetViewport()</code> ).
| | == 面板与层级结构 == |
|
| |
|
| This diagram shows the hierarchy of VGUI panels in Counter-Strike:
| | 所有VGUI元素的基类均为<code>vgui::Panel</code>,它定义了屏幕上具有特定大小和位置的区域,可以自行绘制并处理输入事件。对话框窗口、文本字段和按钮都是具有父子层级关系的VGUI面板。最顶层的根面板由Source引擎提供。客户端根面板覆盖整个屏幕,但不可见。尽管可以直接使用客户端根面板,但大多数客户端面板使用共享的<code>BaseViewport</code>面板作为父级(<code>g_pClientMode->GetViewport()</code>)。 |
|
| |
|
| [[File:Vgui hierarchy.png|center|link=|VGUI client panel hierarchy]]
| | 下图展示了《反恐精英》中的VGUI面板层级结构: |
|
| |
|
| You can inspect your mod's panel hierarchy with the '''VGUI Hierarchy tool'''. Open it by submitting <code>vgui_drawtree 1</code> to the [[developer console]].
| | [[File:Vgui hierarchy.png|center|link=|VGUI客户端面板层级]] |
|
| |
|
| [[File:Vgui drawtree.png|right|VGUI Hierarchy tool]]
| | 您可以使用'''VGUI层级工具'''查看模组的面板结构。通过在{{L|developer console|开发者控制台}}输入<code>vgui_drawtree 1</code>来开启该工具。 |
|
| |
|
| Panel names are color-coded:
| | [[File:Vgui drawtree.png|right|VGUI层级工具]] |
|
| |
|
| * <span style="padding:0 .4em;background:#23221D;color:white;">Visible</span>
| | 面板名称采用颜色编码: |
| * <span style="padding:0 .4em;background:#23221D;color:grey;">Hidden</span>
| |
| * <span style="padding:0 .4em;background:#23221D;color:yellow;">Popup panel (Frame)</span>
| |
| * <span style="padding:0 .4em;background:#23221D;color:limegreen;">Has focus</span>
| |
|
| |
|
| And the following options are available as checkboxes:
| | * <span style="padding:0 .4em;background:#23221D;color:white;">可见</span> |
| | * <span style="padding:0 .4em;background:#23221D;color:grey;">隐藏</span> |
| | * <span style="padding:0 .4em;background:#23221D;color:yellow;">弹出面板(框架)</span> |
| | * <span style="padding:0 .4em;background:#23221D;color:limegreen;">具有焦点</span> |
|
| |
|
| ; Show Visible
| | 工具提供以下复选框选项: |
| : List all visible panels
| |
| ; Show Hidden
| |
| : List all hidden panels
| |
| ; Popups only
| |
| : List only Popup panels (Frames)
| |
| ; Highlight Mouse Over
| |
| : Panels are highlighted with a colored border when you move the mouse cursor over. The panel tree will be expanded to show the current panel.
| |
| ; Freeze
| |
| : Locks current tree view
| |
| ; Show Addresses
| |
| : Shows panel memory address
| |
| ; Show Alpha
| |
| : Show panel alpha value, 0 = translucent, 255 = opaque
| |
| ; In Render Order
| |
| : Sort panels in tree by rendering order
| |
|
| |
|
| == The Panel class==
| | ; 显示可见 |
| | : 列出所有可见面板 |
| | ; 显示隐藏 |
| | : 列出所有隐藏面板 |
| | ; 仅弹窗 |
| | : 仅列出弹出面板(框架) |
| | ; 高亮鼠标悬停 |
| | : 当鼠标悬停时用彩色边框高亮面板,并展开树状图显示当前面板 |
| | ; 冻结 |
| | : 锁定当前树状视图 |
| | ; 显示地址 |
| | : 显示面板内存地址 |
| | ; 显示透明度 |
| | : 显示面板透明度值(0为半透明,255为不透明) |
| | ; 按渲染顺序 |
| | : 按渲染顺序排列面板 |
|
| |
|
| The VGUI base class <code>Panel</code> is derived from by over 50 different control elements defined in <code>\public\vgui_controls\</code>. The header files usually provide a good documentation about the control features and behaviors. The following diagram shows the class hierarchy for some common used elements:
| | == Panel类 == |
| | |
| | VGUI基类<code>Panel</code>派生出<code>\public\vgui_controls\</code>中定义的50多种控件。头文件通常详细说明了控件的特性与行为。下表展示常用元素的类层级: |
|
| |
|
| <center> | | <center> |
Line 76: |
Line 75: |
| |} | | |} |
| </center> | | </center> |
| Since all VGUI classes derive from class Panel, we should take a closer look at the essential member functions and properties.
| |
|
| |
|
| The most commonly used constructor to create a panel is <code>Panel(Panel* parent, const char* panelName)</code>, since each panel needs a parent panel it belongs to and a unique name in its hierarchy level. Once a panel is created you can make it visible or hide it again with <code>SetVisible(bool state)</code>.
| | 由于所有VGUI类都派生自Panel类,我们需要深入了解其核心成员函数和属性。 |
|
| |
|
| VGUI keeps a handle for each new panel, which is used to globally address a panel without passing pointers. Lots of event messaging functions use these <code>VPANEL</code> handles to identify source and target panels. To get the unique <code>VPANEL</code> handle of a panel call <code>GetVPanel()</code>.
| | 最常用的面板构造函数是<code>Panel(Panel* parent, const char* panelName)</code>,因为每个面板都需要指定父面板和同级唯一的名称。创建面板后,可通过<code>SetVisible(bool state)</code>控制可见性。 |
|
| |
|
| To set a panel's position and size use the <code>SetPos(int x,int y)</code> and <code>SetSize(int wide,int tall)</code> member functions. The size is always in pixels and doesn't scale automatically with increasing screen resolutions. The position is always relative to the given parent panel, where (0,0) is the upper left corner.
| | VGUI为每个新面板维护一个句柄,用于全局寻址而无需传递指针。许多事件消息函数使用这些<code>VPANEL</code>句柄标识源和目标面板。通过<code>GetVPanel()</code>可获取面板的<code>VPANEL</code>句柄。 |
|
| |
|
| To iterate through the panel hierarchy using a certain panel as starting point, you get the parent panel by calling <code>GetParent()</code> or <code>GetVParent()</code> to get the corresponding <code>VPANEL</code> handle. Of course a panel can have only one parent panel, but multiple child panels (sub panels, buttons, list elements etc). <code>GetChildCount()</code> will return the total number of children, where each child can be queried with <code>GetChild(int index)</code>.
| | 使用<code>SetPos(int x,int y)</code>和<code>SetSize(int wide,int tall)</code>设置面板位置和尺寸。尺寸以像素为单位,不会随屏幕分辨率自动缩放。位置始终相对于父面板的左上角(0,0)。 |
|
| |
|
| If a panel should react to input events like key pressed or mouse click, the virtual <code>OnEvent()</code> functions like <code>OnMousePressed(MouseCode code)</code> or <code>OnKeyCodePressed(KeyCode code)</code> must be overridden. Mouse and keyboard input events can be enabled/disabled for a panel using <code>SetMouseInputEnabled(bool state)</code> or <code>SetKeyBoardInputEnabled(bool state)</code>.
| | 通过<code>GetParent()</code>或<code>GetVParent()</code>可获取父面板或对应的VPANEL句柄。面板只能有一个父级,但可以有多个子级(通过<code>GetChildCount()</code>和<code>GetChild(int index)</code>查询)。 |
|
| |
|
| Another useful virtual function to override is <code>OnThink()</code> which is called every time the panel is redrawn. Sometimes this is too much and you want update or check the panel state less often. Then you can hook into <code>OnTick()</code> which is called frequently in an adjustable time interval. But VGUI must be told that it should call <code>OnTick()</code> for a given panel and how often, this is done with <code>ivgui()->AddTickSignal(VPANEL panel, int intervalMilliseconds)</code>.
| | 要让面板响应输入事件(如按键或鼠标点击),需重写虚函数如<code>OnMousePressed(MouseCode code)</code>或<code>OnKeyCodePressed(KeyCode code)</code>。通过<code>SetMouseInputEnabled(bool state)</code>和<code>SetKeyBoardInputEnabled(bool state)</code>启用/禁用输入。 |
|
| |
|
| == Windows & popups ==
| | 另一个有用的虚函数是<code>OnThink()</code>,在每次重绘面板时调用。若需降低更新频率,可使用<code>OnTick()</code>并通过<code>ivgui()->AddTickSignal(VPANEL panel, int intervalMilliseconds)</code>设置调用间隔。 |
|
| |
|
| A <code>Panel</code> as described above is always a 100% sub area of its parent panel, its position is always a fixed relative offset and can only be changed by code. Also it draws itself only inside the parent region, if you move a VGUI element outside the parent panel, it will be clipped. This is fine for adding control elements, images or text fields to a panel, but wouldn't allow independent pop-up windows or dialog boxes the user can move around, resize and minimize. Therefore, VGUI panels have the <code>MakePopup()</code> function which decouples a panel from is parent rendering and makes it a new, independent window. Still it belongs to a parent panel and becomes invisible if the parent does.
| | == 窗口与弹窗 == |
|
| |
|
| In most cases you won't call <code>MakePopup()</code> by yourself but use the <code>Frame</code> class instead. The <code>Frame</code> class encapsulates all common GUI windows features like title bar, system menu (close, minimize, maximize), dragging, resizing, focus and layout management.
| | 普通<code>Panel</code>始终是父面板的子区域,位置固定且绘制范围受限。通过<code>MakePopup()</code>可将面板解耦为独立窗口,但仍属于父级。通常使用<code>Frame</code>类实现弹窗功能,其封装了标题栏、系统菜单、拖拽调整等功能。 |
|
| |
|
| Here a short example how to open a frame and activate it. Note that the frame will delete itself once the user closes the <code>Frame</code> or the <code>Close()</code> function is called (just make sure that your stuff is cleaned up in the destructor).
| | 以下示例展示如何创建并激活框架(关闭时会自动销毁): |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
Line 102: |
Line 100: |
| pFrame->SetScheme("ClientScheme.res"); | | pFrame->SetScheme("ClientScheme.res"); |
| pFrame->SetSize( 100, 100 ); | | pFrame->SetSize( 100, 100 ); |
| pFrame->SetTitle("My First Frame", true ); | | pFrame->SetTitle("我的第一个框架", true ); |
| pFrame->Activate(); // set visible, move to front, request focus | | pFrame->Activate(); // 设为可见、置顶并请求焦点 |
| </source> | | </source> |
|
| |
|
| If a Frame is resized, the virtual function <code>PerformLayout()</code> is called so the frame can rearrange its elements to fit best for the new size. The current Frame position and size can also be locked with <code>SetMoveable(bool state)</code> and <code>SetSizeable(bool state)</code>.
| | 调整框架大小时会触发<code>PerformLayout()</code>以重新布局。可通过<code>SetMoveable(bool state)</code>和<code>SetSizeable(bool state)</code>锁定位置和尺寸。 |
| | |
| == Event messaging ==
| |
| | |
| VGUI panels communicate via a message system to signal state changes or events to parents or children (or any other panel). Messages are not sent directly (e.g. by calling a panel listener function), rather they are handed over to VGUI which delivers them to the target panel. Thus beside the message content, a sender and a receiver panel must be specified as VPANEL handles. VGUI sends event messages to inform panels about changes or events (mouse moved, input focus changed etc).
| |
| | |
| The message name and content is specified using a [[KeyValues]] object. The KeyValues class is a very generic and flexible structure to store data records containing strings, integer or float numbers. A KeyValues object has a name and a set of data entries. Each entry has a unique key name and corresponding value. KeyValues also allow creating hierarchical structures using sub keys, though most VGUI messages are flat data records. KeyValues don't have data/type definitions or similar, so you can add or remove entries of any type as you like. Thus sender and receiver must both know the internal structure (e.g. key names, types and their meaning) of a KeyValues message object to communicate successfully. Here some sample code how to use KeyValues in general:
| |
|
| |
|
| <source lang=cpp>
| | == 事件消息传递 == |
| // create a new KeyValues Object named "MyName"
| |
| KeyValues* data = new KeyValues("MyName");
| |
|
| |
|
| // add data entries | | VGUI面板通过消息系统进行通信。消息通过KeyValues对象传递,需指定发送方和接收方的VPANEL句柄。发送消息使用<code>PostMessage(...)</code>或<code>ivgui()->PostMessage(...)</code>,目标面板的<code>OnMessage(...)</code>会分派消息到预定义的处理器(通过<code>MESSAGE_FUNC_*</code>宏注册)。 |
| data->SetInt( "anInteger", 42 );
| |
| data->SetString( "aString", "Marvin" );
| |
|
| |
|
| // read data entries
| | 示例消息处理: |
| int x = data->GetInt("anInteger");
| |
| Con_Printf( data->GetString("aString") );
| |
| | |
| // destroy KeyValues object again, free data records
| |
| data->deleteThis();
| |
| </source>
| |
| | |
| To send a message you can call the Panel member function <code>PostMessage(...)</code> or directly <code>ivgui()-> PostMessage(...)</code>. The name of the KeyValues object is also the message name used for later dispatching. VGUI will call the target panel's <code>OnMessage(...)</code> function, which will dispatch the message to a previous defined message handler. A panel can register new message handlers with one of the <code>MESSAGE_FUNC_*</code> macros, which adds a handler function to the message map. Never override <code>OnMessage(...)</code> to handle new messages, always use a macro. Please note that when you use the macro MESSAGE_FUNC_* only those specific messages will be thrown to your function. If you want to catch the newLine (ENTER) message from a txtEntry element you will use <code>MESSAGE_FUNC_PARAMS( NewLineMessage, "TextNewLine",data)</code>. <code>NewLineMessage</code> is function to call, "TextNewLine" means all messages who's names are "TextNewLine" will be redirected and finally data is the input that will be passed to your function. In this case <code>NewLineMessage</code> will look like this void <code>MyParentPanel::NewLineMessage</code> (KeyValues* data).
| |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
Line 146: |
Line 126: |
| </source> | | </source> |
|
| |
|
| The sending panel creates a KeyValues object, adds message parameters and sends the message (to its parent in this case). The KeyValues object is destroyed by VGUI after it has been processed.
| | 发送方代码: |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
Line 154: |
Line 134: |
| { | | { |
| KeyValues* msg = new KeyValues("MyMessage"); | | KeyValues* msg = new KeyValues("MyMessage"); |
| msg->SetString("text", "Something happened"); | | msg->SetString("text", "有事件发生"); |
| PostMessage(GetVParent(), msg); | | PostMessage(GetVParent(), msg); |
| } | | } |
Line 160: |
Line 140: |
| </source> | | </source> |
|
| |
|
| Using <code>PostMessage()</code> the sending panel must address a single, specific target, which means that all other panels interested in a state change must be known and addressed individually. To avoid hard coding these dependencies, panels have a public event system called action signals. A panel fires generic events with <code>PostActionSignal(KeyValues* message)</code> and interested panels can register as listeners for these signals with <code>AddActionSignalTarget(Panel* target)</code>. These action signals are widely used by VGUI controls, for example messages like <code>"TextChanged"</code> fired by class TextEntry or <code>"ItemSelected"</code> used by class <code>ListPanel</code>. All action signal messages contain a pointer entry "panel" that points to the sending panel.
| | 通过<code>PostActionSignal()</code>发送通用事件,其他面板通过<code>AddActionSignalTarget()</code>注册监听。常用动作信号如"Command"消息,可重写<code>OnCommand(const char* command)</code>处理。 |
| | |
| The example from above using action signals would need the parent panel to register as a listener, preferably in the constructor after the child panel has been created. The child panel just uses <code>PostActionSignal()</code> instead of <code>PostMessage()</code>:
| |
| | |
| <source lang=cpp>
| |
| MyParentPanel::MyParentPanel()
| |
| {
| |
| ...
| |
| m_pChildPanel->AddActionSignalTarget( this );
| |
| }
| |
| | |
| void MyParentPanel::OnMyMessage (KeyValues* data)
| |
| {
| |
| const char* text = data->GetString("text");
| |
| Panel* pSender = (Panel* ) data->GetPtr("panel", NULL);
| |
| }
| |
| | |
| void MyChildPanel::SomethingHappened()
| |
| {
| |
| KeyValues* msg = new KeyValues("MyMessage");
| |
| msg->SetString("text", "Something happened");
| |
| PostActionSignal ( msg );
| |
| }
| |
| </source>
| |
| | |
| To catch the return/enter key from a <code>txtEntry</code> element use this.
| |
| | |
| <source lang=cpp>
| |
| class MyParentPanel : public vgui::Panel
| |
| {
| |
| ...
| |
| private:
| |
| MESSAGE_FUNC_PARAMS( OnMyMessage, "NewLineMessage", data );
| |
| }
| |
| MyParentPanel::MyParentPanel()
| |
| {
| |
| ...
| |
| m_pChildPanel = new vgui::TextEntry( this, "txtEntry" );
| |
| m_pChildPanel->AddActionSignalTarget( this );
| |
| m_pChildPanel->SendNewLine(true); // with the txtEntry Type you need to set it to pass the return key as a message
| |
| }
| |
| | |
| void MyParentPanel::NewLineMessage (KeyValues* data)
| |
| {
| |
| // when the txtEntry box posts an action signal it only sets the name
| |
| // so it is wise to check whether that specific txtEntry is indeed in focus
| |
| // Is the Text Entry box in focus?
| |
| if (m_pChildPanel->HasFocus())
| |
| {
| |
| // We have caught the message, now we can handle it. I would simply repost it to the OnCommand function
| |
| // Post the message to our command handler
| |
| this->OnCommand("Submit");
| |
| }
| |
| }
| |
| </source>
| |
| | |
| A commonly used action signal is the "Command" message, since no message handler needs to be installed. Panels just need to derive the virtual Panel function <code>OnCommand(const char* command)</code> and check for the correct command string. The "Command" message is used by all Button classes and is fired whenever the button is pressed. Here is the example from above using the Command message:
| |
| | |
| <source lang=cpp>
| |
| class MyParentPanel : public vgui::Panel
| |
| {
| |
| ...
| |
| protected:
| |
| virtual void OnCommand(const char* command);
| |
| }
| |
| | |
| MyParentPanel::MyParentPanel()
| |
| {
| |
| ...
| |
| m_pChildPanel->AddActionSignalTarget( this );
| |
| }
| |
| | |
| void MyParentPanel::OnCommand(const char* command)
| |
| {
| |
| if (!stricmp(command, "SomethingHappened"))
| |
| {
| |
| DoSomething ();
| |
| }
| |
| else
| |
| {
| |
| BaseClass::OnCommand(command);
| |
| }
| |
| }
| |
| | |
| void MyChildPanel::SomethingHappened()
| |
| {
| |
| KeyValues* msg = new KeyValues("Command");
| |
| msg->SetString("command", "SomethingHappened");
| |
| PostActionSignal ( msg );
| |
| }
| |
| </source>
| |
| | |
| == Schemes ==
| |
|
| |
|
| A VGUI scheme defines the general "look & feel" of your panels by specifying used colors, fonts & icons of control elements. Schemes are defined in resource files, for example <code>hl2\resource\clientscheme.res</code>. A new panel inherits by default the scheme settings used by its parent. It's also possible to preview the fonts, borders and colors of a scheme by using the <code>showschemevisualizer</code> command, with an argument matching the name of your scheme, in the console. The VGUI scheme manager can load a new scheme with <code>LoadSchemeFromFile(char* fileName, char* tag)</code> which returns <code>HScheme</code> handle. To make a panel using a loaded VGUI scheme call the panel member function <code>SetScheme(HScheme scheme)</code>.
| | == 方案 == |
|
| |
|
| Schemes set the general look of panel elements, but they don't arrange specific control elements of your panel. One way of adding elements to your panel is by doing that in source code. You can create them (usually in the constructor of the parent panel) and set properties like size and position manually using the panel member functions. This will become quite complex and time-consuming for larger dialogs with lots of control elements.
| | VGUI方案(scheme)通过资源文件定义控件的颜色、字体和图标。默认继承父级方案,可通过<code>SetScheme(HScheme scheme)</code>切换。使用<code>LoadControlSettings()</code>加载布局资源文件(需继承自<code>EditablePanel</code>)。 |
|
| |
|
| The most common way to define the layout of your panel elements is to describe all elements in an external resource file like <code>hl2\resource\UI\classmenu.res</code> (a KeyValues text file). When the parent panel is created, this file is loaded and executed with <code>LoadControlSettings(char* dialogResourceName)</code>. Do note, however, that this function is only defined for EditablePanel (or an <code>EditablePanel</code> derivative such as <code>Frame</code>). In this resource file each control element is defined in a separate section. A typical control definition looks like this:
| | 资源文件示例: |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
| MyControlName | | MyControlName |
| { | | { |
| ControlName Label // control class | | ControlName Label |
| fieldName MyControlName // name of the control element | | fieldName MyControlName |
| xpos 8 // x position | | xpos 8 |
| ypos 72 // y position | | ypos 72 |
| wide 160 // width in pixels | | wide 160 |
| tall 24 // height in pixels | | tall 24 |
| visible 1 // it's visible... | | visible 1 |
| enabled 1 // ...and enabled | | enabled 1 |
| labelText "Hello world" // display this unlocalized text | | labelText "你好世界" |
| textAlignment west // left text alignment | | textAlignment west |
| } | | } |
| </source> | | </source> |
|
| |
|
| Each control property has a key name and a value. Properties defined by the base class Panel are available for all controls (like <code>xpos, ypos, wide, tall</code> etc). A list of all available key names is returned by the panel member function <code>GetDescription()</code>. Derived panel classes may add new fields for their specific properties. Processing these new fields must be handled by overriding the virtual function <code>ApplySettings(KeyValues* inResourceData)</code>. Here you can also lookup how values for an existing control property are interpreted.
| | == 构建模式 == |
|
| |
|
| == Build mode ==
| | [[File:Vgui2_4.jpg|left|VGUI构建模式面板]] |
|
| |
|
| [[Image:Vgui2_4.jpg|left|VGUI Build Mode panel]]
| | 通过<code>SHIFT+CTRL+ALT+B</code>开启构建模式,实时编辑面板布局并保存到资源文件。 |
|
| |
|
| An even simpler way to edit panel and control layouts is the VGUI Build Mode. This allows you to modify and save the layout of a panel resource file while the application is running. To edit a panel, just start the game and open this panel, so it gets the input focus. Then press <code>SHIFT+CTRL+ALT+B</code> to open the VGUI Build Mode editor. In Build Mode you can easily rearrange existing elements and change their control properties (press 'Apply' to update changes). To add a new control element, just choose the desired class from the combo-box on the lower right side and an empty control object of that class will be place in your panel for further editing. To save the current layout in the associated resource file press the 'Save' button (make sure the resource file is not write-protected). <br style="clear:both">
| | == 绘制与表面 == |
|
| |
|
| == Drawing & surfaces ==
| | 重写<code>Paint()</code>和<code>PaintBackground()</code>自定义绘制。使用ISurface接口绘制图形、文本和纹理。示例绘制红色方块: |
| | |
| Using schemes and control properties you can change the general appearance and layout of existing controls, but it doesn't allow you to create completely new elements. To change the appearance of a panel you have to override the two virtual functions <code>Panel::Paint()</code> and <code>Panel::PaintBackground()</code>. In these functions you can use drawing functions provided by the ISurface interface to place lines, rectangles, text, images etc. When updating the screen, VGUI calls first <code>PaintBackground()</code> and then <code>Paint()</code> for each panel and its children panels recursively. Drawing coordinates are relative to the panel you draw in. Here same example code how to draw a red box in the upper left corner:
| |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
Line 292: |
Line 178: |
| { | | { |
| BaseClass::Paint(); | | BaseClass::Paint(); |
| surface()->DrawSetColor( 255, 0, 0, 255 ); //RGBA | | surface()->DrawSetColor(255, 0, 0, 255); |
| surface()->DrawFilledRect( 0, 0, 20, 20 ); //x0,y0,x1,y1 | | surface()->DrawFilledRect(0, 0, 20, 20); |
| } | | } |
| </source> | | </source> |
|
| |
|
| To draw text on a panel surface you need to set the used font first. Fonts have names like "DefaultSmall" and their properties like True-Type-Font, size and weight are defined in scheme resource files. The font handle can be retrieved by calling <code>GetFont("Name")</code> of the current panel scheme, then the surface can be told to use it as the current font. After setting the color and position for the next text output, the text itself must be passed as wide char string (Unicode) to <code>DrawPrintText(...)</code>. This text will not be printed as it is and not localized anymore, so localization tokens must be translated beforehand.
| | 文本绘制需设置字体: |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
| void MyPanel::Paint(void) | | void MyPanel::Paint(void) |
| { | | { |
| wchar_t* pText = L"Hello world!"; // wide char text | | wchar_t* pText = L"你好世界!"; |
| | |
| // get the right font handle for this scheme
| |
| vgui::IScheme* pScheme = vgui::scheme()->GetIScheme(GetScheme()); | | vgui::IScheme* pScheme = vgui::scheme()->GetIScheme(GetScheme()); |
| vgui::HFont hFont = pScheme->GetFont( "DefaultSmall" ); | | vgui::HFont hFont = pScheme->GetFont("DefaultSmall"); |
| | | surface()->DrawSetTextFont(hFont); |
| surface()->DrawSetTextFont( hFont ); // set the font | | surface()->DrawSetTextColor(255, 0, 0, 255); |
| surface()->DrawSetTextColor( 255, 0, 0, 255 ); // full red | | surface()->DrawSetTextPos(10, 10); |
| surface()->DrawSetTextPos( 10, 10 ); // x,y position | | surface()->DrawPrintText(pText, wcslen(pText)); |
| surface()->DrawPrintText( pText, wcslen(pText) ); // print text | |
| } | | } |
| </source> | | </source> |
|
| |
|
| To draw a [[texture]] or image, VGUI has to load the texture from disk once (panel constructor) and generate a corresponding texture ID. This texture ID is then used as reference when drawing the texture. Here an example how to load and draw the texture <code>"\materials\your_folder\mylogo.vmt"</code>:
| | 加载并绘制纹理: |
|
| |
|
| <source lang=cpp> | | <source lang=cpp> |
| MyPanel::MyPanel(void) | | MyPanel::MyPanel(void) |
| { | | { |
| //Do _not_ forget CreateNewTextureID(), it gave me a headache for a week
| |
| m_nTextureID = vgui::surface()->CreateNewTextureID(); | | m_nTextureID = vgui::surface()->CreateNewTextureID(); |
| vgui::surface()->DrawSetTextureFile( m_nTextureID, "vgui/your_folder/mylogo" , true, false); | | vgui::surface()->DrawSetTextureFile(m_nTextureID, "vgui/your_folder/mylogo", true, false); |
| } | | } |
|
| |
|
| void MyPanel::Paint(void) | | void MyPanel::Paint(void) |
| { | | { |
| vgui::surface()->DrawSetTexture( m_nTextureID ); | | vgui::surface()->DrawSetTexture(m_nTextureID); |
| vgui::surface()->DrawSetColor(50,50,50,100);
| | vgui::surface()->DrawSetColor(50,50,50,100); |
| vgui::surface()->DrawTexturedRect( 0, 0, 100, 100 ); | | vgui::surface()->DrawTexturedRect(0, 0, 100, 100); |
| } | | } |
| </source> | | </source> |
|
| |
|
| == Proportionality == | | == 比例缩放 == |
|
| |
|
| Coordinate values that you specify in configuration files are relative to a 640x480 resolution if <code>SetProportional(true)</code> is called before a file is loaded (which, by default, it is). These values will be scaled to the correct size no matter what the user's current resolution may be. For example, a "tall" value of 120 will always mean the panel takes up 1/4th the height of the screen, and an "x" value of 320 means that the panel's left edge will always be centered.
| | 在配置文件中使用比例值(基于640x480分辨率),通过<code>SetProportional(true)</code>自动缩放。代码中可使用<code>scheme()->GetProportionalScaledValue()</code>转换坐标。 |
|
| |
|
| {{tip|Use <code>'''c'''-<wide/tall></code> in your <code>xpos</code> and <code>ypos</code> values to '''c'''enter an element on-screen at different aspect ratios.}} | | {{tip|在<code>xpos</code>和<code>ypos</code>中使用<code>'''c'''-<wide/tall></code>实现不同宽高比下的居中。}} |
|
| |
|
| However, sometimes you will need to calculate coordinates for custom painting and forgo the luxury of configuration files. The problem is that values that you specify in code are relative to the user's native resolution, which may or may ''not'' be the same as the current game resolution (thus rendering <code>surface()->GetScreenSize()</code> useless). The solution to this problem is to use <code>scheme()->GetProportionalScaledValue()</code>. This allows you to specify a coordinate on the 640x480 resolution and have it be remapped to the current resolution.
| | == 本地化 == |
|
| |
|
| == Localization ==
| | 使用#开头的令牌实现多语言支持。例如<code>SetText("#MyMod_HelloWorld")</code>,并在资源文件(如<code>mymod_english.txt</code>)中定义翻译: |
|
| |
|
| VGUI text elements support automatic localization for the preferred user language. Lets assume a text label should show "Hello world!" then you could set this text directly with <code>SetText( "Hello World.")</code>. But if the user would choose a different language than English this label would still show the English text. Therefore you should always use localization tokens to tell VGUI to translate the token into user language, in this example <code>SetText( "#MyMod_HelloWorld")</code>. Tokens are strings starting with the hash sign '#' as control character to tell VGUI that this is not plain text.
| | <source lang=cpp> |
| | | "MyMod_HelloWorld" "Hello world." |
| VGUI keeps a global translation table to map tokens to plain text representation. These translation tables are loaded from resource files, where each file has an extra copy for every supported language (e.g. <code>\resource\hl2_english.txt, \resource\hl2_german.txt</code>).
| | </source> |
| | |
| {{tip|Localization files must be stored in the UCS2-Little Endian format. You may need an advanced text editor like Notepad++ to do this or you can use [[VGUI Localization Tool]].}}
| |
| | |
| A new token definition for your game would look like this:
| |
| | |
| In mymod_english.txt:
| |
| | |
| "MyMod_HelloWorld" "Hello world."
| |
| | |
| In mymod_german.txt:
| |
| | |
| "MyMod_HelloWorld" "Hallo Welt."
| |
| "[english]MyMod_HelloWorld" "Hello world."
| |
| | |
| If your game folder is named "mymod" the Source engine will automatically load the correct translation file (<code>/resource/gamedir_language.txt</code>). You can load additional translation files using the ILocalize interface function <code>AddFile(...)</code>.
| |
| | |
| You can use the <code>ILocalize</code> interface also to manually translate a token into current user language, e.g. <code>g_pVGuiLocalize->Find("#MyMod_HelloWorld")</code> (or <code>vgui::localize()->Find("#MyMod_HelloWorld")</code> in previous versions). This function returns the translation as a 16-bit wide char string (Unicode).
| |
|
| |
|
| VGUI uses Unicode for all text representation to support languages that use more then the 255 ASCII characters, like Chinese or Russian. Language resource files are encoded as Unicode. Note that the files will not be loaded properly unless they are specifically saved with proper Unicode encoding. To convert strings between ANSI ASCII and Unicode during runtime you can used the <code>ILocalize</code> interface functions <code>ConvertANSIToUnicode(...)</code> and <code>ConvertUnicodeToANSI(...)</code>. Also a very useful function is <code>ConstructString(...)</code> which is basically like sprintf(...) for Unicode strings.
| | 通过<code>g_pVGuiLocalize->Find("#Token")</code>获取本地化文本。注意文件需保存为UCS2-Little Endian格式。 |
|
| |
|
| == See also == | | == 另见 == |
| * [[HUD Elements]] | | * {{L|HUD Elements}} |
|
| |
|
| == Tutorials == | | == 教程 == |
| * [[VGUI2: Creating a panel|Creating a Panel]] | | * {{L|VGUI2: Creating a panel|创建面板}} |
| * [[Adding Your Logo to the Menu]] | | * {{L|Adding Your Logo to the Menu|添加菜单Logo}} |
| * [[VGUI Screen Creation|Creating a VGUI Screen]] | | * {{L|VGUI Screen Creation|创建VGUI屏幕}} |
|
| |
|
| [[Category:Programming]]
| | {{ACategory|Programming}} |
| [[Category:VGUI]]
| | {{ACategory|VGUI}} |
This page is Machine translatedIt is not recommended to use machine translation without any corrections.
If the article is not corrected in the long term, it will be removed.
Also, please make sure the article complies with the
alternate languages guide.(en)
This page has not been fully translated.You can help by finishing the translation.
If this page cannot be translated for some reason, or is left untranslated for an extended period of time after this notice is posted, the page should be requested to be deleted.
Also, please make sure the article complies with the
alternate languages guide.(en)This notice is put here by LanguageBar template and if you want to remove it after updating the translation you can do so on
this page.
VGUI是Valve专有的图形用户界面(en)系统。所有或大多数
起源游戏以及早期的
Steam都使用VGUI来绘制窗口、对话框和菜单。它还处理本地化 ↓功能:以用户首选语言显示文本。
- 对象架构采用层级结构,所有实现的元素均派生自VGUI基类。
- 键盘/鼠标输入系统是事件驱动的,与其他GUI系统非常相似。
- 最常见GUI元素(如按钮、文本字段或图像)的实现由VGUI控件库提供(
vgui_controls.lib
)。
- 基础接口头文件位于
\public\vgui\
- 控件元素定义在
\public\vgui_controls\
。
作为模组作者,您最可能通过client.dll项目使用VGUI来显示菜单、HUD元素或游戏内显示(如武器或计算机终端等)。
面板与层级结构
所有VGUI元素的基类均为vgui::Panel
,它定义了屏幕上具有特定大小和位置的区域,可以自行绘制并处理输入事件。对话框窗口、文本字段和按钮都是具有父子层级关系的VGUI面板。最顶层的根面板由Source引擎提供。客户端根面板覆盖整个屏幕,但不可见。尽管可以直接使用客户端根面板,但大多数客户端面板使用共享的BaseViewport
面板作为父级(g_pClientMode->GetViewport()
)。
下图展示了《反恐精英》中的VGUI面板层级结构:
您可以使用VGUI层级工具查看模组的面板结构。通过在开发者控制台(en)输入vgui_drawtree 1
来开启该工具。
面板名称采用颜色编码:
工具提供以下复选框选项:
- 显示可见
- 列出所有可见面板
- 显示隐藏
- 列出所有隐藏面板
- 仅弹窗
- 仅列出弹出面板(框架)
- 高亮鼠标悬停
- 当鼠标悬停时用彩色边框高亮面板,并展开树状图显示当前面板
- 冻结
- 锁定当前树状视图
- 显示地址
- 显示面板内存地址
- 显示透明度
- 显示面板透明度值(0为半透明,255为不透明)
- 按渲染顺序
- 按渲染顺序排列面板
Panel类
VGUI基类Panel
派生出\public\vgui_controls\
中定义的50多种控件。头文件通常详细说明了控件的特性与行为。下表展示常用元素的类层级:
Panel
|
EditablePanel
|
Label
|
TextEntry
|
RichText
|
ImagePanel
|
Frame
|
Button
|
ComboBox
|
MessageBox
|
ToggleButton
|
由于所有VGUI类都派生自Panel类,我们需要深入了解其核心成员函数和属性。
最常用的面板构造函数是Panel(Panel* parent, const char* panelName)
,因为每个面板都需要指定父面板和同级唯一的名称。创建面板后,可通过SetVisible(bool state)
控制可见性。
VGUI为每个新面板维护一个句柄,用于全局寻址而无需传递指针。许多事件消息函数使用这些VPANEL
句柄标识源和目标面板。通过GetVPanel()
可获取面板的VPANEL
句柄。
使用SetPos(int x,int y)
和SetSize(int wide,int tall)
设置面板位置和尺寸。尺寸以像素为单位,不会随屏幕分辨率自动缩放。位置始终相对于父面板的左上角(0,0)。
通过GetParent()
或GetVParent()
可获取父面板或对应的VPANEL句柄。面板只能有一个父级,但可以有多个子级(通过GetChildCount()
和GetChild(int index)
查询)。
要让面板响应输入事件(如按键或鼠标点击),需重写虚函数如OnMousePressed(MouseCode code)
或OnKeyCodePressed(KeyCode code)
。通过SetMouseInputEnabled(bool state)
和SetKeyBoardInputEnabled(bool state)
启用/禁用输入。
另一个有用的虚函数是OnThink()
,在每次重绘面板时调用。若需降低更新频率,可使用OnTick()
并通过ivgui()->AddTickSignal(VPANEL panel, int intervalMilliseconds)
设置调用间隔。
窗口与弹窗
普通Panel
始终是父面板的子区域,位置固定且绘制范围受限。通过MakePopup()
可将面板解耦为独立窗口,但仍属于父级。通常使用Frame
类实现弹窗功能,其封装了标题栏、系统菜单、拖拽调整等功能。
以下示例展示如何创建并激活框架(关闭时会自动销毁):
Frame* pFrame = new Frame( g_pClientMode->GetViewport(), "MyFrame" );
pFrame->SetScheme("ClientScheme.res");
pFrame->SetSize( 100, 100 );
pFrame->SetTitle("我的第一个框架", true );
pFrame->Activate(); // 设为可见、置顶并请求焦点
调整框架大小时会触发PerformLayout()
以重新布局。可通过SetMoveable(bool state)
和SetSizeable(bool state)
锁定位置和尺寸。
事件消息传递
VGUI面板通过消息系统进行通信。消息通过KeyValues对象传递,需指定发送方和接收方的VPANEL句柄。发送消息使用PostMessage(...)
或ivgui()->PostMessage(...)
,目标面板的OnMessage(...)
会分派消息到预定义的处理器(通过MESSAGE_FUNC_*
宏注册)。
示例消息处理:
class MyParentPanel : public vgui::Panel
{
...
private:
MESSAGE_FUNC_PARAMS( OnMyMessage, "MyMessage", data );
}
void MyParentPanel::OnMyMessage (KeyValues* data)
{
const char* text = data->GetString("text");
}
发送方代码:
void MyChildPanel::SomethingHappened()
{
if ( GetVParent() )
{
KeyValues* msg = new KeyValues("MyMessage");
msg->SetString("text", "有事件发生");
PostMessage(GetVParent(), msg);
}
}
通过PostActionSignal()
发送通用事件,其他面板通过AddActionSignalTarget()
注册监听。常用动作信号如"Command"消息,可重写OnCommand(const char* command)
处理。
方案
VGUI方案(scheme)通过资源文件定义控件的颜色、字体和图标。默认继承父级方案,可通过SetScheme(HScheme scheme)
切换。使用LoadControlSettings()
加载布局资源文件(需继承自EditablePanel
)。
资源文件示例:
MyControlName
{
ControlName Label
fieldName MyControlName
xpos 8
ypos 72
wide 160
tall 24
visible 1
enabled 1
labelText "你好世界"
textAlignment west
}
构建模式
通过SHIFT+CTRL+ALT+B
开启构建模式,实时编辑面板布局并保存到资源文件。
绘制与表面
重写Paint()
和PaintBackground()
自定义绘制。使用ISurface接口绘制图形、文本和纹理。示例绘制红色方块:
void MyPanel::Paint(void)
{
BaseClass::Paint();
surface()->DrawSetColor(255, 0, 0, 255);
surface()->DrawFilledRect(0, 0, 20, 20);
}
文本绘制需设置字体:
void MyPanel::Paint(void)
{
wchar_t* pText = L"你好世界!";
vgui::IScheme* pScheme = vgui::scheme()->GetIScheme(GetScheme());
vgui::HFont hFont = pScheme->GetFont("DefaultSmall");
surface()->DrawSetTextFont(hFont);
surface()->DrawSetTextColor(255, 0, 0, 255);
surface()->DrawSetTextPos(10, 10);
surface()->DrawPrintText(pText, wcslen(pText));
}
加载并绘制纹理:
MyPanel::MyPanel(void)
{
m_nTextureID = vgui::surface()->CreateNewTextureID();
vgui::surface()->DrawSetTextureFile(m_nTextureID, "vgui/your_folder/mylogo", true, false);
}
void MyPanel::Paint(void)
{
vgui::surface()->DrawSetTexture(m_nTextureID);
vgui::surface()->DrawSetColor(50,50,50,100);
vgui::surface()->DrawTexturedRect(0, 0, 100, 100);
}
比例缩放
在配置文件中使用比例值(基于640x480分辨率),通过SetProportional(true)
自动缩放。代码中可使用scheme()->GetProportionalScaledValue()
转换坐标。
提示:在xpos
和ypos
中使用c-<wide/tall>
实现不同宽高比下的居中。
本地化
使用#开头的令牌实现多语言支持。例如SetText("#MyMod_HelloWorld")
,并在资源文件(如mymod_english.txt
)中定义翻译:
"MyMod_HelloWorld" "Hello world."
通过g_pVGuiLocalize->Find("#Token")
获取本地化文本。注意文件需保存为UCS2-Little Endian格式。
另见
教程