Faction Packed Inventory Item Previewer

Project information

  • Category: UI Tools, RenderTexture Pipeline
  • Project date: 2025 - 2026
  • Role: Technical Artist & Engineer
  • Built for: Faction Packed

Overview

The item previewer is a reusable runtime presentation system for showing live 3D items inside collection screens, item popups, reward flows, and other UI surfaces.

Problem

Items in Faction Packed are not flat icons. They have models, materials, animations, purchase effects, pivot quirks, and item-specific presentation needs. The UI needed a way to show those objects consistently without duplicating preview logic in every popup.

What I Built

  • Shared preview renderer: A single camera and RenderTexture path can feed RawImages across multiple UI contexts.
  • Addressable loading: Items are loaded by ID or addressable key and instantiated into a preview root.
  • Preview API: UI code can request an item preview without knowing the renderer setup details.
  • Drag rotation: Players can inspect items with constrained rotation and damped velocity.
  • Per-item overrides: Item-specific preview data can adjust pivot, scale, max rotation, and presentation behavior.
  • Post-processing-safe modes: The renderer supports different background alpha modes so UI popups and transparent previews can share the same system.

The code examples below are intentionally shortened. They show the reusable shape of the system without publishing full project functions.

// Any screen can bind the shared preview texture, then request an item.
if (renderer.TryAssignToRawImage(itemPreviewRawImage))
{
    await renderer.ShowGridItemAsync(
        idOrKey: itemId,
        backgroundMode: PreviewBackgroundMode.Transparent
    );
}

The UI caller only needs a RawImage and an item ID. The renderer owns the camera, RenderTexture, Addressable lookup, preview root, and background mode, so the same preview path can be reused in vault screens, popups, reward flows, and collection views.

var key = NormalizeAddressableKey(idOrKey);
var handle = Addressables.LoadAssetAsync<GameObject>(key);
await handle.Task;

var instance = Instantiate(
    handle.Result,
    Vector3.zero,
    Quaternion.identity,
    previewerRoot
);

Items can be requested by ID or Addressable key, then spawned into the shared preview root without each UI screen duplicating loading code.

var maxRotation = data.OverrideMaxRotation
    ? data.MaxRotation
    : 360f;

ItemPreviewer.TrySetMaxRotation(maxRotation);
instance.transform.localPosition = data.PivotOffset;
instance.transform.localScale = Vector3.one * data.ScaleOverride;

Item-specific preview data handles presentation quirks like pivot offsets, scale overrides, and rotation limits while preserving a single renderer API.

Technical Notes

The renderer owns a high quality render texture, keeps it matched to screen size when needed, and disables the camera when there are no preview items. It also clears old preview instances and releases addressable handles after instantiation.

The panel helper binds the render texture, waits for the renderer if needed, crops UVs for aspect ratio, and exposes simple methods for load, show, and clear operations.

// The important part is the contract: UI screens do not need renderer internals.
public interface IItemPreviewSurface
{
    RenderTexture RenderTexture { get; }
    bool TryAssignToRawImage(RawImage target);
    Task<GameObject> ShowGridItemAsync(
        string idOrKey,
        PreviewBackgroundMode backgroundMode
    );
}

That narrow contract is what made the previewer useful outside the vault. A screen could opt into the preview system without owning camera setup, Addressable loading, render texture sizing, background alpha, or item cleanup.

Portfolio Takeaway

This is a smaller systems piece, but it is useful for showing production polish: a technical art tool that turns complicated item presentation into a stable UI building block.