terminalCode Example

Slot-based inventory UI with BuildHash optimization

calendar_today May 12, 2026 schedule ~1 min read person patrickjr verified 50

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?