VGUIコントロールの新規追加例



・(´・ω・`)ウインドウつくるよ
ダラダラ説明読むよりチャチャっと実際に作った方がよくわかるんですよね。

Valveのサンプルコードを元に、気に入らない所にちょいと手を加えた後のコードで ウインドウを作ってみましょう。


・ソースコードを用意しよー
VGUIはサーバ側の処理に何ら関係ないのでclient.dll のプロジェクトだけに手を加えればOKです。

client_sdkプロジェクトに

MyPanel.cpp
MyPanel.h

上記のソース、ヘッダファイルを追加します。
分かりやすいようにソリューションエクスプローラー上ではフォルダを一個作ってその中に入れるといいでしょう。


ではではそれぞれのファイルをこんなふうに入力してください〜♪
「おめ、長げぇ〜よバカ!」という人はここからファイルを直接どぞ〜、MyPanel.cppMyPanel.h
MyPanel.cpp
#include "cbase.h"
#pragma warning (disable: 4514)

// Frame、TextEntry、Button、ComboBox、
// 必要なコントロールのヘッダをincludeしておきましょうね
#include <vgui_controls/Frame.h>
#include <vgui_controls/TextEntry.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/ComboBox.h>
#include <KeyValues.h>

#include <vgui/IVGui.h>

// ボクちゃんのパネルのヘッダファイル
#include "MyPanel.h"

// いちいちコントロールクラスの前にvguiのnamespace名付けるの面倒なので
// ここにかいときますよ
using namespace vgui;

// これはどのソースにも入れとかないと定義上書きできねえがら!
// 何も考えずにいれときゃいいがら!
#include "tier0/memdbgon.h"

// コンソール値をつくるよ、
// 初期値=0、クライアント側だけの指定、説明文
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL,
			"My panel no hyouji wo youkyu suruyo <state>");


// ボクちゃんのパネルクラス定義
// Frameを継承してタイトルバー&枠付きウインドウをつくるのが目的
class CMyPanel : public Frame
{
	// これは
	// 自分自身のクラス(CMyPanel)の型をThisClass
	// スーパクラス(Frame)をBaseClass
	// で表記できるようにしてるだけ
	DECLARE_CLASS_SIMPLE(CMyPanel, Frame); 

	// コンストラクタ
	CMyPanel(VPANEL parent);
	// デストラクタ
	~CMyPanel(){};

protected:
	// 仮想関数をオーバーライドしちゃうよ
	virtual void OnTick();
	virtual void OnCommand(const char* pcCommand);

private:
	// エディットBOX
	TextEntry* m_pMyTextEntry;
	// ボタン
	Button* m_pMyButton;
	// コンボBOX
	ComboBox* m_pMyCombo;
};



// パネルの親指定には親パネルのオブジェクトポインタではなく、
// パネルハンドルを使用するため、BaseClassのコンストラクタ第一引数
// にNULLを指定している(Frameにはパネルハンドルを引数とするコンストラクタはない)
CMyPanel::CMyPanel(VPANEL parent)
: BaseClass(NULL, "MyPanel")
{
	// 親パネルを設定
	SetParent( parent );
	
	// キーボードの入力を許可
	SetKeyBoardInputEnabled( true );
	// マウスの入力を許可
	SetMouseInputEnabled( true );
	
	// フォントやコントロールの配置を最適な状態にする
	SetProportional( true );
	// タイトルバーの表示ON
	SetTitleBarVisible( true );
	// 最小化ボタンの表示OFF
	SetMinimizeButtonVisible( false );
	// 最大化ボタンの表示OFF
	SetMaximizeButtonVisible( false );
	// ×ボタンの表示ON
	SetCloseButtonVisible( true );
	// ウインドウサイズ変更不可
	SetSizeable( false );
	// ウインドウ移動可
	SetMoveable( true );
	// ウインドウを不可視に設定
	// Frameにはリソーススクリプトからのvisible指定は効かないので注意
	SetVisible( false );

	// エディットBOX作成
	m_pMyTextEntry = new TextEntry(this, "add_text");

	// ボタン作成(キャプションはリソーススクリプトで入れるから空でいい)
	m_pMyButton = new Button(this, "add_button", L"");

	// コンボBOX作成
	m_pMyCombo = new ComboBox(this, "added_combo", 3, false);

	// スキーム設定(コンソールと同じLook&Feelになります)
	SetScheme("SourceScheme");

	// コントロール設定のリソース読み込み
	LoadControlSettings("resource/UI/MyPanel.res");

	// このパネルのハンドルを引数にしてTickSignalを設定
	// 100ミリ秒単位にOnTickが呼ばれますよ
	vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );

	// ウインドウができますた〜♪
	Msg("Yatta-!Dekimasuta\n");
}

// TickSignalのサイクルごとに呼ばれるよ
// コンソール値cl_showmypanelの値を監視して"1"にセット
// されてたらウインドウを表示するよ
void CMyPanel::OnTick()
{
	// 実際、PanelもFrameもOnTickは空処理なんだけど念のため呼ぼうよ
	BaseClass::OnTick();
	// コンソール値cl_showmypanelがbool値でtrue(1)になっていたらウインドウの表示ON
	if (cl_showmypanel.GetBool()) {
		// ウインドウの表示ON
		SetVisible(true);
		// ウインドウをアクティブ状態にする
		Activate();
		// cl_showmypanelの値を"0"に戻す
		cl_showmypanel.SetValue(0);
	}
}

// コマンド受付
void CMyPanel::OnCommand(const char* pcCommand)
{
	// FrameのOnCommandも呼んでやらないと×ボタンのクリックが
	// 受け付けられない
	BaseClass::OnCommand(pcCommand);

	if(!Q_stricmp(pcCommand, "plz_add")) {
		// ボタンのコマンド"plz_add"が届いた
		char szTextbuf[128];

		// エディットBOXからテキストを取り出す
		m_pMyTextEntry->GetText(szTextbuf, sizeof( szTextbuf ));

		if (Q_strlen(szTextbuf) > 0) {
			// 取り出したテキストが空でなければコンボBOXのアイテムへ加える
			m_pMyCombo->AddItem(szTextbuf, NULL);
		}
	}
}

// こういう形でMyPanelを作らせなくとも、もっと乱暴に作りに行くことは
// 可能だけれど、ここは他のパネルやウインドウの行っている手順とあわせましょう
class CMyPanelInterface : public IMyPanel
{
private:
	CMyPanel *MyPanel;
public:
	CMyPanelInterface()
	{
		MyPanel = NULL;
	}
	void Create(VPANEL parent)
	{
		// MyPanelを作ります
		MyPanel = new CMyPanel(parent);
	}
	void Destroy()
	{
		if (MyPanel)
		{
			// MyPanelが作られていたら破棄します
			MyPanel->SetParent( (Panel *)NULL);
			delete MyPanel;
		}
	}
};

// モジュール内変数のポインタをグローバルポインタ変数に
// 放り込んでる
static CMyPanelInterface g_MyPanel;
IMyPanel* mypanel = &g_MyPanel;


MyPanel.h
#ifndef MYPANEL_H
#define MYPANEL_H

class IMyPanel
{
public:
	virtual void		Create( vgui::VPANEL parent ) = 0;
	virtual void		Destroy( void ) = 0;
};

extern IMyPanel* mypanel;

#endif // MYPANEL_H

コードの細かい説明は各所のコメントを読んでもらうとして、
全体について大まかに説明しますと、コンソール値cl_showmypanelの値を監視して、 値が"1"になったのを検出したらウインドウの表示を行ってます。
ウインドウの表示後、エディットBOXに文字を打ち込んで「Add Text」ボタンをクリック するとコンボBOXのアイテムにエディットBOXに入力した値が追加されます。そんだけ。

・ウインドウの作成と破棄のコードを埋め込みましょう
おっと、まだビルドしちゃいけませんよ、ソリューションエクスプローラの中からvgui_int.cpp ってファイルを探し出して開いてください。


指示に従って3箇所に変更を加えます。
vgui_int.cpp
25行目あたりに以下の赤く示した記述を挿入。
先程作ったヘッダファイルをインクルードしてます。
#include <vgui_controls/Panel.h>
#include <KeyValues.h>

#include "MyPanel.h"

using namespace vgui;

#include <vgui/IInputInternal.h>

vgui_int.cpp
159行目あたりに以下の赤く示した記述を挿入。
GameUI Panelを親パネルにしてCMyPanelの実体を作成しています。
	loadingdisc->Create( gameParent );
	messagechars->Create( gameParent );

	mypanel->Create(enginevgui->GetPanel( PANEL_GAMEUIDLL ));

	// Debugging or related tool
	fps->Create( toolParent );

vgui_int.cpp
175行目あたりに以下の赤く示した記述を挿入。
CMyPanelクラスの実体を破棄しています。
	netgraphpanel->Destroy();
	fps->Destroy();

	mypanel->Destroy();

	messagechars->Destroy();
	loadingdisc->Destroy();

はい、client.dll側の変更は以上です。 client_sdkプロジェクトをビルドして client.dllを作ってください。

・リソーススクリプトを用意しよー
次はMODフォルダから更に下の resource/uiフォルダ下へ

MyPanel.res

こんな名前のファイルを作り、テキストエディタで以下の内容を入力します。
「おめぇ、長げぇ〜つってんだよバカ!」という人はここからファイルを直接どぞ〜、MyPanel.res
MyPanel.res
"Resource/UI/MyPanel.res"
{
	"MyPanel"
	{
		"ControlName"	"CMyPanel"
		"fieldName"	"MyPanel"
		"title"		"Hajimeteno VGUI"
		"xpos"		"100"
		"ypos"		"100"
		"wide"		"300"
		"tall"		"100"
		"autoResize"	"0"
		"pinCorner"	"0"
		"visible"		"0"
		"enabled"		"1"
		"tabPosition"	"0"
		"setTitleBarVisible"	"1"
	}

	"add_text"
	{
		"ControlName"	"TextEntry"
		"fieldName"	"add_text"
		"xpos"		"50"
		"ypos"		"25"
		"wide"		"200"
		"tall"		"20"
	}

	"added_combo"
	{
		"ControlName"	"ComboBox"
		"fieldName"	"added_combo"
		"xpos"		"50"
		"ypos"		"50"
		"wide"		"200"
		"tall"		"20"
		"editable"		"0"
	}

	"add_button"
	{
		"ControlName"	"Button"
		"fieldName"	"add_button"
		"xpos"		"100"
		"ypos"		"75"
		"wide"		"100"
		"tall"		"20"
		"autoResize"	"0"
		"pinCorner"	"2"
		"visible"		"1"
		"enabled"		"1"
		"tabPosition"	"0"
		"labelText"	"Add Text"
		"textAlignment"	"center"
		"dulltext"		"0"
		"brighttext"	"0"
		"Command"		"plz_add"
		"Default"		"1"
	}
}

・実行してみる
HL2を起動しますよ


起動したらコンソールを開いて以下のコマンドを入力します。
すると「もそっ」っと「Hajimeteno VGUI」ウインドウが現れるはずです。
cl_showmypanel 1


じゃーん


ちゃんと想定していた通りの動きをしているか試してみよう。


・もっとスマートに登場させる
コンソールからいちいちコマンド打ってウインドウを開くのはダサいですよね。
なので改良します。
まず、MyPanel.cppに変更を加えてclient_sdkプロジェクトをビルドしてください。

MyPanel.cpp
30行目あたりに以下の赤く示した記述を挿入。
ShowMyPanelコンソールコマンドを追加してます。
// コンソール値をつくるよ、
// 初期値=0、クライアント側だけの指定、説明文
ConVar cl_showmypanel("cl_showmypanel", "0", FCVAR_CLIENTDLL,
			"My panel no hyouji wo youkyu suruyo <state>");

// ボクちゃんのパネルを表示するコマンドを追加
CON_COMMAND(ShowMyPanel, "My panel wo hyouji suruyo!")
{
	cl_showmypanel.SetValue(1);
};

// ボクちゃんのパネルクラス定義
// Frameを継承してタイトルバー&枠付きウインドウをつくるのが目的


次はMODフォルダから更に下の resourceフォルダ下にある

GameMenu.res

をテキストエディタで開きます。
シングルなら"1"〜"13"、デスマッチなら"1"〜"11"までメニューの要素が並んで ますから、末尾へ新規のメニューアイテムを追加します。
GameMenu.res
デスマッチの場合のメニューアイテム追加例、赤い部分がテキストの挿入部分
	}
	"11"
	{
		"label" "#GameUI_GameMenu_Quit"
		"command" "Quit"
	}
	"12"
	{
		"label" "Oresama Senyo"
		"command" "engine ShowMyPanel"
	}
}


それではHL2を起動してみましょう。


( ゚∀゚)俺専メニューキターー!!

そんで、それをクリックすると

ハイ、このとおり。

(´・ω・`)つもどる