Dieses vollständige Blazor-Beispiel überträgt die SDK-v3-Muster aus den unten genannten HUD-Quellen in ein kleines Projekt: eine gemeinsame Sitzung, getrennte Anzeigeabonnements, eine lokale Zeitanzeige und einmalige Panelaktionen. Verwende SDK 0.3.0-preview.2 und sOU Desktop 0.3.34 oder neuer. Alle angezeigten Werte stammen aus der tatsächlichen Sitzung; fehlende Werte bleiben offen.
Projekt und gemeinsame Sitzung
Erstelle ein Blazor-Projekt wie im SDK-Einstieg und verwende die folgenden vollständigen Dateien. Die benötigten NuGet-Pakete sind auch im SDK-Bundle enthalten. hud.json liegt im Projektverzeichnis, panel.json und index.html unter wwwroot. _Imports.razor gilt für die vier Razor-Komponenten im selben Projekt. Behalte beim Verteilen der abgeleiteten Beispiele die unten genannten GPL-Lizenz- und Quellenhinweise bei.
SDK einrichten und paketieren · Methodenreferenz
Hud.csproj
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup><TargetFramework>net10.0</TargetFramework><RootNamespace>PracticeHud</RootNamespace><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings></PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.14" />
<PackageReference Include="SOU.Hud.Sdk.Blazor" Version="[0.3.0-preview.2]" />
<None Update="hud.json" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
</Project>
PracticeOptions.cs
// SPDX-License-Identifier: GPL-3.0-only
using System.Text.Json.Serialization;
namespace PracticeHud;
public sealed record PracticeOptions(string Caption = "Broadcast");
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(PracticeOptions))]
public partial class PracticeJson : JsonSerializerContext;
Program.cs
// SPDX-License-Identifier: GPL-3.0-only
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using PracticeHud;
using SOU.Hud.Sdk.Blazor;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddSouHud<PracticeOptions>(PracticeJson.Default.PracticeOptions);
await builder.Build().RunAsync();
_Imports.razor
@namespace PracticeHud
@using Microsoft.AspNetCore.Components
@using SOU.Hud.Sdk
@using SOU.Hud.Sdk.Blazor
@using SOU.Hud.Sdk.Presentation
@using SOU.Live.Contracts
@using PracticeHud
@using System.Globalization
App.razor
@* SPDX-License-Identifier: GPL-3.0-only *@
<SouHudHost TOptions="PracticeOptions">
<main>
<PlayerHealth />
<RoundClock />
<PanelBanner />
</main>
</SouHudHost>
SouHudHost besitzt die gemeinsame SDK-Laufzeit. Die Kindkomponenten verwenden dieselbe HudSession und öffnen keine eigenen Liveverbindungen. Der Host wartet auf die synchronisierte Uhr und entfernt die Anzeige bei ungültiger Uhrbereitschaft. Eine Kindkomponente gibt ausschließlich ihre eigenen Abonnements frei; sie beendet nicht die gemeinsam verwendete Sitzung.
Selektive Spieleranzeige
PlayerHealth.razor übernimmt das Prinzip kleiner Renderbereiche aus beiden HUDs. Der Selektor verwendet den ausdrücklich beobachteten Spieler und liefert nur den sichtbaren Text. Änderungen anderer Spieler, Panelwerte oder Kartenkoordinaten lösen bei gleichem Ergebnis keinen neuen Inhalt aus. Ein gelieferter HP-Wert 0 bleibt 0; fehlende oder veraltete Werte ergeben —.
@* SPDX-License-Identifier: GPL-3.0-only *@
<SouHudRegion TOptions="PracticeOptions" TValue="string" Select="ReadHealth" Context="health">
<p>HP: @health</p>
</SouHudRegion>
@code {
static string ReadHealth(HudSessionSnapshot<PracticeOptions> state)
{
if (!state.Ready || state.Game is not { Freshness: DataFreshness.Live } game) return "—";
return game.ObservedPlayer?.Vitals.Health?.ToString(CultureInfo.InvariantCulture) ?? "—";
}
}
Lokale Zeitaktualisierung ohne globalen Render-Timer
RoundClock.razor adaptiert die ausdrücklich gewählte Präsentationsbrücke und Rendergrenze aus NYXIA sowie die lokale Zeitgrenze des WASM-HUDs. WithoutMotion ist hier passend, weil die Anzeige keine Radar-, Granaten- oder Positionsdaten benötigt. Diese Option ist keine allgemeine Vorgabe für andere Anzeigen. Die 16-ms-Bündelung betrifft Renderanfragen; der Timerbereich aktualisiert sich getrennt alle 100 ms.
@* SPDX-License-Identifier: GPL-3.0-only *@
<SouHudSnapshotView TOptions="PracticeOptions" Projection="HudProjectionOptions.WithoutMotion" CoalesceMilliseconds="16" Context="frame">
<HudRenderBoundary RenderKey="@((frame.Snapshot?.SessionId, frame.Snapshot?.Freshness, frame.Snapshot?.ReceivedAt, frame.DisplayGame?.Countdown))"
Now="frame.Now" ClockMilliseconds="100" Context="now">
<p>Round: @Label(frame.Snapshot, now)</p>
</HudRenderBoundary>
</SouHudSnapshotView>
@code {
static string Label(LiveSnapshot? snapshot, DateTimeOffset now)
{
if (snapshot is not { Freshness: DataFreshness.Live, Game: { Countdown: { } countdown } }) return "—";
var remaining = HudCountdown.Remaining(countdown.SecondsRemaining, snapshot.ReceivedAt, now,
paused: countdown.Phase == CountdownPhase.Paused);
return remaining is { } value ? Math.Ceiling(value).ToString("0", CultureInfo.InvariantCulture) : "—";
}
}
Die Restzeit wird immer aus dem gelieferten Sekundenwert, dessen GSI-Empfangszeitpunkt und der synchronisierten Serverzeit neu berechnet. Eine Pause hält den Quellwert; nach unten wird bei 0 begrenzt. Eine alte GSI-Probe wird durch Uhrticks nicht frisch. Ohne Countdown, Empfangszeitpunkt oder gültigen Livezustand zeigt die Komponente —.
Panelaktionen, Reset und Aufräumen
PanelBanner.razor verwendet das Aktionsmuster aus dem aktuellen WASM-HUD: Ein show- oder hide-Befehl ändert nur den lokalen Einblendungszustand. Eine verlorene Bereitschaft oder neue Spielsitzung setzt ihn zurück. Die Caption bleibt ein eigener selektiver Bereich und darf ausdrücklich leer sein. Ein erneutes Rendern oder Wiederverbinden spielt keine alte Aktion ab.
@* SPDX-License-Identifier: GPL-3.0-only *@
@implements IDisposable
@inject HudSession<PracticeOptions> Session
@if (visible)
{
<SouHudRegion TOptions="PracticeOptions" TValue="string" Select='@(state => state.Panel?.Text("production", "caption") ?? state.Options?.Caption ?? "")' Context="caption">
<p role="status">@caption</p>
</SouHudRegion>
}
@code {
IDisposable? reset;
bool visible, disposed;
protected override void OnInitialized()
{
reset = Session.Observe(state => (state.Ready, state.Game?.SessionId), identity => {
if (!disposed) _ = InvokeAsync(() => { if (!disposed) { visible = false; StateHasChanged(); } });
});
Session.PanelAction += Action;
}
void Action(HudPanelAction action)
{
if (action is not { Section: "production", Name: "lowerThird", Value: "show" or "hide" } || disposed) return;
_ = InvokeAsync(() => { if (!disposed) { visible = action.Value == "show"; StateHasChanged(); } });
}
public void Dispose()
{
disposed = true;
reset?.Dispose();
Session.PanelAction -= Action;
}
}
Dispose löst das eigene Reset-Abonnement und den Ereignishandler. Der disposed-Guard verwirft bereits eingeplante UI-Callbacks nach dem Entfernen der Komponente. Ergänze für eigene zeitverzögerte Animationen zusätzlich eine Sitzungs- oder Generationsprüfung und beende deren Timer.
wwwroot/panel.json
[
{"name":"production","label":"Production","inputs":[
{"type":"text","name":"caption","label":"Caption"},
{"type":"action","name":"lowerThird","label":"Banner","values":[
{"name":"show","label":"Show"},{"name":"hide","label":"Hide"}
]}
]}
]
hud.json
{"id":"docs-practice-hud","version":"0.1.0","name":"Documentation Practice HUD","apiVersion":3,"runtime":"blazor-wasm","entryPoint":"index.html"}
wwwroot/index.html
<!DOCTYPE html>
<!-- SPDX-License-Identifier: GPL-3.0-only -->
<html lang="en"><head><meta charset="utf-8"><base href="./"><title>Practice HUD</title><link rel="stylesheet" href="practice.css"></head>
<body><div id="app">Loading…</div><script src="_framework/blazor.webassembly.js"></script></body></html>
wwwroot/practice.css
/* SPDX-License-Identifier: GPL-3.0-only */
html, body { margin: 0; background: transparent; color: #fff; font-family: system-ui, sans-serif; }
main { padding: 24px; font-size: 24px; }
p { margin: 0 0 12px; }
Prüfe das Verhalten in deinem HUD
- Beobachteten Spieler wechseln: HP müssen dem gelieferten Spieler folgen; ohne Spieler keinen Ersatzspieler wählen.
- HP unbekannt, 0 und wieder vorhanden sowie GSI stale/disconnected prüfen. Eine fehlende Probe bleibt unbekannt.
- Countdown laufen lassen und pausieren; nur die Zeitfläche darf ihren lokalen Takt benötigen.
- Panelcaption speichern, show/hide auslösen, Verbindung unterbrechen und neue Basis annehmen. Eine vorherige Einblendung bleibt nach Reset aus.
- Komponente entfernen und erneut einfügen: keine doppelten Listener oder Aktionen. Browser und OBS mit dem fertigen Paket prüfen.
Originalquellen und Lizenzen
Die Quellbeispiele sind für diese Anleitung angepasste, abgeleitete Beispiele unter GPL-3.0-only. Copyright (c) 2026 sofc; Änderungen für die sOU-Dokumentation 2026. Die vollständigen Dateien stehen oben. Originalgrafiken, Schriften und private Layoutvorlagen wurden nicht übernommen. SDK, NuGet-Pakete und SDK-Vorlagen behalten ihre MIT-Lizenz; diese Beispielhinweise ändern deren Lizenz nicht.