VGUI Screen Creation: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
mNo edit summary
m (Nesciuse moved page VGUI Screen Creation/en to VGUI Screen Creation without leaving a redirect: Move en subpage to basepage)
 
(67 intermediate revisions by 22 users not shown)
Line 1: Line 1:
[[Category:Programming]][[Category:Tutorials]]
{{LanguageBar}}


== Getting it to load ==
== Adding a VGUI screen to map==
( Originally from jim_the_coder from [http://forums.thewavelength.net/index.php?showtopic=11134 TheWavelength] )
{{note|This section assumes a mod where VGUI screens have been fixed (see the [[#VGUI code modifications|code modifications section]] below) but otherwise left the same. VGUI screens do ''not'' work in {{hl2|4}} or {{hl2dm|4}}; they crash the game. ({{css|4}} and many other games not tested.)}}


1) Create a .res file in ''steamdir''\SourceMods\''yourmod''\scripts\screens
# Create a [[VGUI_Screen|vgui_screen]] entity. This is a point entity.
ex. vgui_test_screen.res:
# Set its '''Panel Name''' to the name of the screen that should be shown. (This is not the filename of the screen.) The available screens are listed in {{code|vgui_screens.txt}} (which should be in the {{code|scripts}} directory).
# Set '''Panel Width in World''' and '''Panel Height in World''' to match the size of the brush. 64 wide by 32 tall would be a reasonable starting size.
# Compile the map and test.


"screen_basic.res"
The position of the entity in the map marks the bottom-left corner of the panel. The panel's direction (the normal of its face) is set by the entity's angle (Yaw).
{
    "Background"
    {
        "ControlName" "MaterialImage"
        "fieldName"  "Background"
        "xpos"  "0"
        "ypos"  "0"
        "zpos"  "-2"
        "wide"  "480"
        "tall"  "240"
        "material"  "vgui/screens/vgui_overlay"
    }
    "OwnerReadout"
    {
        "ControlName" "Label"
        "fieldName"  "OwnerReadout"
        "xpos"  "10"
        "ypos"  "20"
        "wide"  "240"
        "tall"  "34"
        "autoResize" "0"
        "pinCorner"  "0"
        "visible"  "1"
        "enabled"  "1"
        "tabPosition" "0"
        "labelText"  "No Owner"
        "textAlignment" "center"
        "dulltext"  "0"
        "paintBackground" "0"
    }
    "HealthReadout"
    {
        "ControlName" "Label"
        "fieldName"  "HealthReadout"
        "xpos"  "240"
        "ypos"  "20"
        "wide"  "240"
        "tall"  "34"
        "autoResize" "0"
        "pinCorner"  "0"
        "visible"  "1"
        "enabled"  "1"
        "tabPosition" "0"
        "labelText"  "Health: 100%"
        "textAlignment" "center"
        "dulltext"  "0"
        "paintBackground" "0"
    }
    "DismantleButton"
    {
        "ControlName" "MaterialButton"
        "fieldName"  "Dismantle"
        "xpos"  "78"
        "ypos"  "160"
        "wide"  "324"
        "tall"  "48"
        "autoResize" "0"
        "pinCorner"  "0"
        "visible"  "1"
        "enabled"  "1"
        "tabPosition" "2"
        "labelText"  "Dismantle"
        "textAlignment" "center"
        "dulltext"  "0"
        "brighttext" "0"
        "Default"  "0"
        "command"  "quit"
        "paintborder" "0"
        "enabledImage"
        {
            "material" "vgui/screens/vgui_button_enabled"
            "color" "255 255 255 255"
        }
        "mouseOverImage"
        {
            "material" "vgui/screens/vgui_button_hover"
            "color" "255 255 255 255"
        }
        "pressedImage"
        {
            "material" "vgui/screens/vgui_button_pushed"
            "color" "255 255 255 255"
        }
        "disabledImage"
        {
            "material" "vgui/screens/vgui_button_disabled"
            "color" "255 255 255 255"
        }
    }
}


2) The materials used in the .res file can be found in source material.gcf using Nem's [[GCFScape]]. Extract the files in hl2\materials\vgui\screens to ''steamdir''\SourceMods\''moddir''\materials\vgui\screens.
== Creating a VGUI screen ==
VGUI screen files have a ''.res'' extension and should be placed in {{code|scripts\screens\}}.


3) In ''steamdir''\SourceMods\''moddir''\scripts create or open the text file vgui_screens.txt and add a new entry for the screen.
# {{path|vgui_test_screen|res}} is a good starting point for creating a VGUI screen file; it can be found at {{path|...\hl2\scripts\screens}} .
ex.
# The materials used in the ''.res'' file can be found in {{path|hl2_misc_dir|vpk}}. Extract the material files mentioned in the ''.res'' file to {{path|materials\vgui\screens}}.
# Add the screen to {{path|scripts\vgui_screens|txt}}. If it does not exist, create it. Here is an example which describes screen {{code|vgui_test_screen}} at {{path|scripts/screens/vgui_test_screen|res}}. Adjust it for your screen.


  "VGUI_Screens"
  "VGUI_Screens"
Line 115: Line 29:
         "resfile" "scripts/screens/vgui_test_screen.res"
         "resfile" "scripts/screens/vgui_test_screen.res"
     }  
     }  
    "teleport_countdown_screen"
    {
        "type" "teleport_countdown_screen"
        "pixelswide" 480
        "pixelshigh" 240
        "resfile" "scripts/screens/teleport_countdown_screen.res"
    }
  }
  }


