VGUI on entity

From Valve Developer Community
Jump to navigation Jump to search

Intro

This tutorial will take you through how to make a vgui screen on an entity. It was originally from wavelength (http://articles.thewavelength.net/675/) But needed some updates.


Original Tutorial by Ask Jensen AKA Crazy01 ([email protected])

Updated by: --Lodle 02:13, 29 Nov 2007 (PST)

Modifying the RPG model

Lucky for us valve included the RPG model's source in the SDK, so we don't even have to brush up our modeling skills. Start up the model viewer that came with the SDK (it's in the "Source SDK" menu that's under "tools" in the "Play games" part of Steam). (NOTE: Make sure the Current Game drop down in the Source SDK tool is set to your mod before launching model viewer!) When it's loaded, go to File->Load Model... Then go to weapons\v_RPG.mdl. You should now see the RPG model in front of you. Go to the "Attachments" tab.

Mdl 2-1-.jpg

There's a list of attachments already. By clicking through them, you can see that they're all attached to "muzzle". Now, how do we create our own? We can't do that in here. But what we can do is find out where to place the two points. Select 0 on the list of attachments. Try changing the numbers in the Translation tekstbox. See the point move? Now, try and place the point where you would want you upper right corner. I used the value -10.00 8.00 0.00. Now change the Rotate numbers so the green line is pointing up, the red is pointing right and the blue pointing towards the player: -90 0 0. We have our first attachment point! Now, copy the text in the field named QC string and save it. It should read: $attachment "0" "muzzle" -10.00 8.00 0.00 rotate -90 0 0

Mdl 3-1-.jpg

Now we need to find the lower left attachment point. Let's make the screen 8x8, so the lower left translation should be -10.00 0.00 -8.00. Again, we need the QC string, so copy that from the textbox. We're done with the model viewer, so you can close it down.

Next, we need to actually modify the RPG model. Go to your steam folder\SteamApps\<steam-account>\sourcesdk_content\hl2\modelsrc\weapons\v_rocket_launcher\ The files you see are the source files for the RPG model. Open the V_Rocket_launcher.qc file. After the $attachment points paste your QC strings you just got from the model viewer, but change the 0 to "controlpanel0_ur" and "controlpanel0_ll" like so:

 
// attachment points
$attachment 0 "muzzle" 0 0 0
$attachment "laser" "muzzle" -9.00 -5.00 0.50
$attachment "laser_end" "muzzle" 100.00 -5.00 0.50

// Ask: attachment points for vguiscreen
$attachment "controlpanel0_ur" "muzzle" -10.00 8.00 0.00 rotate -90 0 0
$attachment "controlpanel0_ll" "muzzle" -10.00 0.00 -8.00 rotate -90 0 0

Now we need to compile the RPG with the new attachment points.

Open up command promt and go to your SteamApps\<steam-account>\sourcesdk\bin folder. now type in:

studiomdl ..\..\sourcesdk_content\hl2\modelsrc\weapons\v_rocket_launcher\V_Rocket_launcher.qc

The model should now compile, and if Steam is set up correctly your compiled models should be in your <modname>\models\Weapons\ directory. To check if everything went well, launch model viewer again, load up the RPG model and check if the attachment points are there. If not, try going through the steps again, if so: Wohoo! We now have the model ready for our VGUIScreen!


Adding GetControlPanelInfo() to CWeaponRPG

This is pretty simple. In weapon_rpg.h (in the dlls\hl2_dll\ folder) around line 182 under Drop() add:

 
    virtual void Drop( const Vector &vecVelocity );

    // Ask: Get the name of our attached vguiscreen
    void    GetControlPanelInfo( int nPanelIndex, const char *&pPanelName);

    int     GetMinBurst() { return 1; }

and in weapon_rpg.cpp (also under Drop()) add:

 
//-----------------------------------------------------------------------------
// Purpose: Ask: Get the name of our attached vguiscreen
//-----------------------------------------------------------------------------
void CWeaponRPG::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
    pPanelName = "rpg_screen";
}

That's it. The name of the VGUIScreen we'll add in vgui_screens.txt will be "rpg_screen".


Adding the screen to vgui_screens.txt

