AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Tools Unity Ugui

skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-ugui · by tjboudreaux

Unity UI patterns including Canvas optimization, list virtualization, and mobile-friendly UI.

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-ugui

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-ugui)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
6mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Tools Unity Ugui? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Unity uGUI

Overview

Unity's uGUI system provides flexible UI but requires careful optimization to maintain performance, especially on mobile devices.

When to Use

  • Game menus and HUD
  • Inventory and shop screens
  • Scrolling lists
  • Modal dialogs
  • Health bars and floating text

Canvas Optimization

Canvas Splitting

// WRONG: Everything on one canvas
// Every change causes full rebuild

// RIGHT: Split by update frequency
public class CanvasOrganizer : MonoBehaviour
{
    [Header("Static UI - Never changes")]
    [SerializeField] private Canvas _staticCanvas;  // Background, frames
    
    [Header("Semi-Dynamic - Changes occasionally")]
    [SerializeField] private Canvas _dynamicCanvas; // Buttons, icons
    
    [Header("Highly Dynamic - Changes every frame")]
    [SerializeField] private Canvas _hudCanvas;     // Health, mana, timers
    
    private void Start()
    {
        // Static canvas doesn't need updates
        _staticCanvas.GetComponent().enabled = false;
    }
}

Canvas Group for Visibility

public class UIPanel : MonoBehaviour
{
    private CanvasGroup _canvasGroup;
    
    private void Awake()
    {
        _canvasGroup = GetComponent();
    }
    
    // BETTER than SetActive - doesn't rebuild canvas
    public void Show()
    {
        _canvasGroup.alpha = 1f;
        _canvasGroup.interactable = true;
        _canvasGroup.blocksRaycasts = true;
    }
    
    public void Hide()
    {
        _canvasGroup.alpha = 0f;
        _canvasGroup.interactable = false;
        _canvasGroup.blocksRaycasts = false;
    }
    
    public async UniTask FadeIn(float duration)
    {
        _canvasGroup.interactable = true;
        _canvasGroup.blocksRaycasts = true;
        
        float elapsed = 0;
        while (elapsed (true);
        
        foreach (var graphic in graphics)
        {
            // Only enable on actually clickable items
            bool isInteractive = 
                graphic.GetComponent() != null ||
                graphic.GetComponent() != null ||
                graphic.GetComponent() != null ||
                graphic.GetComponent() != null;
            
            graphic.raycastTarget = isInteractive;
        }
    }
}

List Virtualization

Simple Virtual List

public class VirtualizedList : MonoBehaviour 
    where TView : Component
{
    [SerializeField] private ScrollRect _scrollRect;
    [SerializeField] private RectTransform _content;
    [SerializeField] private TView _itemPrefab;
    [SerializeField] private float _itemHeight = 100f;
    
    private readonly List _visibleItems = new();
    private readonly Queue _recycledItems = new();
    private IList _dataSource;
    private Action _bindAction;
    
    private int _firstVisibleIndex;
    private float _viewportHeight;
    
    public void Initialize(IList data, Action bindAction)
    {
        _dataSource = data;
        _bindAction = bindAction;
        
        // Set content height
        _content.sizeDelta = new Vector2(
            _content.sizeDelta.x,
            data.Count * _itemHeight
        );
        
        _viewportHeight = _scrollRect.viewport.rect.height;
        
        _scrollRect.onValueChanged.AddListener(OnScroll);
        
        RefreshVisibleItems();
    }
    
    private void OnScroll(Vector2 _)
    {
        RefreshVisibleItems();
    }
    
    private void RefreshVisibleItems()
    {
        float scrollY = _content.anchoredPosition.y;
        
        int newFirstVisible = Mathf.Max(0, Mathf.FloorToInt(scrollY / _itemHeight));
        int visibleCount = Mathf.CeilToInt(_viewportHeight / _itemHeight) + 2;
        int lastVisible = Mathf.Min(newFirstVisible + visibleCount, _dataSource.Count);
        
        // Recycle items that scrolled out
        for (int i = _visibleItems.Count - 1; i >= 0; i--)
        {
            var item = _visibleItems[i];
            int itemIndex = GetItemIndex(item);
            
            if (itemIndex = lastVisible)
            {
                RecycleItem(item);
                _visibleItems.RemoveAt(i);
            }
        }
        
        // Create/reuse items that scrolled in
        for (int i = newFirstVisible; i  0)
        {
            var item = _recycledItems.Dequeue();
            item.gameObject.SetActive(true);
            return item;
        }
        
        return Instantiate(_itemPrefab, _content);
    }
    
    private void RecycleItem(TView item)
    {
        item.gameObject.SetActive(false);
        _recycledItems.Enqueue(item);
    }
    
    private void PositionItem(TView item, int index)
    {
        var rect = item.GetComponent();
        rect.anchoredPosition = new Vector2(0, -index * _itemHeight);
        item.gameObject.name = $"Item_{index}";
    }
    
    private bool IsItemVisible(int index)
    {
        return _visibleItems.Any(v => GetItemIndex(v) == index);
    }
    
    private int GetItemIndex(TView item)
    {
        var rect = item.GetComponent();
        return Mathf.RoundToInt(-rect.anchoredPosition.y / _itemHeight);
    }
}

Usage Example

public class InventoryScreen : MonoBehaviour
{
    [SerializeField] private VirtualizedList _list;
    
    public void ShowInventory(List items)
    {
        _list.Initialize(items, BindSlot);
    }
    
    private void BindSlot(InventorySlot slot, ItemData data)
    {
        slot.SetIcon(data.Icon);
        slot.SetName(data.Name);
        slot.SetQuantity(data.Quantity);
        slot.OnClick = () => OnItemClicked(data);
    }
}

Safe Text Updates

Cached Text Component

public class SafeText : MonoBehaviour
{
    private TMP_Text _text;
    private string _lastValue;
    
