-
Notifications
You must be signed in to change notification settings - Fork 693
Description
═══════════════════════════════════════════════════════════════════════════════
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:
- UnityEventTools.AddPersistentListener() OR
- Direct manipulation of SerializedObject/SerializedProperty for the UnityEvent
- 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:
- Button.onClick (most common)
- Toggle.onValueChanged
- Slider.onValueChanged
- Dropdown.onValueChanged
- InputField.onEndEdit
- ScrollRect.onValueChanged
- Custom UnityEvent subclasses
- Generic UnityEvent types
- Multiple listeners on same event
- 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