Source 2 | 自訂 HUD 佈局
custom_hud_layout 實體 (C++ CCSCustomHudLayout) 讓地圖或伺服器端外掛程式,能夠使用 Panorama XML + VCSS 為玩家呈現一個 HUD。
請先閱讀此說明: 自訂 HUD 並非一般的 Panorama 文件。載入的 XML 會經過一個專用的白名單驗證器,該驗證器僅允許四種面板類型和一組固定的屬性。標記中沒有 style=、沒有 <script>,也沒有事件處理器。所有動態行為均由伺服器驅動。
完整管線
custom_hud_layout (伺服器端, keyvalue 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++ 建立,每個實體一個,
└─ 你的 XML 沒有 id 和 class
返回路徑 (點擊):玩家訊息 CS_UM_CustomHudClicked (390) → 伺服器端 JS 事件 OnCustomHudClicked,攜帶 { player, layout, buttonId }。
兩種 HUD
每個自訂 HUD 都屬於以下兩種之一,差別在於一個適用於單一玩家的函式呼叫:
| 覆蓋層 (Overlay) — 預設值 | 游標 (Cursor) — SetInputCaptureEnabled(slot, true) | |
|---|---|---|
| 它是什麼 | 螢幕上的像素 | 玩家可以使用的螢幕 |
| 滑鼠 | 無;準星繼續瞄準 | 游標出現在 HUD 上 |
| 點擊 | 沒有東西可以點擊 | Button 的點擊會送達伺服器 |
:hover | 永遠不匹配 | 可以運作,且不會消耗伺服器資源 |
| 用途 | 計時器、記分板、擊殺提示、橫幅 | 商店、投票對話框、選擇器、選單 |
每次佈局建立時,輸入捕捉都會是關閉的,因此需要在實體生成後再次開啟 — 請參閱 entity.md 中的輸入捕捉。在已捕捉的 HUD 中,hittest="false" 決定游標可以穿透哪些面板;請將它寫在所有面板上,然後從必須攔截滑鼠的少數容器上移除它。
HUD 實際可以包含的內容
標記詞彙只有四個標籤,但你可以透過它們放入的內容比看起來更多:
- SVG 圖示 (
.vsvg),在任何 UI 縮放下都保持銳利,可在執行時使用wash-color著色; - 你自己的字型,將
.ttf檔案放入附加元件的panorama/fonts/目錄; - 每位玩家各自的本地化文字,透過
text="#Token"在客戶端以每個玩家自己的語言解析,可使用你自己的字串或整個基礎遊戲目錄; - 基礎遊戲的素材,透過
s2r://路徑引用,無需在你的下載內容中添加任何東西; - 動畫和轉場,包括由類別切換驅動的進入/退出配對;
- 影片,作為普通
Panel上的background-image圖層。
如果你來自網頁開發
Panorama 的形狀類似 HTML/CSS,這既是幫助也是陷阱:語法看似熟悉,然後某個屬性會默默地不產生任何效果。可行的思維模型是 一個完全沒有客戶端腳本的伺服器端渲染頁面,其中僅有的兩種更新是切換面板上的類別和設定面板上的字串。其他所有東西 — 每個變體、每個狀態、每個分支 — 都預先建立好,僅僅是被顯示出來。
| 你會這樣寫 | 在這裡你要這樣寫 |
|---|---|
<div> | <Panel> |
<span>、文字節點 | <Label text="..."> — 文字存在於屬性中,不在標籤之間 |
<img src> | <Image src>,或任何面板上的 background-image |
<button onclick> | <Button id> — 點擊會送到伺服器,而不是腳本 |
style="..." | 無;class 是唯一的樣式鉤子 |
display: none | visibility: collapse |
visibility: hidden | opacity: 0 — 仍在佈局中,仍然可以點擊 |
position: absolute | x / 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 — 顏色只能是十六進位,Alpha 通道是最後一個字節 |
background-image 中的 linear-gradient(...) | background-**color** 中的 gradient( ... ) |
@font-face | 將 .ttf 放入 panorama/fonts/;無需宣告 |
:hover | :hover — 少數幾個完全如預期運作的東西之一 |
| JS 事件處理器 | 伺服器事件 OnCustomHudClicked,由按鈕的 id 作為鍵值 |
由 JS 讀取的 data-* | {s:name} 對話變數,從伺服器設定 |
迴圈或 <template> | 兩者都不存在 — 在標記或樣式表中列舉行數 |
calc()、自訂屬性 | 兩者都不存在 — 在樣式表中列舉值 |
來自網頁工作的兩個習慣會浪費你在這裡的大部分時間。習慣使用計算值:沒有 calc()、沒有變數,伺服器也無法發送數字,因此進度條是每個步驟一個類別,座標則是規則中的字面值。以及習慣使用客戶端行為:會自己打開的下拉選單、會自己切換的分頁、會自己驗證的表單 — 這些都不可能不經過一次往返就發生,因此請圍繞「點擊送到伺服器,類別返回客戶端」來設計互動。
完好無損保留的是設計師最關心的部分:盒模型、對齊、顏色、字體排印、陰影、圓角、轉場和關鍵影格動畫的行為都與網頁足夠接近,視覺工作可以直接轉移。請參閱 references/css.md 以了解完整的屬性集合和所有缺失內容的清單。
快速入門
一個完整的範例:佔點指示器 — 一個固定在螢幕底部、帶有徑向進度環的唯讀條,完全由對話變數和類別切換驅動。它不需要輸入捕捉,因為裡面沒有可點擊的東西。
原始檔案位於附加元件內容根目錄下的 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">
<!-- 預設隱藏:實體一生成就會向**所有**玩家顯示此佈局,因此初始狀態必須是空的。 -->
<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;
}
/* 顯示狀態。兩個屬性都列在上面的 transition 中,因此添加和
移除類別會朝兩個方向產生動畫。 */
.cp-root.show
{
opacity: 1;
transform: translateY(0px);
}
/* 完全從佈局中移除面板,因此相鄰元素會靠攏而不是留下空隙。
沒有其他東西會收攏面板 — opacity: 0 會保留空間。 */
.hidden
{
visibility: collapse;
}
/* 兩個環形圖像疊在內部的方形盒子。兩者都填滿它,因此它們完全對齊。 */
.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-* 規則
一次顯示一個扇形區段。為一個灰色來源著色比為每個隊伍傳送一個紋理更好。 */
.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;
}
/* 伺服器可以選擇的每個步驟一個規則,因為它可以切換類別但不能發送數字。
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;
}
/* 輔助行,使用 Alpha 而非第二種顏色來降低視覺重量。 */
.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
| 鍵值 | 值 |
|---|---|
layout | panorama/layout/custom_game/capture_point.vxml |
4. 從伺服器驅動它 (伺服器端 JS,位於 cs_script 下) — 請參閱 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);
}
由於伺服器只能切換類別和設定字串 — 它不能發送數字 — 像進度環這樣的連續值必須被量化為每個步驟一個類別。對於一個佔點環來說,五個步驟就足夠了;更平滑的條只需要更多規則。
常見拒絕情況
| 你想要什麼 | 為什麼會失敗 |
|---|---|
<TextEntry>、<ProgressBar>、<Movie>、<ToggleButton> 或其他任何標籤 | 只有 Panel、Label、Image、Button — 對於影片,使用 background-image 圖層而不是 <Movie> |
元素上的 style="..." | 沒有面板類型有 style 屬性 |
onactivate="..." 或任何標記事件處理器 | 沒有;點擊會透過 id 送達伺服器 |
<scripts>、<snippets>、<snippet> | 在 AST 節點類型層級被拒絕 |
引入另一個 .xml 的 <include> | 只能引用 .vcss 資源 |
作為屬性的 dialogvariable="..." | 伺服器透過 SetDialogVariableString 設定變數 |
<Button> 上的 hittest | Button 只接受 id 和 class |
完整允許的集合:xml.md。
除錯
驗證器會將訊息記錄到 custom_hud 日誌通道,嚴重性為 warning:
| 訊息 | 原因 |
|---|---|
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'. | 超出四個允許範圍的標籤 |
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 屬性、單位、流向/對齊值集合、預先… |