UNPKG

@akiojin/unity-mcp-server

Version:

MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows

97 lines (89 loc) 3.83 kB
/** * Handler for recording video for a fixed duration (auto-stop). * Orchestrates start→wait→stop via Unity MCP commands. */ import { BaseToolHandler } from '../base/BaseToolHandler.js'; export class CaptureVideoForToolHandler extends BaseToolHandler { constructor(unityConnection) { super( 'capture_video_for', 'Record video for a fixed duration (auto-stop). Optionally enters Play Mode first.', { type: 'object', properties: { captureMode: { type: 'string', enum: ['game'], description: 'Capture source. Currently only "game" supported.' }, width: { type: 'number', description: 'Output width (0 = default 1280)' }, height: { type: 'number', description: 'Output height (0 = default 720)' }, fps: { type: 'number', description: 'Frames per second (default 30)' }, durationSec: { type: 'number', description: 'Duration to record in seconds' }, play: { type: 'boolean', description: 'Enter Play Mode before recording (default true if not already playing)' } }, required: ['durationSec'] } ); this.unityConnection = unityConnection; } /** @override */ async execute(params) { const startTime = Date.now(); let enteredPlay = false; try { // Optionally enter Play Mode (default true if not already playing) let needPlay = !!params.play; if (params.play === undefined) { try { const s0 = await this.unityConnection.sendCommand('get_editor_state', {}); needPlay = !(s0 && s0.isPlaying); } catch { needPlay = true; } } if (needPlay) { await this.unityConnection.sendCommand('play_game', {}); for (let i = 0; i < 60; i++) { const s = await this.unityConnection.sendCommand('get_editor_state', {}); if (s && s.isPlaying) { enteredPlay = true; break; } await sleep(250); } } // Start with auto-stop const { WORKSPACE_ROOT } = await import('../../core/config.js'); const startResp = await this.unityConnection.sendCommand('capture_video_start', { captureMode: params.captureMode || 'game', width: params.width ?? 1280, height: params.height ?? 720, fps: params.fps ?? 30, maxDurationSec: params.durationSec, workspaceRoot: WORKSPACE_ROOT }); if (startResp && startResp.error) { return { error: startResp.error, code: startResp.code || 'UNITY_ERROR' }; } // Wait until stopped (status reports isRecording=false) const deadline = Date.now() + Math.max(0, Math.floor((params.durationSec || 0) * 1000)) + 1500; // small buffer let lastStatus = null; while (Date.now() < deadline) { lastStatus = await this.unityConnection.sendCommand('capture_video_status', {}); if (lastStatus && lastStatus.isRecording === false) break; await sleep(250); } // Safety stop if still recording after deadline if (lastStatus && lastStatus.isRecording) { await this.unityConnection.sendCommand('capture_video_stop', {}); } // Final stop result const stopResp = await this.unityConnection.sendCommand('capture_video_stop', {}); const elapsedMs = Date.now() - startTime; return { ...stopResp, requestedDurationSec: params.durationSec, elapsedMs, message: 'Video recorded for requested duration and stopped' }; } catch (e) { return { error: e.message, code: 'CLIENT_ERROR' }; } finally { // If we entered play, attempt to leave play (best-effort) try { if (enteredPlay) await this.unityConnection.sendCommand('stop_game', {}); } catch {} } } } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }