UNPKG

@akiojin/unity-editor-mcp

Version:

MCP server for Unity Editor integration - enables AI assistants to control Unity Editor

327 lines (299 loc) 11.7 kB
// Tool definition for get_animator_state export const getAnimatorStateToolDefinition = { name: 'get_animator_state', description: 'Get Animator state: layers, transitions, and parameter values for a GameObject.', inputSchema: { type: 'object', properties: { gameObjectName: { type: 'string', description: 'Name of the GameObject with the Animator component' }, includeParameters: { type: 'boolean', description: 'Include all animator parameters and their current values. Default: true', default: true }, includeStates: { type: 'boolean', description: 'Include current state information for each layer. Default: true', default: true }, includeTransitions: { type: 'boolean', description: 'Include active transition information. Default: true', default: true }, includeClips: { type: 'boolean', description: 'Include animation clip information. Default: false', default: false }, layerIndex: { type: 'number', description: 'Specific layer index to query (-1 for all layers). Default: -1', default: -1 } }, required: ['gameObjectName'] } }; // Tool definition for get_animator_runtime_info export const getAnimatorRuntimeInfoToolDefinition = { name: 'get_animator_runtime_info', description: 'Get Animator runtime info (IK, root motion, performance) — Play mode only.', inputSchema: { type: 'object', properties: { gameObjectName: { type: 'string', description: 'Name of the GameObject with the Animator component' }, includeIK: { type: 'boolean', description: 'Include IK (Inverse Kinematics) information. Default: true', default: true }, includeRootMotion: { type: 'boolean', description: 'Include root motion information. Default: true', default: true }, includeBehaviours: { type: 'boolean', description: 'Include state machine behaviours. Default: false', default: false } }, required: ['gameObjectName'] } }; // Handler for get_animator_state export async function getAnimatorStateHandler(unityConnection, args) { try { // Check connection if (!unityConnection.isConnected()) { return { content: [ { type: 'text', text: 'Failed to get animator state: Unity connection not available' } ], isError: true }; } // Validate required parameters if (!args.gameObjectName) { return { content: [ { type: 'text', text: 'Failed to get animator state: gameObjectName is required' } ], isError: true }; } // Send command to Unity const result = await unityConnection.sendCommand('get_animator_state', args); // Check for errors if (!result || typeof result === 'string') { return { content: [ { type: 'text', text: `Failed to get animator state: Invalid response format` } ], isError: true }; } if (result.error) { return { content: [ { type: 'text', text: `Failed to get animator state: ${result.error}` } ], isError: true }; } // Format the response let text = result.summary || `Animator state retrieved for '${args.gameObjectName}'`; if (result.controllerName) { text += `\nController: ${result.controllerName}`; } if (result.layers && Array.isArray(result.layers)) { text += `\n\n## Layer States:`; result.layers.forEach(layer => { text += `\n\n### Layer ${layer.layerIndex}: ${layer.layerName || 'Base Layer'}`; text += `\n- Weight: ${layer.layerWeight}`; if (layer.currentState) { text += `\n- Current State: ${layer.currentState.name || `Hash: ${layer.currentState.fullPathHash}`}`; text += `\n - Normalized Time: ${layer.currentState.normalizedTime?.toFixed(3)}`; text += `\n - Speed: ${layer.currentState.speed}`; if (layer.currentState.motion) { text += `\n - Motion: ${layer.currentState.motion}`; } } if (layer.activeTransition) { text += `\n- Active Transition:`; text += `\n - Duration: ${layer.activeTransition.duration}`; text += `\n - Progress: ${(layer.activeTransition.normalizedTime * 100).toFixed(1)}%`; if (layer.activeTransition.nextState) { text += `\n - To: ${layer.activeTransition.nextState.name || 'Unknown'}`; } } }); } if (result.parameters && Object.keys(result.parameters).length > 0) { text += `\n\n## Parameters:`; for (const [name, param] of Object.entries(result.parameters)) { text += `\n- ${name} (${param.type}): ${param.value}`; if (param.defaultValue !== undefined && param.type !== 'Trigger') { text += ` [default: ${param.defaultValue}]`; } } } return { content: [ { type: 'text', text: text } ], isError: false }; } catch (error) { return { content: [ { type: 'text', text: `Failed to get animator state: ${error.message}` } ], isError: true }; } } // Handler for get_animator_runtime_info export async function getAnimatorRuntimeInfoHandler(unityConnection, args) { try { // Check connection if (!unityConnection.isConnected()) { return { content: [ { type: 'text', text: 'Failed to get animator runtime info: Unity connection not available' } ], isError: true }; } // Validate required parameters if (!args.gameObjectName) { return { content: [ { type: 'text', text: 'Failed to get animator runtime info: gameObjectName is required' } ], isError: true }; } // Send command to Unity const result = await unityConnection.sendCommand('get_animator_runtime_info', args); // Check for errors if (!result || typeof result === 'string') { return { content: [ { type: 'text', text: `Failed to get animator runtime info: Invalid response format` } ], isError: true }; } if (result.error) { return { content: [ { type: 'text', text: `Failed to get animator runtime info: ${result.error}` } ], isError: true }; } // Format the response let text = result.summary || `Animator runtime info retrieved for '${args.gameObjectName}'`; text += `\n\n## General Info:`; text += `\n- Controller: ${result.runtimeAnimatorController || 'None'}`; text += `\n- Update Mode: ${result.updateMode}`; text += `\n- Culling Mode: ${result.cullingMode}`; text += `\n- Speed: ${result.speed}`; text += `\n- Playback Time: ${result.playbackTime?.toFixed(3)}`; if (result.avatar) { text += `\n\n## Avatar:`; text += `\n- Name: ${result.avatar.name}`; text += `\n- Valid: ${result.avatar.isValid}`; text += `\n- Human: ${result.avatar.isHuman}`; } if (result.rootMotion) { text += `\n\n## Root Motion:`; text += `\n- Apply Root Motion: ${result.rootMotion.applyRootMotion}`; text += `\n- Has Root Motion: ${result.rootMotion.hasRootMotion}`; text += `\n- Velocity: (${result.rootMotion.velocity.x?.toFixed(2)}, ${result.rootMotion.velocity.y?.toFixed(2)}, ${result.rootMotion.velocity.z?.toFixed(2)})`; text += `\n- Angular Velocity: (${result.rootMotion.angularVelocity.x?.toFixed(2)}, ${result.rootMotion.angularVelocity.y?.toFixed(2)}, ${result.rootMotion.angularVelocity.z?.toFixed(2)})`; } if (result.ikInfo) { text += `\n\n## IK Info:`; text += `\n- Human Scale: ${result.ikInfo.humanScale}`; text += `\n- Feet Pivot Active: ${result.ikInfo.feetPivotActive}`; text += `\n- Pivot Weight: ${result.ikInfo.pivotWeight}`; if (result.ikInfo.goals) { text += `\n\n### IK Goals:`; for (const [goal, data] of Object.entries(result.ikInfo.goals)) { text += `\n- ${goal}:`; text += `\n - Position Weight: ${data.positionWeight}`; text += `\n - Rotation Weight: ${data.rotationWeight}`; } } } if (result.behaviours && result.behaviours.length > 0) { text += `\n\n## State Machine Behaviours:`; result.behaviours.forEach(behaviour => { text += `\n- Layer ${behaviour.layer}: ${behaviour.type} (${behaviour.enabled ? 'Enabled' : 'Disabled'})`; }); } text += `\n\n## Performance:`; text += `\n- Has Bound Playables: ${result.hasBoundPlayables}`; text += `\n- Has Transform Hierarchy: ${result.hasTransformHierarchy}`; text += `\n- Is Optimizable: ${result.isOptimizable}`; text += `\n- Gravity Weight: ${result.gravityWeight}`; return { content: [ { type: 'text', text: text } ], isError: false }; } catch (error) { return { content: [ { type: 'text', text: `Failed to get animator runtime info: ${error.message}` } ], isError: true }; } }