terminalCode Example
WorldPanel and ScreenPanel — HUD overlays and 3D world-space UI with Razor PanelComponent
WorldPanel and ScreenPanel — UI in the World
s&box UI uses PanelComponent (Razor) attached to either a ScreenPanel (HUD overlay) or WorldPanel (3D world-space UI). The same .razor component works with both.
ScreenPanel — HUD Overlay
Renders UI as a 2D overlay on the screen. Use for health bars, crosshairs, inventory, etc.
CSHARP
// In the scene, add a ScreenPanel component to a GameObject,
// then add your PanelComponent to the same GO.
var go = Scene.CreateObject();
go.Name = "HUD";
go.Components.Create<ScreenPanel>();
go.Components.Create<MyHudComponent>();RAZOR
@* MyHudComponent.razor *@
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
<root>
<div class="hud">
<div class="health">HP: @Health</div>
</div>
</root>
@code {
[Property] public float Health { get; set; } = 100f;
protected override int BuildHash() => System.HashCode.Combine( Health );
}WorldPanel — 3D World-Space UI
Renders UI on a flat plane in the world. Use for nameplates, interactive signs, shop menus, etc.
CSHARP
var go = Scene.CreateObject();
go.Name = "Nameplate";
go.WorldPosition = npcHeadPosition;
var worldPanel = go.Components.Create<WorldPanel>();
worldPanel.PanelSize = new Vector2( 200, 50 );
worldPanel.RenderScale = 1f;
worldPanel.LookAtCamera = true; // billboard toward camera
go.Components.Create<NameplateComponent>();PanelComponent Lifecycle
PanelComponent is a Component, so it has OnStart, OnUpdate, etc. Panel (child panels) uses OnAfterTreeRender( bool firstTime ) and Tick() instead.CSHARP
public sealed class MyHudComponent : PanelComponent
{
protected override void OnUpdate()
{
// Force a UI rebuild when data changes
StateHasChanged();
}
}BuildHash — Efficient Rebuilds
The panel only rebuilds its DOM when BuildHash() returns a different value than last frame:
CSHARP
protected override int BuildHash()
=> System.HashCode.Combine( Health, Armor, AmmoCount );Two-Way Binding
RAZOR
<SliderEntry min="0" max="100" step="1" Value:bind=@Volume />
@code {
public float Volume { get; set; } = 0.8f;
}Accessing the Panel from PanelComponent
CSHARP
// PanelComponent wraps a Panel — access it via .Panel
Panel.Style.Opacity = 0.5f;
Was this helpful?