4) Open Hammer create a small square map with a spawn, a light, and a vgui_screen.
An example {{file|vgui_screen|txt}} file can be found at {{path|root/hl2/scripts}}.
Change the vgui_screen's properties like so:
Panel Name = vgui_test_screen
Panel Width in World = 64
Panel Height in World = 32
 
Height and Width can be changed to whatever you need. Those values define the actual size of the screen in the world.
 
5) Save your files, build your map, launch your mod, and whala! you have a vgui_screen in game.
 
== Getting Input to work ==
 
Most of the information in this section came from Helk over at [http://www.hostile-planet.com HostilePlanet]
 
First I will explain how the code is set up.
 
1) Input is gathered and sent to in_main.cpp
 
2) CInput::CreateMove is called to handle the input.


3) After some other classes look at the input, it goes to C_BasePlayer::CreateMove
==VGUI code modifications==


4) After all other input handling in C_BasePlayer, it is sent to C_BasePlayer::DetermineVguiInputMode.
There is a problem with VGUI screens receiving input in certain games. Unless it is fixed, the game may crash when the cursor points at the screen.


5) DetermineVguiInputMode then checks for a vgui_screen, and sends the input to SetVGuiScreenButtonState which is defined in c_vguiscreen.cpp
{{code|CInput::ExtraMouseSample}} calls {{code|g_pClientMode->CreateMove()}} without initializing the button flags, thus the VGui screen is updated with bad button input.  Since {{code|IN_VALIDVGUIINPUT}} is only set from within {{code|CInput::CreateMove}}, the VGui screens only update when the button flags are valid.


6) SetVGuiScreenButtonState then sends to input to C_VGuiScreen::SetButtonState
There are two known fixes. They both involve code changes and therefore are only applicable to mods.


7) C_VGuiScreen::SetButtonState determines which buttons have been released and which have been pressed and saves them.
=== Fix 1 ===
In '''src\game\shared\in_buttons.h''', where the other flags are defined add:
    #define IN_VALIDVGUIINPUT     (1 << 23) //bitflag for vgui fix


8) C_VGuiScreen::ClientThink is called every x ticks, like most Think Functions, and acts on the input.
next, in '''src\game\client\in_main.cpp''' inside method {{code|CInput::CreateMove ( ''...'' )}}
add:
    cmd->buttons |= IN_VALIDVGUIINPUT;
right above:
    g_pClientMode->CreateMove( input_sample_frametime, cmd )


The problem with the input is that it is generated several times per '''tick''' and the input gets flooded to C_VguiScreen. This wouldn't be a problem except only 1 out of every 4 or so input messages contains the actual input. Because the C_VguiScreen is flooded with bad data( the bad data that is sent is a zero ), the buttonstate that gets saved is garbage and when it is used in C_VguiScreen:ClientThink, it does nothing.
next, in '''src\cl_dll\c_baseplayer.cpp''' inside method {{code|C_BasePlayer::CreateMove( ''...'' )}}
 
add:
I have found one very easy way to solve this. All my code changes are in c_vguiscreen.cpp and mostly involve SetVGuiScreenButtonState. Here is a general overview:
     if(pCmd->buttons & IN_VALIDVGUIINPUT)
 
right above:
1) Start a counter at zero.
     DetermineVguiInputMode( pCmd );
 
''(So it only calls {{code|DetermineVguiInputMode}} if the buttons include our flag)''
2) Increment the counter.
 
