terminalCode Example
Slot-based inventory UI with BuildHash optimization
Slot-Based Inventory UI with BuildHash Optimization
This example shows a slot-based inventory UI with BuildHash() optimization to prevent unnecessary rebuilds and input handling for weapon switching.
CSHARP
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits PanelComponent
@implements Global.IPlayerEvents
@if (Player.IsValid() && Player.WantsHideHud)
return;
@if (!inventory.IsValid())
return;
<root>
@for (int i = 0; i < inventory.MaxSlots; i++)
{
<InventorySlot Index=@i Inventory=@inventory Active=@(activeSlot == i)></InventorySlot>
}
<HotbarPresetsButton Inventory=@inventory></HotbarPresetsButton>
</root>
@code
{
[Property, Group("Sound")] public SoundEvent SwitchSound { get; set; }
Player Player => Player.FindLocalPlayer();
PlayerInventory inventory;
int activeSlot = -1;
BaseCarryable prev;
// BuildHash prevents unnecessary rebuilds when state hasn't changed
protected override int BuildHash() => HashCode.Combine(inventory, activeSlot, Player?.WantsHideHud);
void Global.IPlayerEvents.OnPlayerPickup(PlayerPickupEvent e)
{
StateHasChanged();
}
protected override void OnUpdate()
{
inventory = Game.ActiveScene.GetAllComponents<PlayerInventory>()
.Where(x => x.Network.IsOwner).FirstOrDefault();
activeSlot = inventory?.ActiveWeapon?.InventorySlot ?? -1;
}
public void HandleInput()
{
if (inventory is null) return;
MoveSlot(-(int)Input.MouseWheel.y);
if (Input.Pressed("invprev") && prev.IsValid())
{
var weapon = prev;
prev = inventory.ActiveWeapon;
inventory.SwitchWeapon(weapon);
}
if (Input.Pressed("SlotNext")) MoveSlot(1);
if (Input.Pressed("SlotPrev")) MoveSlot(-1);
if (Input.Pressed("Slot1")) SelectSlot(0);
if (Input.Pressed("Slot2")) SelectSlot(1);
if (Input.Pressed("Slot3")) SelectSlot(2);
if (Input.Pressed("Slot4")) SelectSlot(3);
if (Input.Pressed("Slot5")) SelectSlot(4);
if (Input.Pressed("Slot6")) SelectSlot(5);
if (Input.Pressed("Slot7")) SelectSlot(6);
if (Input.Pressed("Slot8")) SelectSlot(7);
if (Input.Pressed("Slot9")) SelectSlot(8);
}
public void SelectSlot(int slot)
{
var weapon = inventory.GetSlot(slot);
if (weapon.IsValid() && !weapon.CanSwitch())
return;
prev = inventory.ActiveWeapon;
inventory.SwitchWeapon(weapon, allowHolster: true);
Sound.Play(SwitchSound);
}
public void MoveSlot(int delta)
{
if (delta == 0) return;
var weapons = inventory.Weapons.Where(x => x.CanSwitch()).ToList();
if (weapons.Count == 0) return;
BaseCarryable current = inventory.ActiveWeapon ?? weapons.FirstOrDefault();
int currentIndex = weapons.IndexOf(current);
currentIndex += delta;
currentIndex %= weapons.Count;
if (currentIndex < 0)
currentIndex = weapons.Count + currentIndex;
var target = weapons[currentIndex];
prev = inventory.ActiveWeapon;
inventory.SwitchWeapon(target);
Sound.Play(SwitchSound);
}
}Key Features
- BuildHash(): Combines inventory, activeSlot, and WantsHideHud to prevent unnecessary UI rebuilds
- Network.IsOwner: Only shows inventory for the local player's owned inventory
- Mouse Wheel: Scroll through weapons with Input.MouseWheel.y
- Slot Selection: Direct slot access with number keys (Slot1-Slot9)
- Weapon Switching: Uses inventory.SwitchWeapon() with allowHolster option
- Sound Feedback: Plays switch sound on slot changes
- Player Events: Implements IPlayerEvents.OnPlayerPickup to update UI on pickups
Was this helpful?