Communitygithub.com

Source 2 | カスタムHUDレイアウト

マップやサーバーサイドプラグインが、Panorama XML + VCSS で作成されたHUDをプレイヤーに表示できるようにするエンティティとワークフローのコレクションです。

Source 2 | カスタムHUDレイアウト とは?

このエージェントスキルは、Source 2 エンジンにおける`custom_hud_layout`エンティティの詳細な実装ガイドを提供します。このエンティティを使用すると、マップやサーバーサイドプラグインが、制限されたXML要素とCSSプロパティのみを使用して、PanoramaベースのカスタムHUDをプレイヤーに表示できます。スキルは、エンティティのセットアップ、許可されたパネルタイプ(Panel, Label, Image, Button)、SVGアイコン、カスタムフォント、ローカライズされたテキスト、アニメーション、ビデオなど、HUDに含めることができる要素を詳しく説明しています。クライアントサイドのスクリプトはなく、すべての動的動作はサーバーからのクラス切り替えと文字列設定によって駆動され、ボタンクリックは `CS_UM_CustomHudClicked` メッセージを介してサーバーに送信されます。このスキルは、オーバーレイ(クリック不可の表示専用)とカーソル(クリック可能なインタラクティブHUD)の2種類のHUDをサポートし、`CaptureRoot`のようなdialog変数とクラストグルを使用した進行状況リングの完全なコード例を含んでいます。また、XMLバリデーターのエラーメッセージと一般的な落とし穴のトラブルシューティングガイドも含まれています。

対応~Claude Code~Codex CLI✓Cursor
npx skills add Wend4r/s2r-skills

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Source 2 | カスタムHUDレイアウト は何をしますか?

custom_hud_layout エンティティ(C++ CCSCustomHudLayout)を使用すると、マップやサーバーサイドプラグインが、Panorama XML + VCSS で作成されたHUDをプレイヤーに表示できます。

最初に読んでください: カスタムHUDは、通常の Panoramaドキュメントではありません。読み込まれたXMLは、専用のホワイトリストバリデーターによって実行され、4つのパネルタイプと固定の属性セットのみが許可されます。マークアップに style= はなく、<script> もなく、イベントハンドラもありません。すべての動的動作はサーバーから駆動されます。

完全なパイプライン

custom_hud_layout (サーバー, キーバリュー layout="...")
   │  m_strLayout がクライアントにレプリケートされる
   ▼
CSGOCustomHuds (Panorama ウィンドウ) → layout/hud/customhuds.xml
   └─ <Panel class="WindowRoot" hittest="false">
        └─ <CSGOCustomHud id="CustomHud" style="width:100%; height:100%; flow-children:none;">
             └─ CSGOCustomHudLayoutRoot   ← C++ から作成、エンティティごとに1つ、
                     └─ あなたの XML                id も class もなし

戻りパス(クリック時): ユーザーメッセージ CS_UM_CustomHudClicked (390) → サーバーサイド JS イベント OnCustomHudClicked が { player, layout, buttonId } を運びます。

2種類のHUD

すべてのカスタムHUDは2つのうちのいずれかであり、その違いはプレイヤーごとの単一の呼び出しにあります。

オーバーレイ — デフォルトカーソル — SetInputCaptureEnabled(slot, true)
説明画面上のピクセルプレイヤーが使用できる画面
マウスなし; クロスヘアは照準を続けるHUD上にカーソルが表示される
クリック何もクリックできないButton のクリックがサーバーに届く
:hoverマッチしない動作し、サーバーに負荷はかからない
用途タイマー、スコアボード、キルフィード、バナーショップ、投票ダイアログ、ピッカー、メニュー

キャプチャはレイアウトが構築されるたびにオフになるため、エンティティがスポーンした後に再度オンにする必要があります — entity.mdのInput captureを参照してください。キャプチャされたHUD内では、hittest="false" によってカーソルが通過するパネルが決まります。すべてのパネルにこれを記述し、マウスをキャッチする必要がある数少ないコンテナからは外します。

HUDに実際に含めることができるもの

