VGUI Creating A Custom Screen

From Valve Developer Community
Revision as of 14:36, 1 April 2012 by Jeremyw (talk | contribs) (CJCount.res)

Jump to: navigation, search

Quick Briefing on Employed Techniques

The Source Engine SDK is very accessible and allows for dramatic mutations without invading or intruding the primary code. I would highly advice avoiding changes to the source engine's code (and by that I mean, changing source\header files that are already in your solution when you first started creating your mod) unless it is required. In other words, exhaust all other methods before resorting to editing the main code modules of the engine. Thankfully, additional VGUI screens can be made without altering any of the engine's code.


The reason for this is to make sure we all have a common code-base to build off of & also to keep the engine's design solid.

Setting up the solution

The first step is to, if you haven't already, create a seperate filter for your mod named "<your mod>" - the name is insignificant, as long as you can identify it and you think others would be able to as well. (To create a filter, right click on your project, and select Add->New Filter) In this filter you will place your mod's enitites, UI components, Weapon code, etc... I also strongly advice placing in that filter a text document named "patchesToHL2Client.txt" to list any changes you have made to the source sdk engine and for what purpose as you may want to take note of them later.

CJTestScreen.h

In this filter create a header file, "CJTestScreen.h"

  • Create a new class, called CJTestScreen (the "CJ" prefix is just a prefix I give all my game source\header files as not to lose them), 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 out counter is currently reading.

CJTestScreen.h

#pragma once

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

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

	vgui::Label* m_pLblCount;

	unsigned int m_uiCounter;

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

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


CJTestScreen.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 CJTestScreen::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( CJTestScreen, "CJTestScreen" );
  • In your constructor, initilize your
#include "cbase.h"
#include "CJTestScreen.h"
#include "vgui/IVGui.h"

#include <sstream>

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

CJTestScreen::~CJTestScreen()
{
}

bool CJTestScreen::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 CJTestScreen::OnTick()
{

	CVGuiScreenPanel::OnTick();

	if(!m_pLblCount)
		return;
	
	std::stringstream ssBuffer;
	ssBuffer<<"Count: "<<m_uiCounter % 21;

	m_pLblCount->SetText(ssBuffer.str().c_str());

	m_uiCounter++;
}

DECLARE_VGUI_SCREEN_FACTORY( CJTestScreen, "CJTestScreen" );

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 "CJTestScreen" 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/CJCount.res" once we create it.
  • Also notice the pixelswide and pixelshigh members which describe the dimensions of our screen.
...
...
	"CJTestScreen"
	{
		"type"			"CJTestScreen"

		// 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/CJCount.res"
	}
...
...

CJCount.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 CJTestScreen, 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.