    private void Awake()
    {
        _text = GetComponent();
        _lastValue = _text.text;
    }
    
    // Only update if value changed
    public void SetText(string value)
    {
        if (_lastValue != value)
        {
            _text.text = value;
            _lastValue = value;
        }
    }
    
    public void SetTextFormat(string format, params object[] args)
    {
        var value = string.Format(format, args);
        SetText(value);
    }
}

Zero-Allocation Number Display

public class NumberDisplay : MonoBehaviour
{
    private TMP_Text _text;
    private int _lastValue = int.MinValue;
    private readonly char[] _buffer = new char[16];
    
    private void Awake()
    {
        _text = GetComponent();
    }
    
    public void SetValue(int value)
    {
        if (value == _lastValue) return;
        _lastValue = value;
        
        // Zero-allocation int to string
        int length = IntToChars(value, _buffer);
        _text.SetCharArray(_buffer, 0, length);
    }
    
    private int IntToChars(int value, char[] buffer)
    {
        if (value == 0)
        {
            buffer[0] = '0';
            return 1;
        }
        
        bool negative = value  0)
        {
            buffer[--index] = (char)('0' + value % 10);
            value /= 10;
        }
        
        if (negative)
        {
            buffer[--index] = '-';
        }
        
        int length = buffer.Length - index;
        Array.Copy(buffer, index, buffer, 0, length);
        return length;
    }
}

Layout Optimization

Manual Layout

public class OptimizedGrid : MonoBehaviour
{
    [SerializeField] private int _columns = 4;
    [SerializeField] private float _cellWidth = 100f;
    [SerializeField] private float _cellHeight = 100f;
    [SerializeField] private float _spacing = 10f;
    
    private RectTransform _rect;
    
    private void Awake()
    {
        _rect = GetComponent();
    }
    
    // Call once after adding/removing children
    public void RefreshLayout()
    {
        int index = 0;
        
        foreach (Transform child in transform)
        {
            if (!child.gameObject.activeSelf) continue;
            
            int row = index / _columns;
            int col = index % _columns;
            
            var childRect = child.GetComponent();
            childRect.anchoredPosition = new Vector2(
                col * (_cellWidth + _spacing),
                -row * (_cellHeight + _spacing)
            );
            childRect.sizeDelta = new Vector2(_cellWidth, _cellHeight);
            
            index++;
        }
        
        // Update content size
        int totalRows = Mathf.CeilToInt((float)index / _columns);
        _rect.sizeDelta = new Vector2(
            _rect.sizeDelta.x,
            totalRows * (_cellHeight + _spacing)
        );
    }
}

Disable Auto Layout

public class LayoutDisabler : MonoBehaviour
{
    private LayoutGroup _layout;
    private ContentSizeFitter _sizeFitter;
    
    private void Start()
    {
        _layout = GetComponent();
        _sizeFitter = GetComponent();
        
        // Disable after initial layout
        StartCoroutine(DisableAfterFrame());
    }
    
    private IEnumerator DisableAfterFrame()
    {
        yield return null;
        
        if (_sizeFitter != null)
            _sizeFitter.enabled = false;
            
        if (_layout != null)
            _layout.enabled = false;
    }
    
    public void ForceRefresh()
    {
        if (_layout != null)
        {
            _layout.enabled = true;
            LayoutRebuilder.ForceRebuildLayoutImmediate(GetComponent());
            _layout.enabled = false;
        }
    }
}

Modal Dialog System

Dialog Manager

public class DialogManager : MonoBehaviour
{
    public static DialogManager Instance { get; private set; }
    
    [SerializeField] private Canvas _dialogCanvas;
    [SerializeField] private GameObject _backdrop;
    
    private readonly Stack _dialogStack = new();
    
    private void Awake()
    {
        Instance = this;
    }
    
    public async UniTask ShowDialog(UIPanel dialogPrefab) where T : UIPanel
    {
        var dialog = Instantiate(dialogPrefab, _dialogCanvas.transform);
        _dialogStack.Push(dialog);
        
        UpdateBackdrop();
        await dialog.FadeIn(0.2f);
        
        return (T)dialog;
    }
    
    public async UniTask CloseTopDialog()
    {
        if (_dialogStack.Count == 0) return;
        
        var dialog = _dialogStack.Pop();
        await dialog.FadeOut(0.15f);
        Destroy(dialog.gameObject);
        
        UpdateBackdrop();
    }
    
    public async UniTask CloseAllDialogs()
    {
        while (_dialogStack.Count > 0)
        {
            var dialog = _dialogStack.Pop();
            Destroy(dialog.gameObject);
        }
        
        UpdateBackdrop();
    }
    
    private void UpdateBackdrop()
    {
        _backdrop.SetActive(_dialogStack.Count > 0);
    }
}

Best Practices

  1. Split canvases by update frequency
  2. Disable Raycast Target on non-interactive elements
  3. Use CanvasGroup for visibility, not SetActive
  4. Virtualize long lists - Only render visible items
  5. Avoid Layout Groups in dynamic content
  6. Cache text components and avoid redundant updates
  7. Pool UI elements - Don't instantiate frequently
  8. Use sprite atlases for UI images
  9. Avoid transparency overdraw - Minimize overlapping alpha
  10. Profile with Frame Debugger - Check batch counts

Troubleshooting

| Issue | Solution | |-------|----------| | High SetPass calls | Use sprite atlases | | Canvas rebuild every frame | Split canvases, check hierarchy | | Scroll stuttering | Virtualize list | | Touch unresponsive | Check Raycast Target, Canvas order | | Layout jitter | Disable auto layout, use manual | | Memory from UI | Pool elements, unload unused atlases |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.