Inventory.cs
using Sandbox;
public sealed class Inventory : Component
{
[Property] public InventoryPanel InventoryPanel;
private float _appliedSlotHeightScale = 1f;
protected override void OnStart()
{
InventoryPanel.GameObject.Enabled = !IsProxy && !Game.IsRunningInVR;
}
/// <summary>
/// Runs in OnPreRender so it is guaranteed to follow this frame's locomotion (all OnUpdates run
/// before any OnPreRender), and reads Input.VR.Head directly rather than the camera GameObject so
/// it does not depend on where the camera's VRTrackedObject lands in the component update order.
/// </summary>
protected override void OnPreRender()
{
if ( IsProxy || !Game.IsRunningInVR )
return;
var player = GetComponentInParent<PlayerCharacterController>();
if ( !player.IsValid() )
return;
var head = Input.VR.Head;
// Yaw only so hip/chest slots stay at body height while still tracking look heading.
WorldRotation = Rotation.FromYaw( head.Rotation.Angles().yaw );
ApplyCalibratedSlotHeights( player.HeightScale );
// Keep authored slot offsets relative to the head: standing matches the scaled prefab,
// crouch/sit drops the holsters by the same amount the headset dropped.
var standingEyeHeight = player.HeightScale * PlayerCharacterController.ReferenceStandingEyeHeight;
var headHeight = head.Position.z - player.WorldPosition.z;
LocalPosition = Vector3.Up * (headHeight - standingEyeHeight);
}
/// <summary>
/// Prefab slots are authored for the 64" reference citizen. Scale their local height
/// so holsters sit on the avatar after VR standing-eye-height calibration.
/// </summary>
private void ApplyCalibratedSlotHeights( float heightScale )
{
if ( heightScale <= 0f || heightScale.AlmostEqual( _appliedSlotHeightScale ) )
return;
var factor = heightScale / _appliedSlotHeightScale;
foreach ( var slot in GetComponentsInChildren<InventorySlot>() )
{
var pos = slot.LocalPosition;
slot.LocalPosition = pos.WithZ( pos.z * factor );
}
_appliedSlotHeightScale = heightScale;
}
}