Skip to content

Inspector Issues with button.onClick.AddListener #653

@mifsopo1

Description

@mifsopo1

═══════════════════════════════════════════════════════════════════════════════
UNITY MCP DIAGNOSTIC REPORT - UnityEvent Inspector Integration
═══════════════════════════════════════════════════════════════════════════════

REPORT DATE: January 30, 2026
REPORTED BY: mifsopo (Unity Game Developer)
CONTEXT: simulation game development using Unity MCP tools

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ISSUE SUMMARY:

The Unity MCP manage_components tool cannot set UnityEvent listeners (e.g., Button.onClick)
in a way that appears in the Unity Inspector. This forces AI agents to rely on programmatic
listener registration via code, which has significant drawbacks for developer workflows.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CURRENT CAPABILITY ANALYSIS:

✅ WHAT WORKS (via manage_components):

  • Setting simple properties: component.propertyName = value
  • Assigning GameObject/Component references: component.target = instanceID
  • Setting primitive values: int, float, string, bool
  • Modifying serialized fields with basic types

❌ WHAT DOESN'T WORK:

  • Setting UnityEvent targets (Button.onClick, Toggle.onValueChanged, etc.)
  • Configuring UnityEvent method selections
  • Adding multiple UnityEvent listeners via Inspector
  • Setting PersistentListener data structures
  • Configuring dynamic/runtime parameters for Inspector events

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

TECHNICAL BACKGROUND:

UnityEvent Inspector listeners are stored as PersistentCalls:

[Serializable]
public class Button : Selectable, IPointerClickHandler, ISubmitHandler
{
    [SerializeField]
    private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
    
    // Inspector onClick uses PersistentCallGroup internally:
    // - Target GameObject/Component reference
    // - Method name (string)
    // - Argument type
    // - Mode (EventDefined, Void, Object, Int, Float, String, Bool)
}

Setting these requires:

  1. UnityEventTools.AddPersistentListener() OR
  2. Direct manipulation of SerializedObject/SerializedProperty for the UnityEvent
  3. Marking scene/prefab dirty for serialization

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CURRENT WORKAROUNDS (Used by AI):

Workaround 1: Programmatic Registration

void Start()
{
    myButton.onClick.AddListener(OnButtonClicked);
}

PROS:

  • Works reliably
  • AI can generate this code
  • No tool limitations

CONS:

  • NOT visible in Inspector
  • Harder to debug (listeners are runtime-only)
  • No visual indication of what's wired
  • Developers can't easily modify without editing code
  • Breaks Inspector-first Unity workflows

Workaround 2: Manual Instructions
AI provides step-by-step instructions for developers to wire manually.

PROS:

  • Inspector visibility maintained
  • Standard Unity workflow

CONS:

  • Breaks automation
  • Manual steps defeat purpose of AI assistance
  • Time-consuming for developers
  • Error-prone (typos, wrong selections)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

USER IMPACT:

FREQUENCY: High

  • UI systems heavily rely on Button.onClick, Toggle.onValueChanged, etc.
  • Nearly every UI panel requires event wiring
  • Affects shops, menus, HUDs, dialogs, etc.

SEVERITY: Medium-High

  • Doesn't break functionality (programmatic works)
  • Significantly impacts developer experience
  • Creates friction in AI-assisted workflows
  • Makes debugging harder (invisible listeners)

DEVELOPER FEEDBACK:
"is there a reason why you struggle to add on.clicks via inspector code and
always rely on hard coding or manual input?"

This indicates the limitation is noticeable and frustrating in real-world usage.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RECOMMENDED SOLUTIONS:

OPTION A: Add UnityEvent Support to manage_components

Extend manage_components with event listener configuration:

{
  "action": "add",
  "component_type": "Button",
  "target": "StartButton",
  "properties": {
    "onClick": {
      "listeners": [
        {
          "target": "GameManager",
          "method": "StartGame",
          "mode": "EventDefined"
        },
        {
          "target": "AudioManager", 
          "method": "PlayClickSound",
          "mode": "Void"
        }
      ]
    }
  }
}

IMPLEMENTATION NOTES:

  • Use UnityEditor.Events.UnityEventTools.AddPersistentListener()
  • Support all PersistentListenerMode types
  • Handle component type resolution
  • Mark objects dirty for serialization
  • Validate method signatures

OPTION B: New Tool - manage_unity_events

Create dedicated tool for UnityEvent management:

