HUDへのパネル新規追加例



・(´・ω・`)パネルつくるよ
俺ァ、ハッキリHUDにパネルつぐるがら。


・なんか表示してみよー
VGUIはサーバ側の処理に何ら関係ないのでclient.dll のプロジェクトだけに手を加えればOKです。

client_sdkプロジェクトに

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


ファイルができたらこんなふうに入力してください〜♪
コピペすらメンドい人はここからファイルを直接どぞ〜、MyHudParts.cpp
MyHudParts.cpp
// 必要なもんをインクルードしときますよ
#include "cbase.h"
#include "hudelement.h"
#include <vgui_controls/Panel.h>
#include "iclientmode.h"

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

// ボクちゃんのHUD部品パネルの宣言
class CMyHudParts : public CHudElement, public vgui::Panel
{
public:
	// これは
	// 自分自身のクラス(CMyHudParts)の型をThisClass
	// スーパクラス(Panel)をBaseClass
	// で表記できるようにしてるだけ
	DECLARE_CLASS_SIMPLE( CMyHudParts, vgui::Panel );

	// コンストラクタ
	CMyHudParts( const char *pElementName );

	// 仮想関数をオーバーライドしちゃうよ
	virtual void Paint();
};

// このマクロにクラス名を渡しておけば
// HUDヘルパがHUD初期化時にこの部品パネルを勝手に作ってくれる
DECLARE_HUDELEMENT( CMyHudParts );

// HUDヘルパから呼ばれた場合pElementNameには"CMyHudParts"
// がセットされてくる
// PanelのコンストラクタにはHudLayout.resで指定するフィールド名を渡しといて
// CHudElementのコンストラクタにpElementName渡しとく
CMyHudParts::CMyHudParts(const char *pElementName) :
	vgui::Panel( NULL, "MyHudParts" ), CHudElement( pElementName ) {

	// ビューポートパネルを親パネルにしちゃう
	SetParent( g_pClientMode->GetViewport() );
}

// 描画ルーチン
void CMyHudParts::Paint() {
	// とりあえずスーパークラスのPaintをそのまま呼んじゃえ
	BaseClass::Paint();
}

コードの細かい説明は各所のコメントを読んでもらうとして、client_sdkプロジェクトをビルドしちゃってください。

次はMODフォルダ下のscripts/HudLayout.resをテキストエディタで開き、 各HUD部品のレイアウト設定末尾へ、以下の赤く記した部分を挿入します。
HudLayout.res
		"TextColor"	"255 255 255 192"

	}

	MyHudParts
	{
		"fieldName"	"MyHudParts"
		"visible"		"1"
		"xpos"		"c-50"
		"ypos"		"r150"
		"wide"		"100"
		"tall"		"100"

		"PaintBackgroundType"	"2"
	}
}
"PaintBackgroundType"に指定されている"2"は「角の丸いBOXを背景にしますよ」という指定です。 "0"に指定すると単純なBOXに、"1"に指定するとテクスチャを背景に使用するのですが、今はそんなことどうでもいい〜♪

ささ、ゲーム開始です。画面中心から下に行ったとこへ目障りなBOXが出るはずです。


・BOXん中に何か表示してみよー
BOXが出てるだけじゃ「ふ〜ん」って感じですね。そこでBOXの中に文字を表示してみましょう。 PanelクラスのPaintをオーバーライドしてあるので、そこへ描画処理を追加します。
MyHudParts.cppに以下の変更を加えてくらはい。

MyHudParts.cppの7行目あたりに以下の赤く記した行を挿入
#include "hudelement.h"
#include <vgui_controls/Panel.h>
#include "iclientmode.h"

#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>

// これはどのソースにも入れとかないと定義上書きできねえがら!


MyHudParts.cppのPaintメンバ関数の内容を以下の内容に変更
// 描画ルーチン
void CMyHudParts::Paint() {
	// 実際なんもしてないけどスーパークラスのPaintを呼んどこ
	BaseClass::Paint();

	// このパネルの幅を取得
	int width = this->GetWide();
	// このパネルの高さを取得
	int height = this->GetTall();

	// ClientSchemeのスキームハンドルを取得
	vgui::HScheme hScheme = vgui::scheme()->GetScheme( "ClientScheme" );
	// ClientSchemeで"Default"に定義されているフォントのハンドルを取得
	vgui::HFont hFont = vgui::scheme()->GetIScheme(hScheme)->GetFont( "Default" );

	// フォントの高さを得る
	int text_height = vgui::surface()->GetFontTall ( hFont );

	// 描画面にフォントを設定
	vgui::surface()->DrawSetTextFont( hFont );
	// 描画面にテキストの描画色を設定
	vgui::surface()->DrawSetTextColor( 255, 255, 255, 200  );

	// ANSI形式の"HANA MOGERA"をUNICODEに変換
	wchar_t text[256];
	vgui::localize()->ConvertANSIToUnicode( "HANA MOGERA", text, sizeof( text ) );

	// "HANA MOGERA"を描画した場合の横幅を計算
	int text_width = 0; 
	for ( wchar_t *pt = text; *pt != 0; pt++ )
	{
		// 1文字ずつ描画時の幅を加算
		text_width += vgui::surface()->GetCharacterWidth( hFont, *pt );
	}

	// テキストのX座標を決める
	int text_xpos = width / 2 - text_width / 2;

	// テキストのY座標を決める
	int text_ypos = height / 2 - text_height / 2;

	// 描画面にテキストの描画位置を指定
	vgui::surface()->DrawSetTextPos( text_xpos, text_ypos );

	// テキストの描画処理
	for ( pt = text; *pt != 0; pt++ )
	{
		// 1文字ずつ描画
		vgui::surface()->DrawUnicodeChar( *pt );
	}
}


処理内容はパネルの真ん中に"HANA MOGERA"と表示しているだけです。
グローバルで引っ張ってきたvgui::surface()のDrawSetTextPosを呼び出しているところでは X、Y座標はどこを基準にしているのかな?と思うでしょうが、パネルの左上を原点とした座標 でちゃんと扱ってくれるようです(Windowsの場合だとクライアント座標)。

Yes,HANA MOGERA!!


・アニメーションさしてみる
ゲーム中、発砲したり、ダメージ食らった際にはHUD上に様々な視覚的効果がみられます。
今度はこのアニメーション効果を付けてみましょう。

MyHudParts.cppへ更に以下の変更を加えてくらはい。いっぱいあるので間違えないように。

MyHudParts.cppの10行目あたりに以下の赤く記した行を挿入
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>

#include <vgui_controls/AnimationController.h>

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


MyHudParts.cppのCMyHudPartsクラス宣言中に以下の赤く記した行を挿入
	// コンストラクタ
	CMyHudParts( const char *pElementName );

	// 仮想関数をオーバーライドしちゃうよ
	virtual void Paint();
	virtual void OnThink();
private:
	CPanelAnimationVarAliasType( float, label_ypos, "label_ypos", "10", "proportional_float" );
	// 加速中フラグ
	bool m_bAccel;
};


MyHudParts.cppのCMyHudPartsコンストラクタ中に以下の赤く記した行を挿入
CMyHudParts::CMyHudParts(const char *pElementName) :
	vgui::Panel( NULL, "MyHudParts" ), CHudElement( pElementName ) {

	// ビューポートパネルを親パネルにしちゃう
	SetParent( g_pClientMode->GetViewport() );

	// 加速中フラグ初期化
	m_bAccel = false;
}


MyHudParts.cppのPaintメンバ関数中でテキストのY座標を指定している部分を赤く記した内容に変更
	// テキストのX座標を決める
	int text_xpos = width / 2 - text_width / 2;

	// テキストのY座標を決める
	int text_ypos = label_ypos;

	// 描画面にテキストの描画位置を指定
	vgui::surface()->DrawSetTextPos( text_xpos, text_ypos );


MyHudParts.cppの末尾にOnThinkメンバ関数の定義を追加します(下記丸ごと)
// この処理はPaintが呼ばれる前に毎フレーム呼ばれます
void CMyHudParts::OnThink() {
	// プレイヤーが存在しない場合には何も処理させません
	C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
	if ( !pPlayer ) {
		return;
	}

	// プレイヤーの各方向の加速を得る
	Vector vel = pPlayer->GetAbsVelocity();

	// XとZ方向の加速いずれかが1.0を超えていたら移動中とみなす
	bool moving = (fabs(vel.x) > 1.0f || fabs(vel.z) > 1.0f);

	// 移動中と停止を検知してそれぞれのアニメーションを開始
	if (!m_bAccel && moving) {
		// 移動を検知
		m_bAccel = true;
		// アニメーション開始(フェードイン)
		g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MyHudPartsFadeIn" );
	} else if (m_bAccel && !moving) {
		// 停止を検知
		m_bAccel = false;
		// アニメーション開始(フェードアウト)
		g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MyHudPartsFadeOut" );
	}
}

なにをやってんのか分かんないかもしれませんが、後の説明でそこらへんをからめていくとして、とっととビルドしちまってください。


次はMODフォルダ下のscripts/HudAnimations.txtを開き、ファイル末尾へ以下の赤く記したイベント定義を 追加します。アニメーションのイベントスクリプトが書いてあんですよ。
event PoisonLoop
{
	RunEvent PoisonPulse 0.0
}

event MyHudPartsFadeIn
{
	Animate MyHudParts	Alpha	255	Linear 0.0 1.0
	Animate MyHudParts	label_ypos	10	Linear 0.0 1.0
}

event MyHudPartsFadeOut
{
	Animate MyHudParts	Alpha	0	Linear 0.0 1.0
	Animate MyHudParts	label_ypos	80	Linear 0.0 1.0
}

どうすか?プレイヤーが横に移動しはじめるとパネルすぅ〜と現れ始め、"HANA MOGERA"の文字列が 上に移動します。逆に立ち止まるとパネルがしゅ〜んと消え始め、"HANA MOGERA"の文字列が下に 移動します。

VIVA! HANA MOGERA!!


・アニメーションとメンバ変数の対応付け
さて、上でアニメーションを実現させるためには2つのメンバ変数を使用していました。

  • m_flAlpha

  • こいつはPanelクラスが既に持ってるprivateメンバ変数です。パネルの背景を描画する際のアルファ値として使用されて おり、Panelクラスの宣言中でCPanelAnimationVarマクロによって宣言されています。
    CPanelAnimationVar( float, m_flAlpha, "alpha", "255" );
    

  • label_ypos

  • こっちの方はCMyHudPartsクラスの宣言中でCPanelAnimationVarAliasTypeマクロによって宣言されています。
    CPanelAnimationVarAliasType( float, label_ypos, "label_ypos", "10", "proportional_float" );
    


    これらのマクロは何やってんでしょうかね?それぞれの働きを見てみましょう。


    CPanelAnimationVarマクロ
    CPanelAnimationVarマクロを使う場合はこんな書き方をします

    CPanelAnimationVar([型], [変数名], [エイリアス名], [初期値])

    [型]の部分に指定できるのは以下の型だけです。他の型 を指定したって放置されっぱなしになるメンバ変数が一個できるだけですよ。
    float 普通の小数値です。スクリプト中でも"1.0"だの"2.5"って感じで書くだけ。
    int 整数値です。
    Color 色情報。
    スクリプト中には"100 0 0 80"といったRGBA表記と スキーム中の色定義名称を"BgColor"のように指定する2種類の書き方で指定できます。
    bool bool値。
    スクリプト中で"0"以外の整数値を指定した場合にtrue。 "0"を指定した場合のみfalseと扱われます。
    char 見てのとおり文字でやんす。
    スクリプト中で"A"とか"D"とか書いてくださいよ。
    string 見てのとおり文字列・・・なんだけど。
    大体stringって型が定義されてねーからコンパイル通らねえよ。
    HFont または vgui::HFont フォント情報。 スクリプト中にはスキーム中のフォント定義名称を"HudNumbersSmall"のように指定する。 変数にはそのフォントのフォントハンドルが入る。

    [変数名]は変数名そのものですね。
    [エイリアス名]はアニメーションのイベントスクリプト中で使用する名前です。このエイリアスを介して 発行された数値補完指令によって[型]に対応したコンバータが[変数名]の値を変化させます。
    [初期値]はアニメーションのイベントスクリプト中で表記する値と同じ書き方で指定します。


    CPanelAnimationVarAliasTypeマクロ
    CPanelAnimationVarAliasTypeマクロを使う場合はこんな書き方をします

    CPanelAnimationVarAliasType([型], [変数名], [エイリアス名], [初期値], [型エイリアス]);

    [型][型エイリアス]の組み合わせはもう以下の3つしかありませんね、たぶん。

    CPanelAnimationVarAliasTypeの型と型エイリアス組み合わせ
    型エイリアス
    float proportional_float スクリーンサイズ依存の座標値をfloat型で扱う場合。
    int proportional_int スクリーンサイズ依存の座標値をint型で扱う。
    int textureid テクスチャIDを扱う場合。
    スクリプト中で"vgui/hud/800corner1"などと表記されたテクスチャを読込んで割り当てられた テクスチャIDが変数にセットされる。


    要するにさっきの改造ではm_flAlphalabel_yposって2つのメンバ 変数の値がアニメーションのイベントスクリプトに書かれた内容に従って変化してたんだわ。

    ・アニメーションのイベントスクリプト
    さっき追加したアニメーションのイベントは以下の2つでしたね。
    event MyHudPartsFadeIn
    {
    	Animate MyHudParts	Alpha	255	Linear 0.0 1.0
    	Animate MyHudParts	label_ypos	10	Linear 0.0 1.0
    }
    
    event MyHudPartsFadeOut
    {
    	Animate MyHudParts	Alpha	0	Linear 0.0 1.0
    	Animate MyHudParts	label_ypos	80	Linear 0.0 1.0
    }
    


    そんでもってそれぞれのイベントを発動してんのはMyHudParts.cppOnThinkメンバ関数で実行している以下の2箇所の処理です。
    // アニメーション開始(フェードイン)
    g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MyHudPartsFadeIn" );
    
    // アニメーション開始(フェードアウト)
    g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MyHudPartsFadeOut" );
    


    さて、ここまでくれば残った疑問はアニメーションのイベントスクリプトの内容だけとなりました。
    アニメーションのイベント定義では"event [イベント名]"に続けて中括弧で囲まれた 中へアニメーションのコマンドを書込みます。
    コマンドは8種類あります(各コマンドの[ディレイ]コマンド発行までのディレイを秒単位で指定します)。

    コマンド 説明
    Animate 表記:
    Animate [パネル名] [エイリアス名] [ターゲット値] [補完方法] [ディレイ] [実行時間]

    指定されたパネルのエイリアスの現在値を[実行時間]の時間内に[ターゲット値]へ指定した値まで変化させる。
    その際の数値補完方法として[補完方法]に指定できる物は以下の6つどす
    Linear 直線的に値は変化する。
    Accel 最初は変化量が小さく、徐々に変化量が増加していく。
    Deaccel 最初は変化量が大きく、徐々に変化量が減少していく。
    Spline スプライン補完で値がカーブを描いて変化するが、実行時間が短いと Linearとの違いを体感できない。
    Pulse [補完パラメタ] この設定は[補完パラメタ]を指定する必要がある。
    0.0から1.0の値を取る余弦波にLinear指定時に得られる直線補完値を掛け合わせた値が 得られる。[ターゲット値]を1.0よりも小さくしていくにつれて 周期は長くなる。
    Flicker [補完パラメタ] 点滅させる効果に使われ、この設定は[補完パラメタ]を指定する必要がある。
    [補完パラメタ]に0.0〜1.0の値を指定してやると、 0.0かLinear指定時に得られる直線補完値いずれかの値がランダムに得られる、[補完パラメタ]を 小さくするほど0.0が得られる確率が高くなる。
    Runevent 表記:
    Runevent [イベント名] [ディレイ]

    指定したイベントを発行する。
    Stopevent 表記:
    Stopevent [イベント名] [ディレイ]

    指定したイベント処理が実行中なら停止する。
    StopPanelAnimations 表記:
    StopPanelAnimations [パネル名] [ディレイ]

    指定されたパネルに属する全てのエイリアスに対して実行中のアニメーション処理を停止する。
    Stopanimation 表記:
    Stopanimation [パネル名] [停止対象エイリアス名] [ディレイ]

    指定されたエイリアスに対して実行中のアニメーション処理を停止する。
    SetFont 表記:
    SetFont [パネル名] [フォントハンドル設定先エイリアス名] [フォント名] [ディレイ]

    フォントを直に指定したい場合に使用。[フォント名]にはスキームに定義されているフォント定義名を指定する。
    SetTexture 表記:
    SetTexture [パネル名] [テクスチャID設定先エイリアス名] [テクスチャ名] [ディレイ]

    テクスチャを直に指定したい場合に使用。
    SetString 表記:
    SetString [パネル名] [値設定先エイリアス名] [文字列] [ディレイ]

    文字列のみではなくfloatやintのエイリアスに対して直に値を指定する場合にも使用できる。


    エイリアス名については予約済みエイリアスも存在します。
    CPanelAnimationVarマクロやCPanelAnimationVarAliasTypeマクロを使うことなく 初期の状態から以下のエイリアスは使用できます。
    エイリアス
    position パネルの位置が対象。X座標とY座標を"100 100"のように指定。 内部で解像度依存のスケールに変換されます。
    size パネルのサイズが対象。幅と高さを"200 100"のように指定。 内部で解像度依存のスケールに変換されます。
    fgcolor パネルの前景色が対象。RGBA形式で"0 100 128 200"のように指定。
    bgcolor パネルの背景色が対象。RGBA形式で"0 100 128 200"のように指定。
    xpos パネルのX座標が対象。内部で解像度依存のスケールに変換されます。
    ypos パネルのY座標が対象。内部で解像度依存のスケールに変換されます。
    wide パネルの幅が対象。内部で解像度依存のスケールに変換されます。
    tall パネルの高さが対象。内部で解像度依存のスケールに変換されます。

    ・スクリプト無しでアニメーション
    アニメーションのしくみが分かったところで、スクリプト無しでアニメーションを実行する方法 についてちょっと書いときます。

    これはHUDよりも、その他のVGUIでアニメーション効果を得たい場合に使われ、 スクリプトからAnimateコマンドを指定した時と同じ処理になります。
    vgui::GetAnimationController()->RunAnimationCommand(
    				[VGUIコントロール],
    				[エイリアス名],
    				[ターゲット値],
    				[ディレイ],
    				[実行時間],
    				[補完方法]
    );
    


    [補完方法]には以下の定数定義値を指定。
    AnimationController::INTERPOLATOR_LINEAR スクリプトの"Linear"と同じ。
    AnimationController::INTERPOLATOR_ACCEL スクリプトの"Accel"と同じ。
    AnimationController::INTERPOLATOR_DEACCEL スクリプトの"Deaccel"と同じ。
    AnimationController::INTERPOLATOR_PULSE スクリプトの"Pulse"と同じ。
    但し、[補完パラメタ]に該当する値を指定できないので使用不可。
    AnimationController::INTERPOLATOR_FLICKER スクリプトの"Flicker"と同じ。
    但し、[補完パラメタ]に該当する値を指定できないので使用不可。
    AnimationController::INTERPOLATOR_SIMPLESPLINE スクリプトの"Spline"と同じ。


    Frameクラスでウインドウがフェードアウトしていくところの処理を抜粋しました。
    GetAnimationController()->RunAnimationCommand(this,
    		"alpha", 0.0f, 0.0f, m_flTransitionEffectTime, AnimationController::INTERPOLATOR_LINEAR);
    

    (´・ω・`)つもどる