3) Look at the input.
 
4) If the input is non-zero(ie. we have valid data) or if the counter has hit the max(I used 5), reset the counter and send the input to C_VguiScreen::SetButtonState.
 
My final code looks like:
 
#define MAX_VGUI_SCREEN_INPUT_TICK 5
static int tickCount=0;
void SetVGuiScreenButtonState( C_BaseEntity *pVguiScreenEnt, int nButtonState )
{
    tickCount++;
     if( nButtonState !=0 || tickCount == MAX_VGUI_SCREEN_INPUT_TICK )
     {
        DevMsg( "Button state = %d tick = %d\n", nButtonState, tickCount );
        if ( pVguiScreenEnt )
        {
            Assert( dynamic_cast<C_VGuiScreen*>(pVguiScreenEnt) );
            C_VGuiScreen *pVguiScreen = static_cast<C_VGuiScreen*>(pVguiScreenEnt);
            pVguiScreen->SetButtonState( nButtonState );
        }
        tickCount = 0;
    }
}


== Further Possibilities ==
and finally, inside method {{code|C_BasePlayer::DetermineVguiInputMode( ''...'' )}}
change '''both''' instances of:
    pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2);
to read:
    pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2 | IN_VALIDVGUIINPUT);


1) Make it so that a player has to hold down a button to use the screen.
Since an [[HL2]] engine update in October VGUI Screens will crash when you put your mouse over them. This is because the {{code|g_InputInternal}} function used in {{code|\src\cl_dll\c_vguiscreen.cpp}} is no longer working. Trying to access it makes it crash. For a workaround, the following {{code|C_VGuiScreen::ClientThink( void )}} function has proven useful:


2) Make it so that when a player is viewing the screen, all input is sent to the screen, including movement keys. The player will not be able to move again, until they look outside the bounds of the screen.
// Convert (u,v) into (px,py)
int px = (int)(u * m_nPixelWidth + 0.5f);
int py = (int)(v * m_nPixelHeight + 0.5f);
// START TEDDYS FIX
for (int i = 0; i < pPanel->GetChildCount(); i++)
{
vgui::Button *child = dynamic_cast<vgui::Button*>(pPanel->GetChild(i));
if ( child )
{
int x1, x2, y1, y2;
child->GetBounds( x1, y1, x2, y2 );
// Generate mouse input commands
if ( (m_nButtonState & IN_ATTACK) )
{
if ( px >= x1 && px <= x1 + x2 && py >= y1 && py <= y1 + y2 )
child->FireActionSignal();
}
}
}
// FIN TEDDYS FIX
if ( m_bLooseThinkNextFrame == true )
{
m_bLooseThinkNextFrame = false;
SetNextClientThink( CLIENT_THINK_NEVER );
}


3) Change the MAX_VGUI_SCREEN_INPUT_TICK to a convar like cl_screenmaxtick to allow clients control over how often the input should be updated. This could help laggy systems use the screen, and fast systems quicker response times.
=== Fix 2 ===
An alternative fix would be to add a new boolean variable to {{code|CUserCmd}} such as {{code|bButtonFlagsValid}} that can be set depending on the validity of the button flags. This would allow the structure to internally store this information for its whole lifetime while freeing up that extra button bit.


== Revision History ==
== Example screenshots ==
Created: 7/13/05 by TJMonk15
[[File:Granted_vgui.jpg|thumb|left|VGUI screen used in mod]]
[[File:Ekg_vgui.jpg|thumb|left|VGUI screen used to display dynamically drawn EKG data (Pulse!! game)]]
[[File:Bms mortar vgui.jpg|thumb|left|VGUI screen used to control the mortar in {{bms|3.1}}.]]
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:VGUI|S]]

Latest revision as of 11:54, 12 July 2024

English (en)Русский (ru)中文 (zh)Translate (Translate)

Adding a VGUI screen to map

Note.pngNote:This section assumes a mod where VGUI screens have been fixed (see the code modifications section below) but otherwise left the same. VGUI screens do not work in Half-Life 2 Half-Life 2 or Half-Life 2: Deathmatch Half-Life 2: Deathmatch; they crash the game. (Counter-Strike: Source Counter-Strike: Source and many other games not tested.)
  1. Create a vgui_screen entity. This is a point entity.
  2. Set its Panel Name to the name of the screen that should be shown. (This is not the filename of the screen.) The available screens are listed in vgui_screens.txt (which should be in the scripts directory).
  3. Set Panel Width in World and Panel Height in World to match the size of the brush. 64 wide by 32 tall would be a reasonable starting size.
  4. Compile the map and test.