def manage_unity_events(
    target: str,              # GameObject name/path
    component_type: str,      # e.g., "Button"
    event_name: str,          # e.g., "onClick"
    action: str,              # "add", "remove", "clear", "list"
    listener_target: str,     # Target GameObject for listener
    listener_method: str,     # Method name
    listener_mode: str = "EventDefined"  # PersistentListenerMode
) -> dict:

PROS:

  • Clean separation of concerns
  • Explicit event management
  • More detailed control

CONS:

  • Additional tool to maintain
  • More complex for AI to use (separate calls)

OPTION C: Enhanced Properties Syntax

Allow special syntax in properties dict:

{
  "properties": {
    "@onClick": [
      {"target": "GameManager", "method": "StartGame"},
      {"target": "AudioManager", "method": "PlayClick"}
    ]
  }
}

The "@" prefix signals UnityEvent configuration.

PROS:

  • Minimal API changes
  • Fits existing patterns
  • Easy to understand

CONS:

  • Less discoverable
  • Mixing property types
  • Special case handling

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

EXAMPLE USE CASES:

Case 1: Shop UI (Current Project)

  • 85+ shop items with Buy buttons
  • Each needs onClick → ShopManager.BuyItem(itemID)
  • Currently: Manual wiring or programmatic generation
  • With fix: Automated Inspector wiring via AI

Case 2: Pause Menu

  • Resume, Settings, Quit buttons
  • Multiple listeners per button (audio + logic)
  • Currently: Hidden programmatic listeners
  • With fix: Visible Inspector configuration

Case 3: Racing Game UI

  • Track selection buttons (7 tracks)
  • Each button → TrackSelectionUI.SelectTrack(trackID)
  • Currently: Programmatic loop to wire all buttons
  • With fix: AI generates proper Inspector config

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RELATED LIMITATIONS:

Similar issues likely affect:

  • UnityEvent generic variants (e.g., UnityEvent)
  • Custom UnityEvent subclasses
  • Other callback systems (Dropdown.onValueChanged, InputField.onEndEdit)
  • Animation events
  • Timeline callbacks
  • Any serialized delegate/event system

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

TESTING RECOMMENDATIONS:

If implemented, test with:

  1. Button.onClick (most common)
  2. Toggle.onValueChanged
  3. Slider.onValueChanged
  4. Dropdown.onValueChanged
  5. InputField.onEndEdit
  6. ScrollRect.onValueChanged
  7. Custom UnityEvent subclasses
  8. Generic UnityEvent types
  9. Multiple listeners on same event
  10. Listener removal/clearing

Edge cases:

  • Non-public methods (should fail gracefully)
  • Methods with incompatible signatures
  • Component types that don't exist
  • GameObject references that are invalid
  • Circular dependencies

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PRIORITY ASSESSMENT:

BUSINESS IMPACT: Medium-High

  • Affects all UI-heavy projects
  • Common pain point in developer workflows
  • Reduces AI effectiveness for UI work

IMPLEMENTATION COMPLEXITY: Medium

  • Well-documented Unity APIs available
  • Requires careful serialization handling
  • Need proper error handling

USER DEMAND: Confirmed

  • Direct user feedback indicates frustration
  • Workarounds are suboptimal
  • Affects real-world usage patterns

RECOMMENDED PRIORITY: High
This limitation is frequently encountered and has clear workarounds that
significantly degrade the developer experience. Implementation would provide
substantial value to Unity MCP users working on UI-heavy projects.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

REFERENCE IMPLEMENTATION:

Unity C# code for setting Inspector listeners:

using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.Events;

public static void AddInspectorListener(
    Button button, 
    GameObject target, 
    string methodName)
{
    var targetComponent = target.GetComponent<MonoBehaviour>();
    var targetMethod = targetComponent.GetType().GetMethod(methodName);
    
    if (targetMethod != null)
    {
        UnityAction action = Delegate.CreateDelegate(
            typeof(UnityAction), 
            targetComponent, 
            targetMethod
        ) as UnityAction;
        
        UnityEventTools.AddPersistentListener(button.onClick, action);
        
        EditorUtility.SetDirty(button);
    }
}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CONTACT:

For clarification or additional details, contact:

  • Reporter: mifsopo
  • Project: My Hamsters Home (Unity 3D simulation game)
  • MCP Usage: Heavy automation of UI, AI systems, and game mechanics

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

END OF DIAGNOSTIC REPORT

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions