VGUI2 Programming Best Practices
The VGUI2 system is used for all of our UI needs in the game engine and in the Steam UI. VGUI2 provides a rich windowing library that works in native windows and in the game engine (with the same code producing the same visual layout).
Event Driven
Don't poll in an UI panel (i.e use an OnThink()
or OnTick()
function ), use messages instead. The PostMessage()
call lets you send a message to a particular panel.
An example is a hud health control. Rather than polling the player entity every frame and resetting the value (making VGUI2 recalculate the panel every frame because you changed it), have the player fire an event (for example using the GameEvent system) that the hud health control listens for (and then updates itself). By doing this you save VGUI2 the cost of recalculating the panel every frame AND you simplify your code by having a simple setting event. For our current codebase this may involve creating a dispatching interface (i.e the player resource object) to poll world objects and then create events when needed.
You should also steer away from calling SetVisible()
once per frame on a panel. SetVisible()
is a reasonably expensive call, it causes the VGUI2 library to recalculate various values for all children panels when it is called (sometimes even when you set the same visibility value). Only call SetVisible()
when you want to change visibility.
Note that OnThink() and Paint() is not called when visibility is turned off. For the performance, it's better to set a tick at 100ms and check & set the visibility in OnTick()
Hierarchical
VGUI2 has a rich control library that is designed to be derived from and then overriden for the specific pieces of functionality you want to alter (i.e changing the behavior of the panel on a mouse double-click). When writing a control decompose it into its base components rather than writing one monolithic control.
When writing a panel you should rarely call surface()
functions directly, we already have panels to support what you want.
For example, if you want to have a flag capture panel that contains a flag picture and then the name of the capture point then use an ImagePanel for the flag and a Label to display the name. If you then want to animate the image (to show capture progress perhaps) then derive from ImagePanel and override the paint function to blend the images you want.
You should NEVER write a complex Paint()
function that renders a high level control in one pass as this leads to complex code, lack of common code (making bug fixing harder) and makes maintenance on the code hard (as each panel works differently).
Data Driven
Panel position, layout, colors and fonts should all be data driven. Do not hard code these values directly into the code.
You should use resource files to control the position and size of panels. For HUD controls look in scripts/hudlayout.res, for others use LoadControlSettings()
to load a layout. Colors and fonts should be applied from the scheme by overriding the ApplySchemeSettings()
panel call. The scheme is defined in resource/SourceScheme.res.
Visibility
All the panels in VGUI2 are part of one large tree, with the root being the base surface panel. The visibility of any panel is controlled by all the Panels above it in the tree. Making a panel invisible hides all its children (and children's children, etc).
Keyboard and mouse input are also controlled in a similar fashion. Your ability to accept either is controlled by what your parent has set. You can override this by calling SetKeyBoardInputEnabled()
or SetMouseInputEnabled()
. Note that a popup (i.e a vgui::Frame
) has both keyboard and mouse input enabled by default when it is constructed.