@akiojin/unity-mcp-server
Version:
MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows
92 lines (86 loc) • 3.24 kB
JavaScript
import { BaseToolHandler } from '../base/BaseToolHandler.js';
/**
* Handler for starting Unity play mode
*/
export class PlayToolHandler extends BaseToolHandler {
constructor(unityConnection) {
super(
'play_game',
'Enter Play Mode.',
{
type: 'object',
properties: {},
required: []
}
);
this.unityConnection = unityConnection;
}
/**
* Executes the play command
* @param {object} params - Empty object for this command
* @returns {Promise<object>} Play mode state
*/
async execute(params) {
const initialDelayMs = typeof params?.initialDelayMs === 'number' ? params.initialDelayMs : 3000;
const reconnectIntervalMs = typeof params?.reconnectIntervalMs === 'number' ? params.reconnectIntervalMs : 500;
const pollIntervalMs = typeof params?.pollIntervalMs === 'number' ? params.pollIntervalMs : 800;
const maxWaitMs = typeof params?.maxWaitMs === 'number' ? params.maxWaitMs : null; // null = unlimited
try {
if (!this.unityConnection.isConnected()) {
await this.unityConnection.connect();
}
const result = await this.unityConnection.sendCommand('play_game', params);
if (result?.status === 'error') {
const error = new Error(result.error || 'Unity returned error');
error.code = 'UNITY_ERROR';
throw error;
}
const startOk = Date.now();
for (;;) {
try {
const state = await this.unityConnection.sendCommand('get_editor_state', {});
if (state && state.isPlaying) {
return { status: 'success', message: 'Entered play mode', state };
}
} catch {}
await sleep(pollIntervalMs);
if (maxWaitMs != null && Date.now() - startOk > maxWaitMs) {
return result;
}
}
} catch (err) {
const msg = err?.message || '';
const transient = /(Connection closed|timeout|ECONNRESET|EPIPE|socket|Not connected)/i.test(msg);
if (!transient) throw err;
const start = Date.now();
await sleep(initialDelayMs);
// Reconnect until connected (unlimited unless maxWaitMs specified)
for (;;) {
if (this.unityConnection.isConnected()) break;
try { await this.unityConnection.connect(); } catch {}
await sleep(reconnectIntervalMs);
if (maxWaitMs != null && Date.now() - start > maxWaitMs) {
const e = new Error('Timed out waiting to reconnect after Play start');
e.code = 'RECONNECT_TIMEOUT';
throw e;
}
}
// Poll for isPlaying
for (;;) {
try {
const state = await this.unityConnection.sendCommand('get_editor_state', {});
if (state && state.isPlaying) {
return { status: 'success', message: 'Entered play mode (reconnected after reload)', state };
}
} catch {}
await sleep(pollIntervalMs);
if (maxWaitMs != null && Date.now() - start > maxWaitMs) {
const e = new Error('Timed out waiting for Play mode after reconnect');
e.code = 'PLAY_WAIT_TIMEOUT';
throw e;
}
}
}
}
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }