VGUI2: Creating a panel
Contents
前提条件
阅读并已经理解下列内容:
能够修改代码:
- C++
- Script
本教程是关于创建一个简单的互动界面。
了解VGUI2是如何工作的
玩Source引擎游戏时,每个你看到的VGUI2窗口基本都叫做Panel。
每个Panel由以下三个组件组成:
- Scheme
- Control Settings
- Code
The Scheme
“Scheme”是一个存储着某些元素如按钮、组合框、标签栏等等信息的配置文件。典型的一个“Scheme”文件是“SourceScheme.res”。如果你想在菜单创建一个看起来像其他Panel一样的Panel,你应该像它们一样使用同一“Scheme”文件。
Control Settings
“Control Settings”文件存储着 你的Panel相对位置以及Panel元素 的信息。
每个Panel有着自己的资源文件。要想创建一个资源文件,有两种办法:一是用编辑器如记事本自己创建一个资源文件,另一个办法就是用“Valves InGame Resource Editor”。
代码
代码是一个Panel中最重要的部分。因为代码决定着用户按下一个按钮之后执行什么操作。要想创建或者破坏Panel你必须得用代码才能实现。幸运的是,相对于资源文件,在代码里你可以设置更多的东西。 代码是本教程中最重要的东西。
创建一个Panel
好吧,假设我们这次真的想要创建一扇门。因为我们还没能够从头开始创建一扇门,所以第一件事我们要去做的就是step by at the building centre。我们要的是一扇简单的门。 虽然它正常工作了,但我们仍然打算去自定义它。
开始
Panel类是所有VGUI2元素中的基础类。 想要粗略概览VGUI2元素,去看一看VGUI元素目录。当然,我们不只是在本地的建筑中心买一些木头。
重要的类是从Panel类继承的“EditablePanel”类。我们的Panel将会是一个新的从“EditablePanel”继承的类。This results in several advantages: We can code methods related to the content of the panel, we can overwrite the methods of the base classes and do much more useful stuff.
You can create a new file MyPanel.cpp underneath the Source Files inside the client project.
MyPanel.cpp
//The following include files are necessary to allow your MyPanel.cpp to compile.
#include "cbase.h"
#include "IMyPanel.h"
using namespace vgui;
#include <vgui/IVGui.h>
#include <vgui_controls/Frame.h>
//CMyPanel class: Tutorial example class
class CMyPanel : public vgui::Frame
{
DECLARE_CLASS_SIMPLE(CMyPanel, vgui::Frame);
//CMyPanel : This Class / vgui::Frame : BaseClass
CMyPanel(vgui::VPANEL parent); // Constructor
~CMyPanel(){}; // Destructor
protected:
//VGUI overrides:
virtual void OnTick();
virtual void OnCommand(const char* pcCommand);
private:
//Other used VGUI control Elements:
};
The constructor: The argument is vgui::VPANEL parent. After reading the VGUI Documentation , you should know that every panel has a parent and why it has a parent.
Underneath the above code, add:
// Constuctor: Initializes the Panel
CMyPanel::CMyPanel(vgui::VPANEL parent)
: BaseClass(NULL, "MyPanel")
{
SetParent( parent );
SetKeyBoardInputEnabled( true );
SetMouseInputEnabled( true );
SetProportional( false );
SetTitleBarVisible( true );
SetMinimizeButtonVisible( false );
SetMaximizeButtonVisible( false );
SetCloseButtonVisible( false );
SetSizeable( false );
SetMoveable( false );
SetVisible( true );
SetScheme(vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "SourceScheme"));
LoadControlSettings("resource/UI/mypanel.res");
vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
DevMsg("MyPanel has been constructed\n");
}
The first lines are pretty easy to understand. SetScheme is used to set the source scheme, which is the standard scheme for Half-Life 2. We get a pointer to the scheme by calling LoadSchemeFromFile(...). The LoadControlSettings function is used to load the control settings resolution file. The last line is explained in the VGUI2 documentation.
The SetProportional( false ); function decides to make the panel big or small, having it set to true will cause your panel to have big fonts, controls etc.
Finally, if you're going to edit it with the VGUI Build Mode Editor, you will need precreate the directory it goes into, and posibly give your resource an all-lower-case name [See also the Discussion page]
Underneath the above code, add:
//Class: CMyPanelInterface Class. Used for construction.
class CMyPanelInterface : public IMyPanel
{
private:
CMyPanel *MyPanel;
public:
CMyPanelInterface()
{
MyPanel = NULL;
}
void Create(vgui::VPANEL parent)
{
MyPanel = new CMyPanel(parent);
}
void Destroy()
{
if (MyPanel)
{
MyPanel->SetParent( (vgui::Panel *)NULL);
delete MyPanel;
}
}
};
static CMyPanelInterface g_MyPanel;
IMyPanel* mypanel = (IMyPanel*)&g_MyPanel;
void CMyPanel::OnTick()
{
BaseClass::OnTick();
}
void CMyPanel::OnCommand(const char* pcCommand)
{
BaseClass::OnCommand(pcCommand);
}
Next you can create IMyPanel.h in the same folder.
IMyPanel.h
// IMyPanel.h
class IMyPanel
{
public:
virtual void Create( vgui::VPANEL parent ) = 0;
virtual void Destroy( void ) = 0;
};
extern IMyPanel* mypanel;
Calling the panel
To call the panel, we add a few lines to the vgui_int.cpp file. Vgui_int.cpp includes functions which call all the panels. The VGui_CreateGlobalPanels() function is where the addition takes place.
This is the point, where you have to decide when the panel should show up. Either you create a panel that can be accessed during the game, or you create a panel for the main menu.
I assume that you want to create a panel for the game.
So, after including the panel, you should add at VGui_CreateGlobalPanels() the following:
mypanel->Create(gameParent);
and check to see if gameParent has been declared at the top of the function, if it hasn't then add
VPANEL gameParent = enginevgui->GetPanel( PANEL_CLIENTDLL );
Note: To have your screen appear in-game like the Counter-Strike buy menus or team selection menus, change PANEL_CLIENTDLL to PANEL_INGAMESCREENS , otherwise it will be visible only when you press Escape to go to the game menu.
Note: Dont forget to add
#include "IMyPanel.h"
to vgui_int.cpp file.
Then add this line into the VGui_Shutdown() function.
mypanel->Destroy();
If you plan to create a panel for the main menu, you need to put this into the construction function (VGui_CreateGlobalPanels()):
VPANEL GameUiDll = enginevgui->GetPanel( PANEL_GAMEUIDLL);
mypanel->Create(GameUiDll);
This will cause the panel to appear when you start your mod.
Adding elements
There are two ways to add new elements. One is to use the VGUI2 Builder (Press CTRL+SHIFT+ALT+B to open it). Since the VGUI2 Builder doesn’t come with all the essential elements, you should add elements in your code. Therefore, you have the choice in-between 50 elements, individually stored in the vgui_controls folder. Indeed, even this is pretty easy. You add a pointer into the class declaration. Here is an example:
vgui::TextEntry* m_pTime; // Panel class declaration, private section
Add this to the panels constructor:
m_pTime = new vgui::TextEntry(this, "MyTextEntry");
m_pTime->SetPos(15, 310);
m_pTime->SetSize(50, 20);
You'll also have to add the following include to get the constructors for TextEntry:
#include <vgui_controls/TextEntry.h>
It is time to make it appear after pressing a new option that we will add to the main menu. On this example, we will use a variable which allows us to set the state of our panel, combined with a button. Here is the example:
MyPanel.cpp
On start, add at the top under <vgui_controls/Frame.h> the following include, so we can add buttons at the code for our panel:
#include <vgui_controls/Button.h>
Add this variable into the private declaration. It will allows you to create later a button for the panel so it can close it:
private:
//Other used VGUI control Elements:
Button *m_pCloseButton;
Underneath all of the other code, add:
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL, "Sets the state of myPanel <state>");
This line defines a new CVAR that it will make show or hide our new panel. Now continue by adding the following line at OnTick():
void CMyPanel::OnTick()
{
BaseClass::OnTick();
SetVisible(cl_showmypanel.GetBool()); //CL_SHOWMYPANEL / 1 BY DEFAULT
}
On next, add a command to toggle the panel on or off:
CON_COMMAND(ToggleMyPanel, "Toggles myPanel on or off")
{
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
};
In the last part of this tutorial we will add a button that it will make close the panel on pressing it. Add the following at the OnCommand() part:
void CMyPanel::OnCommand(const char* pcCommand)
{
if(!Q_stricmp(pcCommand, "turnoff"))
cl_showmypanel.SetValue(0);
}
This will recieve the command "turnoff" that gets sent when we press the button, and on next, it will set cl_showmypanel to 0, closing the panel.
Create the button itself at the panels constructor (you can do it too at the RES file, but coding it will allow you to have more customization options):
m_pCloseButton = new Button(this, "Button", "Close", this, "turnoff");
m_pCloseButton->SetPos(433, 472);
m_pCloseButton->SetDepressedSound("common/bugreporter_succeeded.wav");
m_pCloseButton->SetReleasedSound("ui/buttonclick.wav");
We are creating a button that is parented with the panel, is called "Button", it has written on it "Close", it sends the command "turnoff", it is set to appear at botton right of our panel, and it will make two sounds, one when pressed, and another when released.
Now add
mypanel->Activate();
underneath
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
. This will
focus (bring it at the front of the screen, over all the panels) on your panel. To be able use the Activate() command, you will need to add in MyPanel.cpp.:
void Activate( void )
{
if ( MyPanel )
{
MyPanel->Activate();
}
}
Underneath:
void Destroy( void )
{
if ( MyPanel )
{
MyPanel->SetParent( (vgui::Panel *)NULL );
delete MyPanel;
}
}
And add in IMyPanel.h.:
virtual void Activate( void ) = 0;
Underneath:
virtual void Destroy( void ) = 0;
Main Menu and the panel res file
To open to your new panel from the main menu, open up GameMenu.res in your /resource/ folder, and add:
"5"//Actually this number must be in order with the rest of elements { "label" "My Panel" "command" "engine ToggleMyPanel" "notmulti" "1" //Add this only if you don't want to show this option while on a multiplayer game ("notsingle" "1" for singleplayer) }
(There are more examples of the possible variables here ).
This adds on the position 5 a new option called My Panel, that on press, it will send the command "ToggleMyPanel".
And for the last step, create your panel RES design (this tutorial creates one at the ui folder inside resource). You can use the following as example:
"resource/ui/mypanel.res" { "MyPanel" { "ControlName" "CMyPanel" "fieldName" "MyPanel" "xpos" "797" "ypos" "301" "wide" "512" "tall" "512" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "settitlebarvisible" "1" "title" "MOD OPTIONS" } "frame_topGrip" { "ControlName" "Panel" "fieldName" "frame_topGrip" "xpos" "8" "ypos" "0" "wide" "496" "tall" "5" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_bottomGrip" { "ControlName" "Panel" "fieldName" "frame_bottomGrip" "xpos" "8" "ypos" "507" "wide" "486" "tall" "5" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_leftGrip" { "ControlName" "Panel" "fieldName" "frame_leftGrip" "xpos" "0" "ypos" "8" "wide" "5" "tall" "496" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_rightGrip" { "ControlName" "Panel" "fieldName" "frame_rightGrip" "xpos" "507" "ypos" "8" "wide" "5" "tall" "486" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_tlGrip" { "ControlName" "Panel" "fieldName" "frame_tlGrip" "xpos" "0" "ypos" "0" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_trGrip" { "ControlName" "Panel" "fieldName" "frame_trGrip" "xpos" "504" "ypos" "0" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_blGrip" { "ControlName" "Panel" "fieldName" "frame_blGrip" "xpos" "0" "ypos" "504" "wide" "8" "tall" "8" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_brGrip" { "ControlName" "Panel" "fieldName" "frame_brGrip" "xpos" "494" "ypos" "494" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_caption" { "ControlName" "Panel" "fieldName" "frame_caption" "xpos" "3" "ypos" "-16" "wide" "502" "tall" "23" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" } "frame_minimize" { "ControlName" "Button" "fieldName" "frame_minimize" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "0" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "frame_maximize" { "ControlName" "Button" "fieldName" "frame_maximize" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "1" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "frame_mintosystray" { "ControlName" "Button" "fieldName" "frame_mintosystray" "xpos" "0" "ypos" "0" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "0" "enabled" "1" "tabPosition" "0" "labelText" "o" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "command" "MinimizeToSysTray" "Default" "0" } "frame_close" { "ControlName" "Button" "fieldName" "frame_close" "xpos" "487" "ypos" "8" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "labelText" "r" "textAlignment" "north-west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "2" "textinsety" "1" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" "command" "turnoff" } "frame_menu" { "ControlName" "FrameSystemButton" "fieldName" "frame_menu" "xpos" "7" "ypos" "8" "wide" "18" "tall" "18" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "textAlignment" "west" "dulltext" "0" "brighttext" "0" "wrap" "0" "centerwrap" "0" "textinsetx" "0" "textinsety" "0" "auto_wide_tocontents" "0" "use_proportional_insets" "0" "Default" "0" } "BuildModeDialog" { "ControlName" "BuildModeDialog" "fieldName" "BuildModeDialog" "xpos" "327" "ypos" "301" "wide" "300" "tall" "420" "autoResize" "0" "pinCorner" "0" "RoundedCorners" "15" "pin_corner_to_sibling" "0" "pin_to_sibling_corner" "0" "visible" "1" "enabled" "1" "tabPosition" "0" "settitlebarvisible" "1" "title" "#Frame_Untitled" } }
Final result
This is how it should look at the end both your Cpp and header files:
MyPanel.cpp
//The following include files are necessary to allow your MyPanel.cpp to compile.
#include "cbase.h"
#include "IMyPanel.h"
using namespace vgui;
#include <vgui/IVGui.h>
#include <vgui_controls/Frame.h>
#include <vgui_controls/Button.h>
//CMyPanel class: Tutorial example class
class CMyPanel : public vgui::Frame
{
DECLARE_CLASS_SIMPLE(CMyPanel, vgui::Frame);
//CMyPanel : This Class / vgui::Frame : BaseClass
CMyPanel(vgui::VPANEL parent); // Constructor
~CMyPanel(){}; // Destructor
protected:
//VGUI overrides:
virtual void OnTick();
virtual void OnCommand(const char* pcCommand);
private:
//Other used VGUI control Elements:
Button *m_pCloseButton;
};
// Constuctor: Initializes the Panel
CMyPanel::CMyPanel(vgui::VPANEL parent)
: BaseClass(NULL, "MyPanel")
{
SetParent(parent);
SetKeyBoardInputEnabled(true);
SetMouseInputEnabled(true);
SetProportional(false);
SetTitleBarVisible(true);
SetMinimizeButtonVisible(false);
SetMaximizeButtonVisible(false);
SetCloseButtonVisible(false);
SetSizeable(false);
SetMoveable(false);
SetVisible(true);
SetScheme(vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "SourceScheme"));
LoadControlSettings("resource/UI/playermodelsel.res");
vgui::ivgui()->AddTickSignal(GetVPanel(), 100);
DevMsg("MyPanel has been constructed\n");
//Button done
m_pCloseButton = new Button(this, "Button", "Close", this, "turnoff");
m_pCloseButton->SetPos(433, 472);
m_pCloseButton->SetDepressedSound("common/bugreporter_succeeded.wav");
m_pCloseButton->SetReleasedSound("ui/buttonclick.wav");
}
//Class: CMyPanelInterface Class. Used for construction.
class CMyPanelInterface : public MyPanel
{
private:
CMyPanel *MyPanel;
public:
CMyPanelInterface()
{
MyPanel = NULL;
}
void Create(vgui::VPANEL parent)
{
MyPanel = new CMyPanel(parent);
}
void Destroy()
{
if (MyPanel)
{
MyPanel->SetParent((vgui::Panel *)NULL);
delete MyPanel;
}
}
void Activate(void)
{
if (MyPanel)
{
MyPanel->Activate();
}
}
};
static CMyPanelInterface g_MyPanel;
MyPanel* mypanel = (MyPanel*)&g_MyPanel;
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL, "Sets the state of myPanel <state>");
void CMyPanel::OnTick()
{
BaseClass::OnTick();
SetVisible(cl_showmypanel.GetBool());
}
CON_COMMAND(OpenTestPanelFenix, "Toggles testpanelfenix on or off")
{
cl_showmypanel.SetValue(!cl_showmypanel.GetBool());
mypanel->Activate();
};
void CMyPanel::OnCommand(const char* pcCommand)
{
BaseClass::OnCommand(pcCommand);
if (!Q_stricmp(pcCommand, "turnoff"))
cl_showmypanel.SetValue(0);
}
IMyPanel.h
class MyPanel
{
public:
virtual void Create(vgui::VPANEL parent) = 0;
virtual void Destroy(void) = 0;
virtual void Activate(void) = 0;
};
extern MyPanel* mypanel;
Now compile your files, boot your mod, and press the new option that it should appear at the main menu. If everything goes fine, a panel will open, and pressing the button "Close" will close it. You can also close it by pressing again the main menu option, since we made it toggle.
Makefile changes on Linux
In source-sdk-2013/mp/src/game/client/client_linux32_hl2mp.mak (for multiplayer), you need to add a mention of your new file to the list of files to be built, and then a recipe to build it.
First, add the .cpp file to the list to be built:
CPPFILES= \
MyPanel.cpp \
../../common/compiledcaptionswap.cpp \
../../common/language.cpp \
...
Then, down near the "recipe" for vgui_int.cpp, add the following recipe to build MyPamel:
ifneq (clean, $(findstring clean, $(MAKECMDGOALS)))
-include $(OBJ_DIR)/MyPanel.P
endif
$(OBJ_DIR)/MyPanel.o : $(PWD)/MyPanel.cpp $(PWD)/client_linux32_hl2mp.mak $(SRCROOT)/devtools/makefile_base_posix.mak
$(PRE_COMPILE_FILE)
$(COMPILE_FILE) $(POST_COMPILE_FILE)
See also
- Modifying Source GameUI is a lighter dose of creating your own UI.
- Non RES-File Controls are controls that can be compiled in code instead of RES files like in Half-Life 1