Open up vgui_screens.txt (It's in your mod's folder under scripts). Add a new screen under teleport_countdown_screen like this:

 
    "rpg_screen"
    {
        // Ask: This is the vguiscreen attached to the RPG
        // this type is defined in c_rpg_screen.cpp
        "type"          "rpg_screen"

        // 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"       "scripts/screens/rpg_screen.res"
    }

There isn't very much to be explained here. We'll create the new screen type "rpg_screen" for the RPG later on. First we need to create the .res file.


Creating the layout (.res) file

Go to the screens directory in your scripts folder. There's already two files there; vgui_test_screen.res and teleport_countdown_screen.res. Let's take a look at vgui_test_screen.res:

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

        "material"      "vgui/screens/vgui_bg"
    }

    "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"
        ...
    }
}

As you can see, there are 4 controls in here. A MaterialImage, two Labels and a MaterialButton (If you're unsure about what I'm talking about, have a look at the Source SDK documentation here). I suggest we change this file to suit our needs. First, change line 1 from "screen_basic.res" to "rpg_screen.res". Then remove the MaterialButton "DismantleButton". We now have "Background", "OwnerReadout" and "HealthReadout" left. The background is simply an image, so let's keep that in. Change the "OwnerReadout"'s name and fieldName to "AmmoReadout" and the labelText to "Ammo:". This will just be a static label. Change the "HealthReadout"'s name and fieldName to "AmmoCountReadout" and the labelText to "0", save the file as "rpg_screen.res" in your scripts\screens\ folder. It should now look like this:

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

        "material"      "vgui/screens/vgui_bg"
    }

    "AmmoReadout"
    {
        "ControlName"   "Label"
        "fieldName"     "AmmoReadout"
        "xpos"          "10"
        "ypos"          "20"
        "wide"          "240"
        "tall"          "34"
        "autoResize"    "0"
        "pinCorner"     "0"
        "visible"       "1"
        "enabled"       "1"
        "tabPosition"   "0"
        "labelText"     "Ammo:"
        "textAlignment" "center"
        "dulltext"      "0"
        "paintBackground" "0"
    }

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

We now have our layout ready for our VGUIScreen. But this is all static. We still need to make the screen type that sets the AmmoCountReadout label to the number of missiles left.


Creating the "rpg_screen" screen type

Create a new file in your cl_dll\hl2_hud\ directory named c_rpg_screen.cpp and add it to your client project. Write the following:

 
//=============================================================================//
//
// Purpose: Ask: This is a screen we'll use for the RPG
//
//=============================================================================//
#include "cbase.h"

#include "C_VGuiScreen.h"
#include <vgui/IVGUI.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/Label.h>
#include "clientmode_hlnormal.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

Not much to explain here. Just the includes we'll need in our code.

 
//-----------------------------------------------------------------------------
//
// In-game vgui panel which shows the RPG's ammo count
//
//-----------------------------------------------------------------------------
class CRPGScreen : public CVGuiScreenPanel
{
    DECLARE_CLASS( CRPGScreen, CVGuiScreenPanel );

public:
    CRPGScreen( vgui::Panel *parent, const char *panelName );

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

private:
    vgui::Label *m_pAmmoCount;
};

Our RPGScreen class. Notice that under private we define our m_pAmmoCount pointer as a vgui::Label. This will be the label we'll update.

//-----------------------------------------------------------------------------
// Standard VGUI panel for objects
//-----------------------------------------------------------------------------
DECLARE_VGUI_SCREEN_FACTORY( CRPGScreen, "rpg_screen" );


//-----------------------------------------------------------------------------
// Constructor:
//-----------------------------------------------------------------------------
CRPGScreen::CRPGScreen( vgui::Panel *parent, const char *panelName )
    : BaseClass( parent, panelName, g_hVGuiCombineScheme )
{
}

Here we let Source know that the "rpg_screen" screen type is actually our CRPGScreen. As you can see in the constructor, we use the g_hVGuiCombineScheme scheme for our rpg_screen.res file. We could create our own, but this is alright for now.

	 
//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
bool CRPGScreen::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData )
{
    // Load all of the controls in
    if ( !BaseClass::Init(pKeyValues, pInitData) )
        return false;

    // Make sure we get ticked...
    vgui::ivgui()->AddTickSignal( GetVPanel() );

    // Ask: Here we find a pointer to our AmmoCountReadout Label and store it in m_pAmmoCount
    m_pAmmoCount =  dynamic_cast<vgui::Label*>(FindChildByName( "AmmoCountReadout" ));

    return true;
}