マークアップの語彙は4つのタグですが、それらを通して配置できるものは見た目よりも広範囲です。

  • SVGアイコン (.vsvg): 任意のUIスケールでシャープに表示され、実行時に wash-color で色付けされます。
  • 独自のフォント: .ttf ファイルをアドオンの panorama/fonts/ にドロップします。
  • プレイヤーごとのローカライズされたテキスト: text="#Token" を介して、各プレイヤーの言語でクライアント側で解決され、独自の文字列またはベースゲーム全体のカタログを使用します。
  • ベースゲームのアートワーク: ダウンロードに何も追加せずに、s2r:// パスで参照します。
  • アニメーションとトランジション: クラストグルによって駆動されるエントリ/終了ペアを含みます。
  • ビデオ: 通常の Panel の background-image レイヤーとして。

Web開発者向け

Panorama は HTML/CSS のような形をしており、これは助けにもなり、落とし穴にもなります。構文は馴染み深く読めますが、プロパティが静かに何も行わないことがあります。ここで有効なメンタルモデルは、クライアントスクリプトが一切ないサーバーレンダリングページであり、存在する更新は パネルのクラスを切り替える と パネルに文字列を設定する の2つだけです。それ以外のすべて — すべてのバリエーション、すべての状態、すべての分岐 — は事前に作成され、単に表示されるだけです。

あなたが書くものここで書くもの
<div><Panel>
<span>, テキストノード<Label text="..."> — テキストはタグの間ではなく属性に存在します
<img src><Image src>、または任意のパネルの background-image
<button onclick><Button id> — クリックはスクリプトではなくサーバーに送られます
style="..."なし; class が唯一のスタイリングフックです
display: nonevisibility: collapse
visibility: hiddenopacity: 0 — レイアウト内に残り、クリック可能です
position: absolutex / y に加えて ignore-parent-flow: true
flexbox, grid親の flow-children、子の horizontal-align / vertical-align
gap子の margin
filter: blur(4px)blur: gaussian( 4, 4, 1 )
rgba(0, 0, 0, 0.5)#00000080 — 色は16進数のみ、アルファは最後のバイトです
background-image 内の linear-gradient(...)background-**color** 内の gradient( ... )
@font-face.ttf を panorama/fonts/ にドロップ; 宣言は不要
:hover:hover — 期待通りに動作する数少ないものの1つ
JS イベントハンドラサーバーイベント OnCustomHudClicked、ボタンの id でキー指定
JS によって読み取られる data-*{s:name} ダイアログ変数、サーバーから設定
ループまたは <template>どちらも存在しません — マークアップまたはスタイルシート内で行を列挙します
calc(), カスタムプロパティどちらも存在しません — スタイルシート内で値を列挙します

Web開発からの2つの習慣が、ここで時間の無駄の大部分を引き起こします。計算値に頼ること: calc() も変数もなく、サーバーは数値を送信できないため、プログレスバーはステップごとのクラスになり、座標はルール内のリテラルになります。そして、クライアントの動作に頼ること: 自分自身で開くドロップダウン、自分自身で切り替わるタブストrip、自分自身で検証するフォーム — これらはすべてラウンドトリップなしでは発生できないため、クリックがサーバーに送られ、クラスが返ってくるというインタラクションを設計してください。

無傷で生き残るのは、デザイナーが最も気にする部分です。ボックスモデル、配置、色、タイポグラフィ、シャドウ、角丸、トランジション、キーフレームアニメーションはすべて、視覚的な作業がそのまま転送されるのに十分なほどWebに近い動作をします。完全なプロパティセットと欠落しているものの完全なリストについては、references/css.md を参照してください。

クイックスタート

実例: キャプチャポイントインジケーター — 画面下部に固定された読み取り専用のストripで、放射状の進行リングを持ち、ダイアログ変数とクラストグルによって完全に駆動されます。クリック可能な要素がないため、入力キャプチャは必要ありません。

作成されたソースは、アドオンのコンテンツルート内の panorama/layout/custom_game/ と panorama/styles/custom_game/ に配置され、コンパイラによって .vxml_c / .vcss_c に変換されます。

panorama/layout/custom_game/capture_point.xml
panorama/styles/custom_game/capture_point.css

1. マークアップ — panorama/layout/custom_game/capture_point.xml

