VGUI Creating A Custom Screen

From Valve Developer Community
Jump to navigation Jump to search
Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Dead End - Icon.png
This article has no Wikipedia icon links to other VDC articles. Please help improve this article by adding links Wikipedia icon that are relevant to the context within the existing text.
January 2024

Quick Briefing

We will be trying to achieve the act of rendering vgui components in to our world. This is done via VGUI screens. A much more polished example is what you might see below:

caption

TestScreen.h

Create a new header file, "TestScreen.h"

  • Create a new class, called TestScreen, which inherits from CVGuiScreenPanel found in c_vguiscreen.h. As described in the header, this is the base class for all vgui screens.
  • Use DECLARE_CLASS to give the source sdk some recognition to your class if need be later.
  • Define your class constructor and destructor as you see below. Your constructor is expected to take two arguments, one is a pointer to the panel which is going to be your screen's parent. The other is string describing the meta class name you are to be creating (We'll get in to what these are a little later.)
  • Declare two virtual functions which overload that of our super class. OnTick (so we can keep track of time...) as well as the function Init.
  • Declare a pointer to a label, counter, that we will be incrementing at intervals of one second in length. When Init is called, we will initilize this pointer such that it points to the actual Label in our panel.
  • Declare a new unsigned integer, which will keep track of what number our counter is currently reading.

TestScreen.h

#pragma once

#include "cbase.h"
#include "c_vguiscreen.h"
#include "vgui_controls/Label.h"

class TestScreen : public CVGuiScreenPanel
{
private:
	DECLARE_CLASS(TestScreen, CVGuiScreenPanel);	

	vgui::Label* m_pLblCount;

	unsigned int m_uiCounter;

public:
	TestScreen(vgui::Panel *pParent, const char *pMetaClassName);
	virtual ~TestScreen();

	virtual void OnTick();
	virtual bool Init(KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData );
};

TestScreen.cpp

  • At the end (or start) of your source file, place a macro DECLARE_VGUI_SCREEN_FACTORY. The first argument is the class which will be acting as a screen factory, and the second argument is the name of your screen factory. Later, you will reference this name in vgui_screens.txt when you are describing your screens.
  • In the constructor, we simply initilize our label pointer & our counter to zero and call the base class's constructor.
  • Per every tick, as can be seen in our implementation of OnTick, we call the base class's OnTick routine, and then increment our counter. We also set the text for our label (and format it using a stringstream.)
  • In TestScreen::Init, we simply setup our frame's Tick signal, and have it tick at intervals of 1 second (1000 ms)
  • The general trend within the source sdk engine (or so I have seen) is that the pMetaClassName member in our constructor is simply ignored and passed down to the super class. With that said, you can make several different kinds of forms with your factory depending on the "MetaClassName" argument (we'll get in to what this actually is when we edit vgui_screens.txt).
DECLARE_VGUI_SCREEN_FACTORY( TestScreen, "TestScreen" );
  • In your constructor, initilize your
#include "cbase.h"
#include "TestScreen.h"
#include "vgui/IVGui.h"

#include <sstream>

TestScreen::TestScreen(vgui::Panel *pParent, const char *pMetaClassName) : 
	CVGuiScreenPanel(pParent, pMetaClassName)
{
	m_pLblCount = 0;
	m_uiCounter = 0;
}

TestScreen::~TestScreen()
{
}

bool TestScreen::Init(KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData)
{

	if(!CVGuiScreenPanel::Init(pKeyValues, pInitData))
		return false;
	
	vgui::ivgui()->AddTickSignal(GetVPanel(), 1000);
	
	m_pLblCount = dynamic_cast<vgui::Label*>(FindChildByName("lblCount"));
	return true;
}

void TestScreen::OnTick()
{

	CVGuiScreenPanel::OnTick();

	if(!m_pLblCount)
		return;
	
	m_pLblCount->SetText(VarArgs("Count: %i", m_uiCounter % 21));
	m_uiCounter++;
}

DECLARE_VGUI_SCREEN_FACTORY( TestScreen, "TestScreen" );

Setting up vgui_screens.txt

Found in the directory: <your mod>\scripts. I.e, mine is:

C:\Program Files (x86)\Steam\steamapps\sourcemods\cj\scripts

In here, add the following under "VGUI_Screens";

  • The "TestScreen" you see is the name of the factory that will be constructing the screen.
  • We reference the resource file that describes the appearence of our screen in the "resfile" property. As you can see, we will be storing this resource file in "<your mod>/resource/screens/Count.res" once we create it.
  • Also notice the pixelswide and pixelshigh members which describe the dimensions of our screen.
...
...
	"TestScreen"
	{
		"type"			"TestScreen"

		// These describe the dimensions of the screen *in pixels*
		"pixelswide"	480
		"pixelshigh"	240

		// This is the name of the .res file to load up and apply to the vgui panel
		"resfile"		"resource/screens/Count.res"
	}
...
...

Count.res

This file should be saved in "<your mod>/resource/screens/"

I simply took another screens res file and changed it up a bit; but you can use the in game vgui editor and cut the excess components (i.e the title bar, etc...), save it, and then plug it in to your screen.

"screen_basic.res"
{
	"Background"
	{
		"ControlName"	"MaterialImage"
		"fieldName"		"Background"
		"xpos"			"0"
		"ypos"			"0"
		"zpos"			"-2"
		"wide"			"480"
		"tall"			"240"

		"material"		"vgui/screens/vgui_bg"
	}

	"lblCount"
	{
		"ControlName"	"Label"
		"fieldName"		"lblCount"
		"xpos"			"120"
		"ypos"			"110"
		"wide"			"240"
		"tall"			"34"
		"autoResize"	"0"
		"pinCorner"		"0"
		"visible"		"1"
		"enabled"		"1"
		"tabPosition"	"0"
		"labelText"		"0"
		"textAlignment"	"center"
		"dulltext"		"0"
		"paintBackground" "0"
	}
}

Creating the panel in Hammer

Finally, open Hammer, build yourself a simple map and place a vgui_screen entity. Set the "Panel Name" property to TestScreen, and you should have a working counter (with an annoying background image loaded in to your map. Beware that the opposite side to your screen does not render - so be sure to circle the entity before you figure that what you've done hasn't worked.