As you can see, we get ahold of our Label in here. We also make sure we get ticked so we can update the Label.

//-----------------------------------------------------------------------------
// Ask: Let's update the label, shall we?
//-----------------------------------------------------------------------------
void CRPGScreen::OnTick()
{
    BaseClass::OnTick();

    // Get our player
    CBasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
    if ( !pPlayer )
        return;

    // Get the players active weapon
    CBaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon();

    // If pWeapon is NULL or it doesn't use primary ammo, don't update our screen
    if ( !pWeapon || !pWeapon->UsesPrimaryAmmo() )
        return;

    // Our RPG isn't clip-based, so we need to check the player's arsenal of rockets
    int ammo1 = pPlayer->GetAmmoCount( pWeapon->GetPrimaryAmmoType() );

    // If our Label exist
    if ( m_pAmmoCount )
    {
        char buf[32];
        Q_snprintf( buf, sizeof( buf ), "%d", ammo1 );
        // Set the Labels text to the number of missiles we have left.
        m_pAmmoCount->SetText( buf );
    }
}

Adding the Define

The vgui panel code is not normaly added in to a standard compile. To include it you must define VGUI_CONTROL_PANELS in the server project only! Goto your server project properties and select C/C++ -> Preprocessor Definitions and add it to the list.


Fixing Valves Code

In the current sdk (ep1) there is a bug in the code which will mean your panels never get created. In the file goto the function void CBaseViewModel::SpawnControlPanels() and replace it with the following:

//-----------------------------------------------------------------------------
// This is called by the base object when it's time to spawn the control panels
//-----------------------------------------------------------------------------
void CBaseViewModel::SpawnControlPanels()
{
#if defined( VGUI_CONTROL_PANELS )
	char buf[64];

	// Destroy existing panels
	DestroyControlPanels();

	CBaseCombatWeapon *weapon = m_hWeapon.Get();

	if ( weapon == NULL )
		return;

	MDLCACHE_CRITICAL_SECTION();

	// FIXME: Deal with dynamically resizing control panels?

	// If we're attached to an entity, spawn control panels on it instead of use
	CBaseAnimating *pEntityToSpawnOn = this;
	char *pOrgLL = "controlpanel%d_ll";
	char *pOrgUR = "controlpanel%d_ur";
	char *pAttachmentNameLL = pOrgLL;
	char *pAttachmentNameUR = pOrgUR;


	Assert( pEntityToSpawnOn );

	// Lookup the attachment point...
	int nPanel;
	for ( nPanel = 0; true; ++nPanel )
	{
		Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
		int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);

		Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
		int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);

		if (nLLAttachmentIndex <= 0)
			return;

		if (nURAttachmentIndex <= 0)
			return;

		const char *pScreenName;
		weapon->GetControlPanelInfo( nPanel, pScreenName );
		if (!pScreenName)
			continue;

		const char *pScreenClassname;
		weapon->GetControlPanelClassName( nPanel, pScreenClassname );
		if ( !pScreenClassname )
			continue;

		// Compute the screen size from the attachment points...
		matrix3x4_t	panelToWorld;
		bool a = pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );

		matrix3x4_t	worldToPanel;
		MatrixInvert( panelToWorld, worldToPanel );

		// Now get the lower right position + transform into panel space
		Vector lr, lrlocal;
		bool b = pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
		MatrixGetColumn( panelToWorld, 3, lr );
		VectorTransform( lr, worldToPanel, lrlocal );


		float flWidth = lrlocal.x;
		float flHeight = lrlocal.y;

		CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );

		pScreen->ChangeTeam( GetTeamNumber() );
		pScreen->SetActualSize( flWidth, flHeight );
		pScreen->SetActive( true );
		pScreen->MakeVisibleOnlyToTeammates( false );
		pScreen->SetAttachedToViewModel( true );
		int nScreen = m_hScreens.AddToTail( );
		m_hScreens[nScreen].Set( pScreen );

	}
#endif
}


Conclusion

As the commenting say, we first get the local player, then its weapon, get the number of rockets and then update our label. And we're done! Compile and run your mod. If all went well your RPG now has a screen telling us how much ammo we have left!

Okay, not the prettiest in the world, but it's a start :)

Ingame-1-.jpg