Static controller that spawns a temporary 3D gun preview GameObject above the local player using the current loadout selections, including charms and gems. It creates a root, positions it relative to the camera or world up, clones the gun prefab and attaches charm and gem prefabs to the gun, and provides Show/Hide/Refresh. Also contains a small rotator Component (currently inactive).
using System.Linq;
/// <summary>
/// Manages a temporary 3D gun preview shown above the lobby when the loadout panel is open.
/// </summary>
public static class GunPreviewController
{
static GameObject _previewRoot;
/// <summary>
/// Spawn the preview gun using the player's current loadout selections.
/// Safe to call if a preview is already showing — it will be replaced.
/// </summary>
public static void Show()
{
Hide();
var player = Manager.Instance?.LocalPlayer;
if ( player == null || !player.IsValid() ) return;
var gunId = ProgressManager.GetEffectiveSelectedGunId();
var gunPrefab = ProgressManager.GetPrefabPath( gunId );
if ( string.IsNullOrEmpty( gunPrefab ) )
gunPrefab = "prefabs/guns/gun_default.prefab";
var charmIds = ProgressManager.GetEffectiveSelectedCharmIds();
var charm0 = charmIds.Count > 0 ? ProgressManager.GetPrefabPath( charmIds[0] ) ?? "" : "";
var charm1 = charmIds.Count > 1 ? ProgressManager.GetPrefabPath( charmIds[1] ) ?? "" : "";
var equippedGems = ProgressManager.GetEffectiveEquippedGems();
string GetGem( int i ) => i < equippedGems.Count ? ProgressManager.GetPrefabPath( equippedGems[i] ) ?? "" : "";
// Create a root object positioned above the player in screen space
_previewRoot = new GameObject( "GunPreview" );
_previewRoot.Enabled = true;
float HEIGHT_OFFSET = 130f;
var cam = Game.ActiveScene.Camera;
if ( cam != null )
{
// Move the preview upward on-screen (camera-relative "up") so it appears above the panel
var screenUp = cam.WorldRotation.Up;
_previewRoot.WorldPosition = player.WorldPosition + screenUp * HEIGHT_OFFSET;
}
else
{
_previewRoot.WorldPosition = player.WorldPosition + Vector3.Up * HEIGHT_OFFSET;
}
_previewRoot.Components.Create<GunPreviewRotator>();
// Clone the gun prefab
var gunObj = GameObject.Clone( gunPrefab, new CloneConfig { StartEnabled = true } );
if ( gunObj == null )
{
Log.Warning( "GunPreviewController: failed to clone gun prefab " + gunPrefab );
_previewRoot.Destroy();
_previewRoot = null;
return;
}
gunObj.SetParent( _previewRoot );
gunObj.LocalTransform = new Transform( Vector3.Zero, Rotation.FromYaw( -90f ), Vector3.One );
SpawnCharmonGun( gunObj, charm0, charm1 );
SpawnGemsOnGun( gunObj, GetGem( 0 ), GetGem( 1 ), GetGem( 2 ), GetGem( 3 ) );
}
/// <summary>Destroy the preview gun, if any.</summary>
public static void Hide()
{
if ( _previewRoot != null && _previewRoot.IsValid() )
_previewRoot.Destroy();
_previewRoot = null;
}
/// <summary>Destroy and re-create the preview using current selections. Call after any loadout change.</summary>
public static void Refresh()
{
Hide();
Show();
}
static void SpawnCharmonGun( GameObject gunObj, string charm0, string charm1 )
{
var gun = gunObj.GetComponent<Gun>();
if ( gun == null ) return;
var anchors = gun.GetCharmAnchors();
var paths = new[] { charm0, charm1 };
for ( int i = 0; i < anchors.Count && i < paths.Length; i++ )
{
if ( string.IsNullOrEmpty( paths[i] ) || anchors[i] == null ) continue;
var charmObj = GameObject.Clone( paths[i], new CloneConfig { StartEnabled = true } );
if ( charmObj == null ) continue;
charmObj.SetParent( anchors[i] );
charmObj.LocalTransform = new Transform( Vector3.Zero, Rotation.Identity, Vector3.One );
}
}
static void SpawnGemsOnGun( GameObject gunObj, string gem0, string gem1, string gem2, string gem3 )
{
var gun = gunObj.GetComponent<Gun>();
if ( gun == null || gun.GemSlots == null ) return;
var gemPaths = new[] { gem0, gem1, gem2, gem3 };
for ( int i = 0; i < gun.GemSlots.Count && i < gemPaths.Length; i++ )
{
if ( string.IsNullOrEmpty( gemPaths[i] ) ) continue;
var slot = gun.GemSlots[i];
if ( slot == null ) continue;
var gemObj = GameObject.Clone( gemPaths[i], new CloneConfig { StartEnabled = true } );
if ( gemObj == null ) { Log.Warning( $"GunPreviewController: gem prefab not found: {gemPaths[i]}" ); continue; }
gemObj.SetParent( slot );
gemObj.LocalTransform = new Transform( Vector3.Zero, Rotation.Identity, Vector3.One );
}
}
}
/// <summary>Slowly rotates the gun preview around the world-up axis for a display effect.</summary>
public class GunPreviewRotator : Component
{
protected override void OnUpdate()
{
//WorldRotation = WorldRotation.RotateAroundAxis( Vector3.Up, Time.Delta * 60f );
}
}