The position of the entity in the map marks the bottom-left corner of the panel. The panel's direction (the normal of its face) is set by the entity's angle (Yaw).

Creating a VGUI screen

VGUI screen files have a .res extension and should be placed in scripts\screens\.

  1. 🖿vgui_test_screen.res is a good starting point for creating a VGUI screen file; it can be found at 🖿...\hl2\scripts\screens .
  2. The materials used in the .res file can be found in 🖿hl2_misc_dir.vpk. Extract the material files mentioned in the .res file to 🖿materials\vgui\screens.
  3. Add the screen to 🖿scripts\vgui_screens.txt. If it does not exist, create it. Here is an example which describes screen vgui_test_screen at 🖿scripts/screens/vgui_test_screen.res. Adjust it for your screen.
"VGUI_Screens"
{
   "vgui_test_screen"
   {
       // This is our example screen
       "type"		"vgui_screen_panel"
       "pixelswide"	480
       "pixelshigh"	240
       // This must be the file you created in step 1
       "resfile"	"scripts/screens/vgui_test_screen.res"
   } 
}

An example 🖿vgui_screen.txt file can be found at 🖿root/hl2/scripts.

VGUI code modifications

There is a problem with VGUI screens receiving input in certain games. Unless it is fixed, the game may crash when the cursor points at the screen.

CInput::ExtraMouseSample calls g_pClientMode->CreateMove() without initializing the button flags, thus the VGui screen is updated with bad button input. Since IN_VALIDVGUIINPUT is only set from within CInput::CreateMove, the VGui screens only update when the button flags are valid.

There are two known fixes. They both involve code changes and therefore are only applicable to mods.

Fix 1

In src\game\shared\in_buttons.h, where the other flags are defined add:

   #define IN_VALIDVGUIINPUT		    (1 << 23) //bitflag for vgui fix

next, in src\game\client\in_main.cpp inside method CInput::CreateMove ( ... ) add:

   cmd->buttons |= IN_VALIDVGUIINPUT;

right above:

   g_pClientMode->CreateMove( input_sample_frametime, cmd )

next, in src\cl_dll\c_baseplayer.cpp inside method C_BasePlayer::CreateMove( ... ) add:

   if(pCmd->buttons & IN_VALIDVGUIINPUT)

right above:

   DetermineVguiInputMode( pCmd );

(So it only calls DetermineVguiInputMode if the buttons include our flag)

and finally, inside method C_BasePlayer::DetermineVguiInputMode( ... ) change both instances of:

   pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2);

to read:

   pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2 | IN_VALIDVGUIINPUT);

Since an HL2 engine update in October VGUI Screens will crash when you put your mouse over them. This is because the g_InputInternal function used in \src\cl_dll\c_vguiscreen.cpp is no longer working. Trying to access it makes it crash. For a workaround, the following C_VGuiScreen::ClientThink( void ) function has proven useful:

	// Convert (u,v) into (px,py)
	int px = (int)(u * m_nPixelWidth + 0.5f);
	int py = (int)(v * m_nPixelHeight + 0.5f);

// START TEDDYS FIX
	for (int i = 0; i < pPanel->GetChildCount(); i++)
	{
		vgui::Button *child = dynamic_cast<vgui::Button*>(pPanel->GetChild(i));
		if ( child )
		{
			int x1, x2, y1, y2;
			child->GetBounds( x1, y1, x2, y2 );

			// Generate mouse input commands
			if ( (m_nButtonState & IN_ATTACK) )
			{
				if ( px >= x1 && px <= x1 + x2 && py >= y1 && py <= y1 + y2 )
					child->FireActionSignal();
			}
		}
	}
// FIN TEDDYS FIX

	if ( m_bLooseThinkNextFrame == true )
	{
		m_bLooseThinkNextFrame = false;
		SetNextClientThink( CLIENT_THINK_NEVER );
	}

Fix 2

An alternative fix would be to add a new boolean variable to CUserCmd such as bButtonFlagsValid that can be set depending on the validity of the button flags. This would allow the structure to internally store this information for its whole lifetime while freeing up that extra button bit.

Example screenshots

VGUI screen used in mod
VGUI screen used to display dynamically drawn EKG data (Pulse!! game)
VGUI screen used to control the mortar in Black Mesa.