<root>
	<styles>
		<include src="s2r://panorama/styles/custom_game/capture_point.vcss_c" />
	</styles>

	<!--
		外側のパネルには id を付けないでください。ローダーが割り当て、コンパイラが拒否します。
		匿名のフルスクリーンラッパーのままにし、内部に実際のルートを配置します。
	-->
	<Panel class="cp-screen" hittest="false">

		<!-- 非表示で開始: エンティティはスポーンした瞬間にこのレイアウトを EVERY プレイヤーに表示するため、作成された状態は空の状態でなければなりません。 -->
		<Panel id="CaptureRoot" class="cp-root hidden" hittest="false">

			<Panel class="cp-ring">
				<Panel class="cp-ring-track" hittest="false" />
				<!-- サーバーは cp-fill のクラスを cp-fill-0 … cp-fill-100 に書き換えます。
				     スタイルシートは各ステップを clip: radial() スイープに変換します。 -->
				<Panel id="CaptureFill" class="cp-ring-fill cp-fill-0" hittest="false" />
				<Label id="CapturePct" class="cp-pct" text="{s:pct}" />
			</Panel>

			<Panel class="cp-copy">
				<Label id="CaptureName" class="cp-name" text="{s:point_name}" />
				<Label id="CaptureHint" class="cp-hint" text="{s:hint}" />
			</Panel>

		</Panel>
	</Panel>
</root>

2. スタイル — panorama/styles/custom_game/capture_point.css(インラインスタイルは一切ありません)

/* フルスクリーンラッパー。noclip により、リングのグローがパネルの端を超えて溢れ出し、
   切り取られるのを防ぎます。ラッパー自体はヒットテストされません。 */
.cp-screen
{
	width: 100%;
	height: 100%;
	overflow: noclip;
}

/* ストリップ自体。下端の上に配置されます。非表示でオフセットされた状態で開始し、
   サーバーが "show" を追加して表示を再生します。これが .hidden の代替である理由です。
   折りたたまれたパネルはレイアウトから外れており、アニメーションの基点がありません。 */
.cp-root
{
	flow-children: right;
	horizontal-align: center;
	vertical-align: bottom;
	margin-bottom: 96px;
	opacity: 0;
	transform: translateY(20px);
	transition: opacity 0.15s ease-out 0.0s, transform 0.15s ease-out 0.0s;
}

/* 表示された状態。両方のプロパティが上記のトランジションにリストされているため、
   クラスの追加と削除によってアニメーションが双方向に実行されます。 */
.cp-root.show
{
	opacity: 1;
	transform: translateY(0px);
}

/* パネルをレイアウトから完全に削除するため、隣接する要素は穴を残さずに詰められます。
   これ以外でパネルを折りたたむものはありません — opacity: 0 はスペースを予約したままにします。 */
.hidden
{
	visibility: collapse;
}

/* 2つのリング画像が内部に積み重なる正方形のボックス。両方ともボックスを埋めるため、正確に整列します。 */
.cp-ring
{
	width: 56px;
	height: 56px;
}

/* 下にある塗りつぶされていないリング。上部の塗りつぶしが進捗として読み取られるように薄暗く表示されます。
   アートワークは s2r:// 経由でコンパイルされたテクスチャから取得されます。標準的な形式です。 */
.cp-ring-track
{
	width: 100%;
	height: 100%;
	background-image: url( "s2r://panorama/images/custom_game/ring_png.vtex" );
	background-size: contain;
	background-repeat: no-repeat;
	opacity: 0.25;
}

/* 上部の同じアートワーク。wash-color で色が変更され、以下の .cp-fill-* ルールによって
   セクターごとに表示されます。1つのグレーのソースを色付けする方が、チームごとにテクスチャを
   用意するより効率的です。 */
.cp-ring-fill
{
	width: 100%;
	height: 100%;
	background-image: url( "s2r://panorama/images/custom_game/ring_png.vtex" );
	background-size: contain;
	background-repeat: no-repeat;
	wash-color: #4caf6dff;
}

/* サーバーが選択できる各ステップに対して1つのルール。サーバーはクラスを切り替えることはできますが、
   数値を送信することはできません。clip はレイアウトコストがかからずアニメーションするため、
   より多くのステップを追加してもコストは低く抑えられます。 */
.cp-fill-0
{
	clip: radial( 50% 50%, 0deg, 0deg );
}

.cp-fill-25
{
	clip: radial( 50% 50%, 0deg, 90deg );
}

.cp-fill-50
{
	clip: radial( 50% 50%, 0deg, 180deg );
}

.cp-fill-75
{
	clip: radial( 50% 50%, 0deg, 270deg );
}

.cp-fill-100
{
	clip: radial( 50% 50%, 0deg, 360deg );
}

/* リングの右側にあるテキスト列。リングに対して中央揃え。 */
.cp-copy
{
	flow-children: down;
	vertical-align: center;
	margin-left: 12px;
}

/* ポイント名 — プレイヤーが最初に読む行。 */
.cp-name
{
	font-size: 20px;
	font-weight: bold;
	color: #ffffffff;
}

/* 補助行。2番目の色ではなくアルファで控えめに表示。 */
.cp-hint
{
	font-size: 14px;
	color: #ffffff99;
}

/* チームアクセント。サーバーはルートに team-ct または team-t を設定し、子孫セレクターが
   塗りつぶしを再描画します。そのため、チームカラーと進捗は互いに独立しています。 */
.cp-root.team-ct .cp-ring-fill
{
	wash-color: #56a0ddff;
}

.cp-root.team-t .cp-ring-fill
{
	wash-color: #f0a531ff;
}

3. エンティティ — Hammer または CreateEntityByName を使用

キーバリュー値
layoutpanorama/layout/custom_game/capture_point.vxml

4. サーバーからの駆動 (cs_script 下のサーバーサイドJS) — entity.md を参照

const hud = Instance.FindEntityByName("capture_hud"); // JS ラッパークラス: CustomHudLayout

function showCapture(slot, name, pct, team) {
	hud.SetHasClassForPlayer(slot, "CaptureRoot", "show", true);
	hud.SetHasClassForPlayer(slot, "CaptureRoot", "team-" + team, true);

	// ステップクラスを交換: 古いクラスをクリアし、新しいクラスを設定
	hud.SetHasClassForPlayer(slot, "CaptureFill", "cp-fill-" + roundTo25(pct), true);

	hud.SetDialogVariableStringForPlayer(slot, "CapturePct", "pct", pct + "%");
	hud.SetDialogVariableStringForPlayer(slot, "CaptureName", "point_name", name);
	hud.SetDialogVariableStringForPlayer(slot, "CaptureHint", "hint", "Hold the point");
}

function hideCapture(slot) {
	hud.SetHasClassForPlayer(slot, "CaptureRoot", "show", false);
}

サーバーはクラスの切り替えと文字列の設定しかできず、数値を送信できないため、進行リングのような連続値は、ステップごとのクラスに量子化する必要があります。キャプチャリングには5ステップで十分です。より滑らかなバーには、より多くのルールが必要になるだけです。

よくある拒否理由

やりたいこと失敗する理由
<TextEntry>, <ProgressBar>, <Movie>, <ToggleButton>, その他のタグPanel, Label, Image, Button のみ。ビデオには <Movie> の代わりに background-image レイヤーを使用します
要素の style="..."どのパネルタイプにも style 属性はありません
onactivate="..." または任意のマークアップイベントハンドラなし。クリックは id でキー指定されてサーバーに届きます
<scripts>, <snippets>, <snippet>ASTノードタイプレベルで拒否されます
別の .xml の <include>.vcss リソースのみ参照可能です
属性としての dialogvariable="..."サーバーは SetDialogVariableString を介して変数を設定します
<Button> の hittestButton は id と class のみを受け入れます

完全な許可セット: xml.md。

デバッグ

バリデーターは、custom_hud ロギングチャンネルに警告レベルでログを出力します。

メッセージ原因
Layout xml is an invalid resource name "%s"layout がリソース名に解決されない
Failed to load layout '%s'.ファイルが見つからないか、コンパイルされていない
Layout contains disallowed node '%s' (type: %d).<scripts>/<snippets>/<snippet> など
Layout contains disallowed panel type '%s'.許可された4つ以外のタグ
Layout contains disallowed attribute %s for panel type '%s'.そのタグのリストにない属性
Layout contains reference to disallowed resource type '%s'.<include> が .vcss 以外のリソースをターゲットにしている
Layout xml did not pass CustomHud validation "%s"最終的な拒否
Layout is invalid.親がパネルでない属性ノード

検証に失敗すると、パネルは作成されません — 空白の画面、部分的なレンダリングはありません。実行中のレイアウトを無効な状態にホットリロードすると、既存のHUDは破棄されます。

参考文献

ファイル内容
xml.md完全なタグ/属性ホワイトリスト、ASTノードタイプ、バリデータールール
css.md全140のPanorama CSSプロパティ、単位、フロー/配置の値セット、事前に...

...(トリミングされました。翻訳用)

関連スキル